A cloud role's OIDC trust policy for CI pins the issuer, the audience and the repository, but not the branch. What can go wrong?
answer
- subject claim is the authorization rule
- repository-only means any branch
- branch protection guards merges, not runs
- pin the ref or the environment
- test it from a scratch branch
basics
~20 sAny pipeline run in that repository can assume the role — including one from a throwaway branch. Anyone able to push a branch and trigger CI can rewrite the pipeline to run arbitrary commands with production credentials, without review.
solid answer
~50 sThe subject claim is the access-control decision, and pinning only the repository grants the role to every run in it. Someone who can push a branch — or land any change that CI executes — writes their own pipeline steps on that branch, triggers a run, and the cloud happily federates them into the production role, because the subject still says the right repository. Branch protection does not help: protection governs merges into the default branch, not what CI executes on an unprotected one. The fix is to make the condition match only runs you would approve: pin the subject to the specific ref, or better, to a protected deployment environment, so the token minted on a random branch simply does not match. Use exact matching rather than a wildcard, keep one role per pipeline instead of a shared one, and check the pattern by trying to assume the role from a scratch branch.
code
json · 5 lines{
"too broad": "repo:acme/payments:*",
"pinned to a branch": "repo:acme/payments:ref:refs/heads/main",
"pinned to an environment": "repo:acme/payments:environment:production"
}go deeper
Know that the trust policy decides which pipeline runs may get cloud credentials, and that naming only the repository is far broader than naming a specific branch.
Explain the subject claim's structure and show the corrected condition, pinning an exact ref or environment rather than a wildcard over the repository.
Demonstrate the attack path end to end — push a branch, edit the pipeline on it, trigger a run — and say why branch protection does not close it, then propose environment-pinned conditions and audit alerting.
Own this as an access-control surface across the estate: one role per pipeline, exact-match conditions, a review path for trust-policy changes, and detection on unexpected subjects rather than trust in the configuration.
## What the subject claim is When a CI platform mints an identity token for a job, it fills the `sub` claim with a structured string describing the run's context — typically the repository, plus what kind of run it is: a branch ref, a tag, a pull request, or a named deployment environment. The cloud's trust configuration attaches a condition to that claim. Whether a run may assume a role is decided entirely by whether its `sub` matches. That makes the subject condition an authorization rule, written in a place most teams treat as connection plumbing. It is usually copied from a quickstart, made permissive enough that the first pipeline works, and never revisited. ## Why repository-only is a much bigger grant than it looks A condition that matches any subject beginning with the repository grants the role to every run in that repository. That set includes: - runs on branches nobody reviews, - runs on tags, - scheduled and manually dispatched runs, - and, depending on platform configuration, runs associated with pull requests. The attack needs no exotic access. Anyone who can push a branch to the repository can add a step to the pipeline definition on that branch, push, and let CI execute it. The pipeline on an unreviewed branch is not the reviewed pipeline on the default branch — it is whatever that branch says it is. The step requests an identity token, exchanges it, and now runs with production credentials. The common misconception is that branch protection covers this. It does not. Protection rules govern what may *merge into* the protected branch. They say nothing about what CI runs on a branch that will never be merged. A contractor with write access, a stale bot account, or an attacker holding one developer's session all clear that bar. ## Pinning it properly The goal is a condition that only runs you would have approved can satisfy. ```json { "too broad": "repo:acme/payments:*", "pinned to a branch": "repo:acme/payments:ref:refs/heads/main", "pinned to an environment": "repo:acme/payments:environment:production" } ``` Pinning to the default branch is a real improvement, because reaching that ref requires passing review. Pinning to a **deployment environment** is usually better still: the environment is a first-class object on the CI side with its own protection — required approvers, allowed branches, wait timers — so the token only carries that subject when the platform has already enforced those gates. It also keeps the identity stable when branch naming changes. A few rules that follow: - Prefer **exact string equality** over wildcard matching. A pattern intended as `repo:acme/payments:ref:refs/heads/release-*` is easy to write in a way that also matches `refs/heads/release-x/../evil`, and wildcards drift wider over time as people paste them around. - Watch the boundary. A prefix condition on `repo:acme/pay` also matches `repo:acme/payments-sandbox`. Anchor the whole value. - **One role per pipeline**, not one shared deploy role. If a single role is federated by twenty repositories, its permissions are the union of twenty needs and the subject condition has to be broad enough for all of them. - Keep the audience pinned as well. A permissive audience lets a token minted for a low-value relying party be replayed at the cloud. ## Verifying rather than assuming This is one of the few security controls you can test directly. Create a scratch branch, add a step that attempts the exchange, and confirm it is refused. Then confirm the intended pipeline still succeeds. If the scratch branch gets credentials, the condition is wrong regardless of what the documentation says. On the detection side, the cloud's audit log records each federated assumption along with the subject presented. Alerting on assumptions of production roles whose subject is outside the expected set catches both misconfiguration and abuse, and it is far cheaper than reasoning about the pattern by eye. ## The wider point OIDC federation is often sold as "no more stored keys", and that part is true. But it moves the risk rather than deleting it: the question changes from *who can read the secret* to *which runs match the condition*. A team that migrates to federation and writes a repository-only subject condition has replaced a stolen-key risk with an anyone-with-push-access risk, and has usually not noticed the trade.
- Why is pinning the subject to a deployment environment often better than pinning it to a branch?Because the environment carries its own gates. The platform only stamps that subject on a run that has already satisfied the environment's protection — required approvers, allowed branches, wait timers — so the cloud inherits those checks for free. It also survives branch renames and lets one repository hold several roles with genuinely different trust conditions.
- A team argues branch protection already prevents this. What is the flaw in that argument?Branch protection controls what merges into the protected branch. It does not control what CI executes on an unprotected branch, and the pipeline definition CI runs there is the one that branch contains. If the trust condition accepts any ref, an unreviewed branch is a fully authorized path to the role.
- How would you detect that a role is being assumed from an unexpected pipeline context?The cloud's audit records for federated assumptions include the subject that was presented. Emit an alert whenever a production role is assumed with a subject outside the expected set. That catches both a broadened condition and active abuse, and unlike reviewing the pattern by eye it keeps working as the configuration changes.
saying these in an interview costs you the question
- Believes branch protection stops unreviewed branches from assuming the role.
- Treats the subject condition as connection setup, not authorization.
- Uses one shared deploy role federated by many repositories.
- Writes wildcard subject patterns without anchoring the value.
- Assumes removing the stored key removed the risk.