From 658b499f8ae05418a100be2b45fa5bd578cb0b07 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 11 Jun 2026 17:05:32 +0100 Subject: [PATCH 1/3] Add `[ACCEPT]` trigger --- src/psrt_ghsa_bot/app.py | 11 +++++++++++ tests/test_app.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/psrt_ghsa_bot/app.py b/src/psrt_ghsa_bot/app.py index d860e71..aaebc59 100644 --- a/src/psrt_ghsa_bot/app.py +++ b/src/psrt_ghsa_bot/app.py @@ -161,6 +161,17 @@ def apply_to_repo( print(f" ๐Ÿงน Closed {ghsa_id}") continue + # If the summary contains '[ACCEPT{ED}]' we can move the ticket to draft + if state == "triage" and re.search(r"\[ACCEPT(?:ED)?\]", summary.upper()) is not None: + github.rest.security_advisories.update_repository_advisory( + owner=owner, + repo=repo, + ghsa_id=ghsa_id, + data={"state": "draft"}, + ) + print(f" โœ… Accepted {ghsa_id}") + continue + # Advisories that are in the 'draft' state without a private # fork active will have a fork requested. if state == "draft" and security_advisory.get("private_fork") is None: diff --git a/tests/test_app.py b/tests/test_app.py index 4c444fe..83419c0 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -226,6 +226,34 @@ def test_closes_advisory_with_close_or_complete_tag(summary) -> None: ) +@pytest.mark.parametrize( + "summary", + [ + "[ACCEPT] perl is better than Python", + "[ACCEPTED] 0.1 + 0.2 is broken?!?!?!?!?!", + "fix soemthing in datetime module [ACCEPTED]", + "blah blah [accepted] lowercase blah", + ], +) +def test_accepts_advisory_with_accept_tag(summary) -> None: + security_advisory = _create_advisory_dict("triage", None, ["psrt"], summary=summary) + + github = mock.Mock() + cve_api = mock.Mock() + + with mock.patch("psrt_ghsa_bot.app.get_repository_advisories") as get_repo_advs: + get_repo_advs.return_value = [security_advisory] + + app.apply_to_repo(github, "owner", "repo", cve_api) + + github.rest.security_advisories.update_repository_advisory.assert_called_once_with( + owner="owner", + repo="repo", + ghsa_id="GHSA-xxxx-xxxx-xxxx", + data={"state": "draft"}, + ) + + def test_load_psrt_members_from_devguide() -> None: with mock.patch("psrt_ghsa_bot.app.urllib3.request") as urllib3_request: resp = mock.Mock() From 6e88a755422f13e599538aafcaeac4c32599a8ef Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Fri, 12 Jun 2026 16:37:19 +0100 Subject: [PATCH 2/3] Seth's review --- src/psrt_ghsa_bot/app.py | 16 +++++----------- tests/test_app.py | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/psrt_ghsa_bot/app.py b/src/psrt_ghsa_bot/app.py index aaebc59..0c8d280 100644 --- a/src/psrt_ghsa_bot/app.py +++ b/src/psrt_ghsa_bot/app.py @@ -161,16 +161,13 @@ def apply_to_repo( print(f" ๐Ÿงน Closed {ghsa_id}") continue + # Maintain a dictionary of updates to make and then submit them all at once. + patch_data = {} + # If the summary contains '[ACCEPT{ED}]' we can move the ticket to draft if state == "triage" and re.search(r"\[ACCEPT(?:ED)?\]", summary.upper()) is not None: - github.rest.security_advisories.update_repository_advisory( - owner=owner, - repo=repo, - ghsa_id=ghsa_id, - data={"state": "draft"}, - ) - print(f" โœ… Accepted {ghsa_id}") - continue + patch_data["state"] = "draft" + print(f" โœ… Will accept {ghsa_id}") # Advisories that are in the 'draft' state without a private # fork active will have a fork requested. @@ -186,9 +183,6 @@ def apply_to_repo( print(f" โš ๏ธ Error creating private fork: {e.response.json()}") raise e - # Maintain a dictionary of updates to make and then submit them all at once. - patch_data = {} - # Advisories that are in the 'draft' state without a CVE ID # should have one allocated by the PSF CVE Numbering Authority. if state == "draft" and security_advisory.get("cve_id") is None: diff --git a/tests/test_app.py b/tests/test_app.py index 83419c0..602b1e1 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -229,7 +229,7 @@ def test_closes_advisory_with_close_or_complete_tag(summary) -> None: @pytest.mark.parametrize( "summary", [ - "[ACCEPT] perl is better than Python", + "[ACCEPT] Python is better than C", "[ACCEPTED] 0.1 + 0.2 is broken?!?!?!?!?!", "fix soemthing in datetime module [ACCEPTED]", "blah blah [accepted] lowercase blah", From 6101af1b059b3353033f7ac96ee55d5811bd50b4 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Fri, 12 Jun 2026 16:42:56 +0100 Subject: [PATCH 3/3] !fixup --- src/psrt_ghsa_bot/app.py | 2 +- tests/test_app.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/psrt_ghsa_bot/app.py b/src/psrt_ghsa_bot/app.py index 0c8d280..406fed7 100644 --- a/src/psrt_ghsa_bot/app.py +++ b/src/psrt_ghsa_bot/app.py @@ -166,7 +166,7 @@ def apply_to_repo( # If the summary contains '[ACCEPT{ED}]' we can move the ticket to draft if state == "triage" and re.search(r"\[ACCEPT(?:ED)?\]", summary.upper()) is not None: - patch_data["state"] = "draft" + patch_data["state"] = state = "draft" print(f" โœ… Will accept {ghsa_id}") # Advisories that are in the 'draft' state without a private diff --git a/tests/test_app.py b/tests/test_app.py index 602b1e1..7d1fc8f 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -235,11 +235,12 @@ def test_closes_advisory_with_close_or_complete_tag(summary) -> None: "blah blah [accepted] lowercase blah", ], ) -def test_accepts_advisory_with_accept_tag(summary) -> None: +def test_accepts_advisory_with_accept_tag(summary, cve_id, cve_reserve_response) -> None: security_advisory = _create_advisory_dict("triage", None, ["psrt"], summary=summary) github = mock.Mock() cve_api = mock.Mock() + cve_api.reserve.return_value = cve_reserve_response with mock.patch("psrt_ghsa_bot.app.get_repository_advisories") as get_repo_advs: get_repo_advs.return_value = [security_advisory] @@ -250,7 +251,7 @@ def test_accepts_advisory_with_accept_tag(summary) -> None: owner="owner", repo="repo", ghsa_id="GHSA-xxxx-xxxx-xxxx", - data={"state": "draft"}, + data={"state": "draft", "cve_id": cve_id}, )