From d03fe88940333b14ca917a4b507bf5e91e183732 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Thu, 16 Oct 2025 16:49:28 +0000 Subject: [PATCH 1/2] Emit metrics on GitHub event_name claim --- tests/unit/oidc/models/test_github.py | 14 +++++++++++++- warehouse/oidc/models/github.py | 21 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/unit/oidc/models/test_github.py b/tests/unit/oidc/models/test_github.py index 7e187b6c9746..da66f4d725e2 100644 --- a/tests/unit/oidc/models/test_github.py +++ b/tests/unit/oidc/models/test_github.py @@ -338,7 +338,7 @@ def test_github_publisher_missing_claims(self, monkeypatch, missing): ] assert scope.fingerprint == [missing] - def test_github_publisher_missing_optional_claims(self, monkeypatch): + def test_github_publisher_missing_optional_claims(self, metrics, monkeypatch): publisher = github.GitHubPublisher( repository_name="fakerepo", repository_owner="fakeowner", @@ -352,6 +352,7 @@ def test_github_publisher_missing_optional_claims(self, monkeypatch): service_ = pretend.stub( jwt_identifier_exists=pretend.call_recorder(lambda s: False), + metrics=metrics, ) signed_claims = { @@ -433,6 +434,17 @@ def test_check_repository(self, truth, claim, valid): check = github.GitHubPublisher.__required_verifiable_claims__["repository"] assert check(truth, claim, pretend.stub()) == valid + def test_check_event_name_emits_metrics(self, metrics): + check = github.GitHubPublisher.__required_verifiable_claims__["event_name"] + publisher_service = pretend.stub(metrics=metrics) + + assert check( + "throwaway", + "pull_request_target", + pretend.stub(), + publisher_service=publisher_service, + ) + @pytest.mark.parametrize( ("claim", "ref", "sha", "valid", "expected"), [ diff --git a/warehouse/oidc/models/github.py b/warehouse/oidc/models/github.py index 121e024604a6..e61c13f75249 100644 --- a/warehouse/oidc/models/github.py +++ b/warehouse/oidc/models/github.py @@ -28,6 +28,8 @@ if typing.TYPE_CHECKING: from sqlalchemy.orm import Session + from warehouse.oidc.services import OIDCPublisherService + GITHUB_OIDC_ISSUER_URL = "https://token.actions.githubusercontent.com" # This expression matches the workflow filename component of a GitHub @@ -152,6 +154,18 @@ def _check_sub( return f"{org}:{repo}".lower() == ground_truth.lower() +def _check_event_name( + ground_truth: str, signed_claim: str, _all_signed_claims: SignedClaims, **kwargs +) -> bool: + # Log the event name + publisher_service: OIDCPublisherService = kwargs["publisher_service"] + publisher_service.metrics.increment( + "warehouse.oidc.claim", tags=["publisher:GitHub", f"event_name:{signed_claim}"] + ) + # Always permit all event names for now + return True + + class GitHubPublisherMixin: """ Common functionality for both pending and concrete GitHub OIDC publishers. @@ -170,6 +184,7 @@ class GitHubPublisherMixin: "repository_owner_id": check_claim_binary(str.__eq__), "job_workflow_ref": _check_job_workflow_ref, "jti": check_existing_jti, + "event_name": _check_event_name, } __required_unverifiable_claims__: set[str] = {"ref", "sha"} @@ -186,7 +201,6 @@ class GitHubPublisherMixin: "run_attempt", "head_ref", "base_ref", - "event_name", "ref_type", "repository_id", "workflow", @@ -275,6 +289,11 @@ def jti(self) -> str: """Placeholder value for JTI.""" return "placeholder" + @property + def event_name(self) -> str: + """Placeholder value for event_name (not used)""" + return "placeholder" + def publisher_url(self, claims: SignedClaims | None = None) -> str: base = self.publisher_base_url sha = claims.get("sha") if claims else None From 308ea43186834983d7c0686a96db956478bf8418 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Thu, 16 Oct 2025 16:55:56 +0000 Subject: [PATCH 2/2] Assert on the metrics call --- tests/unit/oidc/models/test_github.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/oidc/models/test_github.py b/tests/unit/oidc/models/test_github.py index da66f4d725e2..d9e5e941d316 100644 --- a/tests/unit/oidc/models/test_github.py +++ b/tests/unit/oidc/models/test_github.py @@ -444,6 +444,12 @@ def test_check_event_name_emits_metrics(self, metrics): pretend.stub(), publisher_service=publisher_service, ) + assert metrics.increment.calls == [ + pretend.call( + "warehouse.oidc.claim", + tags=["publisher:GitHub", "event_name:pull_request_target"], + ), + ] @pytest.mark.parametrize( ("claim", "ref", "sha", "valid", "expected"),