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.
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 type | Messaging |
|---|---|
| target type | Identity |
| source | <Cloud Tasks queue (Messaging)> |
| target | <configured SA (Identity/ServiceAccount)> |
| conditions | service_state |
| state logic | ACTIVE 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."