gcp-tasks-queue-exposes-oidc-sa

A Cloud Tasks HTTP target queue configured with httpTarget.oidcToken.serviceAccountEmail (or oauthToken) attaches a minted token for that SA to every dispatched request, exposing the SA's credential to the consumer endpoint.

derived gcp emits ExposesCredential

match (record)

{ "field": "httpTarget.oidcToken.serviceAccountEmail", "resource_type": "google.cloud.tasks.v2.Queue" }

where

queue.httpTarget.oidcToken.serviceAccountEmail (or httpTarget.oauthToken.serviceAccountEmail) is non-empty queue.state != DISABLED

emit

source typeMessaging
target typeIdentity
source<Cloud Tasks queue (Messaging)>
target<configured SA (Identity/ServiceAccount)>
conditionsservice_state
state logicACTIVE when the queue is RUNNING and the SA is non-empty — every dispatch mints and attaches a short-lived token for that SA. CONDITIONAL(service_state) for a PAUSED queue (the credential exposure is realized on resume). The token is only delivered at dispatch time; no persistent key material is stored in the queue.

Narrative

Queue {queue.name} is configured with OIDC token SA {sa.name} (httpTarget.oidcToken.serviceAccountEmail). Cloud Tasks mints a short-lived OIDC ID token for {sa.name} and attaches it to every HTTP dispatch - the consumer endpoint receives a bearer token for {sa.name}. Credential-chains rolls this into CanImpersonate if the SA is CredentialsFor a privileged identity.

Raw rule rules/derived/gcp/tasks.yaml

id: gcp-tasks-queue-exposes-oidc-sa
emits: ExposesCredential
description: A Cloud Tasks HTTP target queue configured with httpTarget.oidcToken.serviceAccountEmail
  (or oauthToken) attaches a minted token for that SA to every dispatched request, exposing the SA's credential
  to the consumer endpoint.
match_record:
  resource_type: google.cloud.tasks.v2.Queue
  field: httpTarget.oidcToken.serviceAccountEmail
where:
- queue.httpTarget.oidcToken.serviceAccountEmail (or httpTarget.oauthToken.serviceAccountEmail) is non-empty
- queue.state != DISABLED
emit:
  source_type: Messaging
  target_type: Identity
  source: <Cloud Tasks queue (Messaging)>
  target: <configured SA (Identity/ServiceAccount)>
  permissions: []
  conditions:
  - service_state
  state_logic: "ACTIVE when the queue is RUNNING and the SA is non-empty \u2014 every dispatch mints and\
    \ attaches a short-lived token for that SA. CONDITIONAL(service_state) for a PAUSED queue (the credential\
    \ exposure is realized on resume). The token is only delivered at dispatch time; no persistent key\
    \ material is stored in the queue."
  derived_from:
  - queue.httpTarget.oidcToken.serviceAccountEmail (or oauthToken.serviceAccountEmail) from cloudtasks.queues.get
  false_positive_note: "The OIDC/OAuth token is delivered to the consumer HTTP endpoint, NOT exposed via\
    \ the Cloud Tasks API. An attacker with cloudtasks.tasks.create who does not control the consumer\
    \ endpoint does not directly receive this token \u2014 they trigger the consumer with an attacker-influenced\
    \ payload, but the token goes to the legitimate endpoint. The token is exposed only if: (a) the consumer\
    \ endpoint is attacker-controlled, (b) a uriOverride redirects to an attacker endpoint, or (c) the\
    \ consumer logs/echoes the Authorization header. Do NOT conflate the queue SA with the consumer's\
    \ runtime SA (Cloud Run / GCF SA) \u2014 they are different identities. Only emit ExposesCredential\
    \ from the queue node, not from the consumer. For App Engine queues (no httpTarget), do not emit this\
    \ edge."
  narrative: "Queue {queue.name} is configured with OIDC token SA {sa.name} (httpTarget.oidcToken.serviceAccountEmail).\
    \ Cloud Tasks mints a short-lived OIDC ID token for {sa.name} and attaches it to every HTTP dispatch\
    \ \u2014 the consumer endpoint receives a bearer token for {sa.name}. Credential-chains rolls this\
    \ into CanImpersonate if the SA is CredentialsFor a privileged identity."
move · open · esc close