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..46c9754 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) + 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=slug 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()