{
 "slug": "federation-authority-analysis",
 "topic_id": "TOPIC-213",
 "cluster": "Machine, Agent & Signing Identity",
 "tier": "Tier B",
 "title": "Workload identity federation: removing the secret without removing the question",
 "summary": "Federation is a genuine improvement — no static secret to leak. The trust policy that decides which repository or branch may assume a powerful role is a configuration file, changed by whoever can merge to it.",
 "lede": "Keyless authentication was the right move and it moved the problem rather than removing it. The secret is gone; the authority is now expressed in code, and code is changed by pull request.",
 "date": "2024-06-10",
 "category": "Developer",
 "author_id": "constance-ibe-whitmore",
 "tags": [
  "workload identity federation",
  "OIDC",
  "CI/CD",
  "cloud security",
  "trust policy",
  "keyless"
 ],
 "image_title": "Federation Authority Analysis",
 "schema": "Article",
 "key_takeaways": [
  "Federation replaces a bearer secret with a claim set evaluated against a trust policy. The security property moves from secret custody to policy correctness.",
  "Four trust policy failure modes account for most misconfigurations, and wildcards on branch or workflow are the dominant one.",
  "Trust policy changes are privileged configuration changes and should carry human approval bound to the policy content."
 ],
 "body": [
  {
   "type": "h2",
   "text": "What federation actually does"
  },
  {
   "type": "diagram",
   "kind": "compare",
   "alt": "Before and after federation",
   "caption": [],
   "nodes": "The secret is gone. The authority decision moved into a policy document.",
   "left": {
    "title": "Static secret",
    "items": [
     "A key in a secret store",
     "Can leak",
     "Rotation required",
     "Theft is the risk"
    ]
   },
   "right": {
    "title": "Federation",
    "items": [
     "No secret to leak",
     "A trust policy instead",
     "No rotation",
     "Policy breadth is the risk"
    ]
   }
  },
  {
   "type": "p",
   "html": "A CI pipeline needs to deploy to a cloud account. Historically that meant a long-lived access key stored as a pipeline secret — a bearer credential, exfiltrable, frequently leaked."
  },
  {
   "type": "p",
   "html": "Workload identity federation replaces it. The CI platform issues a short-lived OIDC token asserting claims about the workload — which repository, which branch, which workflow, which environment. The cloud provider evaluates those claims against a trust policy and, if they match, issues short-lived credentials."
  },
  {
   "type": "p",
   "html": "No static secret exists. That is a real and substantial improvement and nothing here argues against adopting it."
  },
  {
   "type": "h2",
   "text": "Where the security property now lives"
  },
  {
   "type": "p",
   "html": "It has moved, and the move is worth naming precisely."
  },
  {
   "type": "table",
   "head": [
    "",
    "Static secret",
    "Federation"
   ],
   "rows": [
    [
     "What must be protected",
     "The secret value",
     "The trust policy"
    ],
    [
     "How it is compromised",
     "Exfiltration",
     "Misconfiguration or unauthorised change"
    ],
    [
     "Who can compromise it",
     "Anyone who reads the secret",
     "Anyone who can merge to the policy"
    ],
    [
     "Detection",
     "Secret scanning",
     "<strong style=\"font-weight:600\">Code review, if anyone looks</strong>"
    ],
    [
     "Blast radius",
     "The credential's permissions",
     "The role's permissions, for anyone matching the policy"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The last row is the one that surprises people. A permissive trust policy on a powerful role is worse than a leaked key, because it is a standing invitation rather than a single compromised credential."
  },
  {
   "type": "h2",
   "text": "Four failure modes"
  },
  {
   "type": "ol",
   "items": [
    "<strong style=\"font-weight:600\">Wildcard subject.</strong> A policy matching any branch or any workflow in a repository. A pull request from a fork, or a branch an attacker can create, then assumes the role.",
    "<strong style=\"font-weight:600\">Missing audience check.</strong> A policy that does not constrain the audience claim, permitting tokens issued for a different purpose to be replayed.",
    "<strong style=\"font-weight:600\">Over-broad repository match.</strong> A policy scoped to an organisation rather than a repository, so any repository in the org can assume a production role.",
    "<strong style=\"font-weight:600\">Unreviewed policy change.</strong> The policy lives in infrastructure code and is changed in a pull request that reviewers approve as routine."
   ]
  },
  {
   "type": "p",
   "html": "The first is the most common and the most exploited. The fourth is the one that makes the other three persistent, because it is how a tightened policy becomes loose again six months later."
  },
  {
   "type": "h2",
   "text": "Auditing your policies"
  },
  {
   "type": "p",
   "html": "A short exercise with a clear output."
  },
  {
   "type": "ol",
   "items": [
    "Export every trust policy attached to a role with write access to production.",
    "For each, extract the subject condition. Flag any containing a wildcard in the branch, workflow or environment position.",
    "Check the audience condition is present and constrained.",
    "For each flagged policy, determine who can create a matching branch or workflow. That set is who can assume the role.",
    "Report the size of that set per production role. It is usually much larger than the team that owns the role."
   ]
  },
  {
   "type": "h2",
   "text": "Approval bound to the policy content"
  },
  {
   "type": "p",
   "html": "Since the trust policy is the security boundary, a change to it should carry human approval bound to what changed — not a pull request approval on a diff nobody read carefully among forty other lines of Terraform."
  },
  {
   "type": "code",
   "text": "{\n  \"type\": \"manav-stmt/1\",\n  \"action\": \"trust_policy_change\",\n  \"render\": [\n    \"Role: [arn / resource name]  Permissions: [summary]\",\n    \"Current subject condition: [value]\",\n    \"Proposed subject condition: [value]\",\n    \"Wildcard present: [yes/no]  Audience constrained: [yes/no]\",\n    \"Principals able to match after change: [count, description]\"\n  ],\n  \"approver\": \"[credential assertion]\"\n}"
  },
  {
   "type": "p",
   "html": "The last render line is the one that changes decisions. An approver seeing <em>after this change, 340 repositories can assume this production role</em> approves differently from one seeing a Terraform diff."
  },
  {
   "type": "h2",
   "text": "What federation still leaves open"
  },
  {
   "type": "p",
   "html": "Even with a correct policy, a compromised pipeline running in an authorised repository on an authorised branch obtains legitimate credentials. Federation authenticates the workload, and a compromised workload is still the workload."
  },
  {
   "type": "p",
   "html": "That residual is addressed at the action layer — human approval bound to consequential deployments — rather than at the credential layer, and it is the reason federation should be treated as one control rather than the answer."
  },
  {
   "type": "h2",
   "text": "Where federation trust policies go wrong"
  },
  {
   "type": "table",
   "caption": "Four common over-broad patterns",
   "head": [
    "Pattern",
    "Effect"
   ],
   "rows": [
    [
     "Repository wildcard",
     "Any repository in the org can assume the role"
    ],
    [
     "No branch condition",
     "Any branch, including one an attacker can create"
    ],
    [
     "No environment condition",
     "A test workflow assumes a production role"
    ],
    [
     "<strong style=\"font-weight:600\">Pull request context permitted</strong>",
     "<strong style=\"font-weight:600\">A fork's PR can assume the role</strong>"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The last is the one that has produced real incidents. Federation makes the subject claim easy to get almost right, and almost right is the failure mode."
  },
  {
   "type": "h2",
   "text": "Objections and honest limits"
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“Federation is strictly better than secrets.”</strong> On the theft axis, clearly. On the breadth axis it can be worse, because a static secret has an obvious owner and a trust policy feels like configuration rather than a credential."
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“We reviewed the policy when we set it up.”</strong> Which is when repositories, branches and workflows were different. A trust policy is a credential and should be reviewed like one."
  }
 ],
 "faq": [
  {
   "q": "Should we not adopt federation?",
   "a": "Adopt it. It removes an entire class of leaked-credential incidents. The point is that it relocates the security property to the trust policy, which then needs the attention the secret used to get."
  },
  {
   "q": "Are wildcards ever acceptable?",
   "a": "In non-production roles with narrow permissions, sometimes. On a role with production write access, treat a wildcard as a finding."
  },
  {
   "q": "Does this apply to all cloud providers?",
   "a": "The mechanism differs in detail across providers but the structure — claims evaluated against a policy — and the failure modes are common to all of them."
  },
  {
   "q": "How do we stop policies loosening over time?",
   "a": "Bind approval to the policy content with the resulting principal count rendered, so loosening is a visible decision rather than a line in a diff."
  },
  {
   "q": "Is federation better than static secrets?",
   "a": "On theft, clearly. On breadth it can be worse, because a trust policy feels like configuration rather than a credential."
  },
  {
   "q": "What is the most dangerous pattern?",
   "a": "Permitting pull request context on a powerful role, which can let a fork's workflow assume it."
  },
  {
   "q": "How often should trust policies be reviewed?",
   "a": "On the same cadence as secrets. A trust policy is a credential, and repositories and workflows change."
  }
 ],
 "sources": [
  {
   "t": "GitHub Actions — OpenID Connect and cloud federation",
   "u": "https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect"
  },
  {
   "t": "OpenID Connect Core 1.0",
   "u": "https://openid.net/specs/openid-connect-core-1_0.html"
  },
  {
   "t": "CISA — known exploited vulnerabilities and incident reporting",
   "u": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog"
  },
  {
   "t": "FTC — business guidance on marketplaces and consumer protection",
   "u": "https://www.ftc.gov/business-guidance"
  },
  {
   "t": "OWASP — Non-Human Identities Top 10",
   "u": "https://owasp.org/www-project-non-human-identities-top-10/"
  }
 ],
 "related": [
  {
   "slug": "revocation-latency-framework",
   "title": "Ninety-one percent of leaked secrets still work",
   "category": "Developer"
  },
  {
   "slug": "custody-authority-separation",
   "title": "After the 2026 certificate authority compromise",
   "category": "Developer"
  },
  {
   "slug": "accountable-ownership-test",
   "title": "Eighty machine identities per human",
   "category": "Developer"
  }
 ],
 "image": "https://cdn.twc.sh/images/igcache/Federation%20Authority%20Analysis/1200_630/blog.jpg",
 "wordcount": 991,
 "url": "/blog/federation-authority-analysis.html",
 "reading_time": "4 min read",
 "seo_title": "Workload identity federation: the remaining question",
 "meta_description": "Federation removes the static secret, which is a real improvement. The trust policy deciding which branch may assume a powerful role remains.",
 "hub": {
  "slug": "topics/machine-identity",
  "title": "Machine and signing identity"
 },
 "answer": "The static secret, which is a real improvement. What it does not remove is the question: the trust policy deciding which repository, branch or environment may assume a powerful role is now the credential, and it is frequently written once and never reviewed.",
 "answer_q": "What does workload identity federation actually remove?",
 "glossary": [
  {
   "term": "Workload identity federation",
   "def": "Exchanging a build system's OIDC token for cloud credentials, removing the need for a stored secret."
  },
  {
   "term": "Subject claim",
   "def": "The identifier in the token describing the workflow — the string a trust policy matches against."
  },
  {
   "term": "Trust policy",
   "def": "The rule deciding which subjects may assume a role. In federation, this is the credential."
  }
 ],
 "checklist": {
  "title": "Auditing federation trust policies",
  "id": "federation",
  "desc": "Four steps.",
  "steps": [
   {
    "name": "List every role assumable by federation.",
    "text": "And what each can reach."
   },
   {
    "name": "Check for wildcards in the subject claim.",
    "text": "Repository, branch and environment."
   },
   {
    "name": "Exclude pull request context from powerful roles.",
    "text": "A fork should not reach production."
   },
   {
    "name": "Review on the same cadence as secrets.",
    "text": "It is a credential, not configuration."
   }
  ]
 },
 "cta": {
  "title": "Where this fits in Manav",
  "html": "Manav anchors machine and agent authority to a named human through a signed delegation with scope, depth and enforced revocation — so an action at hop four still resolves to a person.",
  "href": "../docs.html",
  "label": "See delegation chains"
 }
}