gcp-pubsub-push-sa-exposes-credential

A Pub/Sub push subscription delivers a Google-signed OIDC token for its pushConfig.oidcToken.serviceAccountEmail to the push endpoint on every delivery; code at the endpoint can extract and replay the token, exposing the push SA's identity.

derived gcp emits ExposesCredential

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'match_record': {'resource_type': 'google.pubsub.Subscription', 'field': 'pushConfig.oidcToken.serviceAccountEmail'}, 'where': ['pushConfig.oidcToken.serviceAccountEmail is non-empty (push OIDC auth configured)', '?subscription.state is ACTIVE']}

where

node_type(?subscription) == Queue

emit

source typeMessaging
target typeServiceAccount
source<Pub/Sub subscription node>
target<push SA (ServiceAccount node for oidcToken.serviceAccountEmail)>
conditionstrigger_exists service_state
state logicCONDITIONAL(trigger_exists): the OIDC token is only delivered when messages are being actively pushed (subscription ACTIVE + messages in the topic + push endpoint reachable). ACTIVE when the subscription is confirmed ACTIVE and push delivery is ongoing (subscription has recent delivery metrics). BLOCKED when the subscription is PAUSED or the push SA's actAs is revoked (Pub/Sub Service Agent no longer holds actAs on the push SA).

Narrative

Pub/Sub push subscription {subscription.name} delivers a Google-signed OIDC token for {push_sa.name} to {push_endpoint} on every message POST; code running at the push endpoint can extract the token from the Authorization header and authenticate to GCP APIs as {push_sa.name}.

Raw rule rules/derived/gcp/pubsub.yaml

id: gcp-pubsub-push-sa-exposes-credential
emits: ExposesCredential
description: A Pub/Sub push subscription delivers a Google-signed OIDC token for its pushConfig.oidcToken.serviceAccountEmail
  to the push endpoint on every delivery; code at the endpoint can extract and replay the token, exposing
  the push SA's identity.
match:
- match_record:
    resource_type: google.pubsub.Subscription
    field: pushConfig.oidcToken.serviceAccountEmail
  where:
  - pushConfig.oidcToken.serviceAccountEmail is non-empty (push OIDC auth configured)
  - ?subscription.state is ACTIVE
where:
- node_type(?subscription) == Queue
emit:
  source_type: Messaging
  target_type: ServiceAccount
  source: <Pub/Sub subscription node>
  target: <push SA (ServiceAccount node for oidcToken.serviceAccountEmail)>
  permissions: []
  conditions:
  - trigger_exists
  - service_state
  state_logic: 'CONDITIONAL(trigger_exists): the OIDC token is only delivered when messages are being
    actively pushed (subscription ACTIVE + messages in the topic + push endpoint reachable). ACTIVE when
    the subscription is confirmed ACTIVE and push delivery is ongoing (subscription has recent delivery
    metrics). BLOCKED when the subscription is PAUSED or the push SA''s actAs is revoked (Pub/Sub Service
    Agent no longer holds actAs on the push SA).'
  confidence: 0.85
  derived_from:
  - pubsub.subscriptions.get[pushConfig.oidcToken.serviceAccountEmail, state]
  false_positive_note: "The OIDC token is a SHORT-LIVED bearer token (1-hour expiry by default). The push\
    \ endpoint receives it as an Authorization header and may or may not log/store it \u2014 the ExposesCredential\
    \ fact is about the delivery mechanism, not a confirmed theft. Actual exploitation requires code at\
    \ the push endpoint to extract the token from the request header and use it to call GCP APIs as the\
    \ push SA. The push SA is a DIFFERENT identity from the consumer's runtime SA (Cloud Run / GCF runtime\
    \ SA) \u2014 do NOT conflate them. A push SA with minimal roles (e.g. only run.invoker on the target\
    \ service) is a low-value exposure; a push SA with broad project roles is high-value. The ExposesCredential\
    \ edge feeds credential-chains only if CredentialsFor(token \u2192 push SA) is also emitted."
  narrative: Pub/Sub push subscription {subscription.name} delivers a Google-signed OIDC token for {push_sa.name}
    to {push_endpoint} on every message POST; code running at the push endpoint can extract the token
    from the Authorization header and authenticate to GCP APIs as {push_sa.name}.
move · open · esc close