frontdoor rule · FD001

GitHub Actions OIDC: no subject condition on IAM role

Nothing pins who may come through the door

Amazon Web Services, Google Cloud, Microsoft AzureUpdated Source on GitHub

Severity: critical. High instead when a real narrowing condition holds the door (aws:PrincipalOrgID, sts:ExternalId), or when it is a SAML trust — that admits every user of your identity provider, which is far too wide but is not the open internet.

What it means

A federated trust has two halves: which issuer you accept tokens from, and which identity at that issuer you accept. This finding means the second half is missing.

Registering GitHub Actions as an OIDC provider does not scope anything to your repositories. GitHub mints a token for every repository on the platform. Without a condition on the subject claim, all of them satisfy the trust policy.

jsonc
// Any GitHub repository in the world can assume this role.
"Condition": {
  "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }
}

The audience condition above looks like a restriction. It is not one: it says the token was minted for AWS, not who minted it.

What an attacker does

Creates a free repository, adds a three-line workflow that requests an OIDC token, assumes your role. There is no exploit and no vulnerability — the policy is doing exactly what it says.

How to fix it

AWS

jsonc
"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:ACME/api:ref:refs/heads/main"
  }
}

Use StringEquals for an exact subject. StringLike is for a subject that genuinely needs a wildcard, and every wildcard is a decision worth writing down.

GCP — two things must be true. The IAM binding has to name an identity rather than the whole pool, and the provider should carry an attributeCondition:

bash
gcloud iam service-accounts add-iam-policy-binding SA_EMAIL \
  --role=roles/iam.workloadIdentityUser \
  --member='principalSet://iam.googleapis.com/projects/N/locations/global/workloadIdentityPools/POOL/attribute.repository/ACME/api'

A member ending in /* binds every identity the pool will ever admit.

Azure — set a subject on the federated identity credential. A credential with no subject and no claims-matching expression accepts any token the issuer produces. A multi-tenant app registration (signInAudience of AzureADMultipleOrgs) is the same problem by a different route: any tenant can consent to it.

When this is a false positive

Rarely, but it happens: a GCP pool used by exactly one workload, where the attributeCondition pins the subject and the binding is deliberately pool-wide. frontdoor reads the condition and does not fire in that case. If it fires anyway, that is a bug worth reporting.

frontdoor answers this once, when you run it; Secorvia checks it continuously across every account you connect, on a free tier that needs no card.