From f15c4864a87e4d5e0d8c285370f16af08753732e Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 1 Jul 2026 20:06:08 +0200 Subject: [PATCH 1/2] Only reserve CVEs for specific installations --- .env.example | 1 + .github/workflows/cron.yml | 1 + README.md | 6 +++++- src/psrt_ghsa_bot/app.py | 19 +++++++++++++++---- tests/test_app.py | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index f56b89f..7bbe4b0 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,5 @@ GH_AUTH_TOKEN="ghp_123456" CVE_USERNAME="user@example.org" CVE_API_KEY="123456" CVE_ENV="testproddev" +CVE_ENABLED_REPOS="python/cpython" SENTRY_DSN="{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}{PATH}/{PROJECT_ID}" diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index d79d631..6ac8a22 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -44,4 +44,5 @@ jobs: CVE_USERNAME: ${{ vars.CVE_USERNAME }} CVE_API_KEY: ${{ secrets.CVE_API_KEY }} CVE_ENV: ${{ vars.CVE_ENV }} + CVE_ENABLED_REPOS: ${{ vars.CVE_ENABLED_REPOS }} SENTRY_DSN: ${{ github.event_name == 'schedule' && secrets.SENTRY_DSN || '' }} diff --git a/README.md b/README.md index 3d792e0..603967e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,11 @@ PSRT GHSA Bot is a GitHub App that automates the [Python Security Response Team handling of GitHub Security Advisories. It runs hourly (or by manual dispatch) and, for every advisory it closes ones marked as completed, promotes accepted ones from triage to draft, reserves CVE IDs, creates private forks, and adds the -PSRT members as collaborators. +PSRT team as collaborators. + +It processes every repository the GitHub App is installed on. +The process is identical across repositories except that CVE IDs are only +reserved for repositories listed in the `CVE_ENABLED_REPOS` environment variable. ```mermaid flowchart TD diff --git a/src/psrt_ghsa_bot/app.py b/src/psrt_ghsa_bot/app.py index a2ccab0..f28e604 100644 --- a/src/psrt_ghsa_bot/app.py +++ b/src/psrt_ghsa_bot/app.py @@ -119,7 +119,7 @@ def reserve_one_cve(cve_api: CveApi) -> str: return cve_ids[0] -def apply_to_repo(github: GitHub, owner: str, repo: str, cve_api: CveApi) -> None: +def apply_to_repo(github: GitHub, owner: str, repo: str, cve_api: CveApi, *, reserve_cves: bool = True) -> None: """Applies the PSRT GitHub Security Advisory process to the repository.""" security_advisories = get_repository_advisories(github, owner, repo) advisory_count = 0 @@ -173,7 +173,7 @@ def apply_to_repo(github: GitHub, owner: str, repo: str, cve_api: CveApi) -> Non # 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: + if reserve_cves and state == "draft" and security_advisory.get("cve_id") is None: cve_id = reserve_one_cve(cve_api) patch_data["cve_id"] = cve_id print(f" ✅ Will reserve CVE ID: {cve_id}") @@ -219,6 +219,10 @@ def run() -> None: env=os.environ.get("CVE_ENV", "prod"), ) + cve_enabled_repos = frozenset( + name.strip() for name in (os.environ.get("CVE_ENABLED_REPOS") or "python/cpython").split(",") if name.strip() + ) + print("Fetching installations...") # Apply to all repositories for each installation. installations = github.rest.paginate( @@ -238,8 +242,15 @@ def run() -> None: map_func=lambda r: r.parsed_data.repositories, ) for repo in repos: - print(f" Checking repo: {repo.owner.login}/{repo.name}") - apply_to_repo(installation_github, repo.owner.login, repo.name, cve_api) + name = f"{repo.owner.login}/{repo.name}" + print(f"Processing repo: {name}") + apply_to_repo( + installation_github, + repo.owner.login, + repo.name, + cve_api, + reserve_cves=name in cve_enabled_repos, + ) print(f"\nDone! Processed {installation_count} installation(s).") diff --git a/tests/test_app.py b/tests/test_app.py index 3dd8235..25551c8 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -156,6 +156,21 @@ def test_does_not_reserve_cve_id_for_triage_security_advisories(state) -> None: github.rest.security_advisories.update_repository_advisory.assert_not_called() +def test_does_not_reserve_cve_id_when_reserve_cves_disabled() -> None: + security_advisory = _create_advisory_dict("draft", None, ["psrt"]) + + 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, reserve_cves=False) + + cve_api.reserve.assert_not_called() + github.rest.security_advisories.update_repository_advisory.assert_not_called() + + def test_create_private_fork() -> None: github = mock.Mock() cve_api = mock.Mock() From 208a45fbfb90a9c20784be79422e6e8404fbec99 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 1 Jul 2026 20:16:42 +0200 Subject: [PATCH 2/2] `name` -> `slug` --- src/psrt_ghsa_bot/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/psrt_ghsa_bot/app.py b/src/psrt_ghsa_bot/app.py index f28e604..46c9754 100644 --- a/src/psrt_ghsa_bot/app.py +++ b/src/psrt_ghsa_bot/app.py @@ -242,14 +242,14 @@ def run() -> None: map_func=lambda r: r.parsed_data.repositories, ) for repo in repos: - name = f"{repo.owner.login}/{repo.name}" - print(f"Processing repo: {name}") + slug = f"{repo.owner.login}/{repo.name}" + print(f"Processing repo: {slug}") apply_to_repo( installation_github, repo.owner.login, repo.name, cve_api, - reserve_cves=name in cve_enabled_repos, + reserve_cves=slug in cve_enabled_repos, ) print(f"\nDone! Processed {installation_count} installation(s).")