frontdoor rule · FD012
GitHub OIDC accepts the pull_request context
The pull_request context is accepted
Severity: high.
What it means
The subject condition accepts the pull-request context:
repo:acme/api:pull_requestGitHub issues that subject for every pull request in the repository. It carries no pull-request number, no source branch, no author, and no indication of whether the pull request came from a fork. There is nothing further to pin it to.
So a condition written against it does not say "this pull request". It says "any pull request in this repository, forever".
Why the credentials arrive before the review does
The usual mental model is that a pull request is safe because it has to be reviewed and merged before anything happens. The OIDC token does not wait for that. It is minted when the workflow starts, which is when the pull request is opened.
Required reviews, required status checks and branch protection all govern what gets merged. None of them govern what a pull-request workflow may do while it runs. If that workflow can assume a role, the role is reachable from an unmerged, unreviewed change.
Who can reach it depends on the repository:
- Anyone with write access can open a pull request from a branch and get a token. If this role deploys to production, every collaborator can deploy to production without going through review, whatever the branch rules say.
- Fork pull requests are more restricted by default: GitHub does not grant
id-token: writeto a workflow triggered by a fork's pull request unless the repository or organisation has been configured to send write tokens to them. That setting exists and is not rare. If it is on, the reachable set is everyone on GitHub.
What is deliberately not claimed here
An earlier version of this page said repo:acme/api:pull_request is the subject
a pull_request_target workflow runs under. GitHub does not document the
subject pull_request_target emits, and since December 2025 that trigger
resolves GITHUB_REF to the repository's default branch, which points the other
way.
The finding stands on what GitHub does document: one subject covers every pull
request. pull_request_target is still worth auditing on its own, because it
runs with the base repository's permissions against code from the pull request,
but that is a separate problem from this condition and this rule does not assert
a link between them.
How to fix it
Work out which workflow needs this role, and whether it has to run on pull requests at all. Most roles caught by this rule are deployment roles that were given a subject wide enough to cover a test run once.
Move the credentials to a subject a pull request cannot produce. A protected environment is the strongest option, because the environment claim is stable across trigger types and can carry its own reviewers:
"StringEquals": { "token.actions.githubusercontent.com:sub": "repo:acme/api:environment:production" }A branch ref works too, if the deploy only ever runs from one branch:
"StringEquals": { "token.actions.githubusercontent.com:sub": "repo:acme/api:ref:refs/heads/main" }If a pull-request workflow genuinely needs cloud access, to run integration tests for example, give it a separate role with read-only permissions in a test account, and suppress this finding against that role with the reason written down in
.frontdoorignore.Check the fork token setting while you are here. Whether the repository sends write tokens to workflows from fork pull requests decides whether the reachable set is your collaborators or the internet.
A note on the immutable subject format
Repositories created after 15 July 2026 use subjects that carry numeric ids:
repo:acme@123456/api@456789:pull_requestThe rule fires the same way, and any fix frontdoor suggests keeps the ids,
because a condition written without them would not match the token at all.
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.