Skip to content

Pass audience to verify_oauth2_token in get_email_from_bearer_token - #5389

Open
herdiyana256 wants to merge 1 commit into
google:masterfrom
herdiyana256:fix-pubsub-push-audience-validation
Open

Pass audience to verify_oauth2_token in get_email_from_bearer_token#5389
herdiyana256 wants to merge 1 commit into
google:masterfrom
herdiyana256:fix-pubsub-push-audience-validation

Conversation

@herdiyana256

@herdiyana256 herdiyana256 commented Jul 25, 2026

Copy link
Copy Markdown

The Pub/Sub push handler authenticates the incoming OIDC token but never checks its audience:

# handlers/.../get_email_from_bearer_token
claim = id_token.verify_oauth2_token(token, google_requests.Request())

verify_oauth2_token treats a missing audience as "do not check it". An ID token is minted for a specific destination, but any party that can mint tokens as the same service account (or that the service account itself calls out to) holds a token signed by that SA for a different audience. With no audience check here, such a token replays against this endpoint and passes.

# request.url is the URL Pub/Sub actually pushed to, which a correctly
# configured OIDC push subscription sets as the token's audience.
claim = id_token.verify_oauth2_token(
    token, google_requests.Request(), audience=request.url)

Binding verification to request.url means only a token minted for this exact push endpoint is accepted, closing the cross-audience replay. Adds a regression test for the missing-audience case.

id_token.verify_oauth2_token() was called without an audience argument.
Per its own docstring, when audience is None the audience/aud claim is
not verified at all. This function is the sole auth check for the
pubsub_push-decorated external_update endpoint, gating on the token's
email claim matching the App Engine default service account.

Since audience wasn't checked, any validly-signed ID token asserting
that same service-account email would be accepted here regardless of
what destination it was actually minted for. The App Engine default
service account is commonly used broadly across a GCP project, so a
token obtained for an entirely different audience (another service or
endpoint authenticating with the same identity) could be replayed
against this endpoint.

Now passes audience=request.url, matching the URL Pub/Sub actually
pushed to, which is what a correctly configured OIDC push subscription
sets as the token's audience.
@herdiyana256
herdiyana256 requested a review from a team as a code owner July 25, 2026 15:52
@herdiyana256

Copy link
Copy Markdown
Author

Gentle bump, open ~3 weeks with no reviewer. Small fix: get_email_from_bearer_token verifies the bearer token without passing an audience, so the pubsub_push endpoint accepts any token bearing the right email claim. @PauloVLB @javanlacerda could one of you take a look or route it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant