Security

No Key to Steal

Most breaches of a build pipeline end the same way, with a long-lived key in the wrong hands. OIDC federation replaces the key with a signed, short-lived proof of identity, and it now works between GitHub, AWS, Azure, Google Cloud and the AI services behind them.

A map before you read

One line per section, written by an AI model running entirely in your browser. Free, private, nothing leaves your device.

On January 4, 2023, CircleCI told its customers to rotate every secret they had stored with it. An attacker had put malware on an engineer’s laptop, taken over a session that was already signed in, and used it to “exfiltrate data from a subset of databases and stores, including customer environment variables, tokens, and keys” (CircleCI). The incident report recommended what you’d expect, that customers rotate their “OAuth tokens, Project API Tokens, SSH keys, and more.” It also recommended something you might not. “Use OIDC tokens wherever possible to avoid storing long-lived credentials in CircleCI.”

Notice what that advice is. It isn’t to store secrets more carefully. It’s to stop storing them.

The best way to connect one cloud to another is to give neither of them a key. A key can be stolen, leaked, guessed or brute-forced, and whichever happens, it works for whoever holds it until someone notices. A federated identity leaves nothing standing to attack.

A Key Is a Standing Secret

A long-lived access key, a service account’s JSON file, an API token pasted into a CI secret, all work the same way. Whoever holds one is you, from anywhere, for as long as it lives. A strong random key is practically impossible to guess, and attackers rarely try. They find it, in an environment variable, a log, a laptop or a compromised build step.

That’s the shape of nearly every pipeline breach. Starting January 31, 2021, someone altered Codecov’s Bash Uploader, a script that CI pipelines downloaded and ran on every build, so that it sent their environment variables “to a third-party server outside of Codecov’s infrastructure.” It went unnoticed until April 1, and Codecov’s advice to customers was to “immediately re-roll all of your credentials, tokens, or keys located in the environment variables in your CI process” (Codecov). In March 2025, a compromised version of the popular tj-actions/changed-files action spent two days “exposing CI/CD secrets in workflow logs” (GitHub Advisory, CVE-2025-30066). The same pattern carried the swarm attack I wrote about in The Swarm and the Sandbox, where every step ran on a credential found in an environment variable.

Each time, the fix is the same chore, rotation, and it only starts after someone notices. The breach didn’t create the vulnerability. The key was the vulnerability, sitting there waiting to be found.

What OIDC Changes

OpenID Connect was finalized in February 2014, and most people know it as the protocol behind “Sign in with Google.” Its core is an ID token, a signed JWT (I took one apart in The Anatomy of a JWT) carrying claims about who it’s for. Three of them do the work here. The iss names the issuer, the aud names the audience the token is meant for, and the exp is the time after which the token “MUST NOT be accepted” (OpenID Connect Core 1.0).

Federation uses that token in place of a key. A workload asks the platform it runs on to sign a statement of who it is. The cloud it wants to reach checks the signature against the issuer’s public keys, checks the claims against a trust policy, and hands back credentials that expire on their own. Nothing secret is stored on either side. GitHub’s documentation puts the benefit plainly: “You won’t need to duplicate your cloud credentials as long-lived GitHub secrets,” because the cloud provider “issues a short-lived access token that is only valid for a single job” (GitHub).

This site deploys that way. Every push to main runs a GitHub Actions job that builds the site and publishes it to S3 and CloudFront, and there’s no AWS key anywhere in the repository or its secrets. The workflow asks for an identity token and names a role:

permissions: { id-token: write, contents: read }
# …
- uses: aws-actions/configure-aws-credentials@v4
  with: { role-to-assume: '${{ secrets.AWS_DEPLOY_ROLE }}', aws-region: us-east-1 }

The role’s trust policy decides who may assume it, and it’s narrow on purpose. The token has to be issued by GitHub, meant for AWS, and come from one repository’s main branch:

Principal: { Federated: !Ref GitHubOidc }   # token.actions.githubusercontent.com
Action: sts:AssumeRoleWithWebIdentity
Condition:
  StringEquals: { 'token.actions.githubusercontent.com:aud': sts.amazonaws.com }
  StringLike: { 'token.actions.githubusercontent.com:sub': 'repo:quesurifn@21092055/kylecrfahey.com@1425026737:ref:refs/heads/main' }
A deploy job on main, with permission to request an identity token, asks GitHub's OIDC issuer for a token and receives a signed JWT for that job. It trades the token with AWS STS through AssumeRoleWithWebIdentity. The trust policy checks that the issuer is GitHub, the audience is sts.amazonaws.com and the subject is this repository's main branch, then returns temporary credentials, one hour by default. The job uses them to deploy to S3 and CloudFront with deploy-only permissions.
How this site deploys. GitHub vouches for the job, AWS checks what GitHub vouched for, and the credentials that come back expire on their own.

Notice the numbers in that subject. They’re the IDs of the account and the repository, not just their names. Names can be given up and claimed again, and GitHub’s April 2026 changelog explains why that matters: “If a repository or organization name was recycled, a new owner could mint tokens with the same subject claim” (GitHub). Repositories created after July 15, 2026 get the ID-based subject by default.

The difference that makes is in how long a stolen credential is worth anything.

1 min1 hour1 day30 days1 yearHow long a stolen credential keeps working (log scale)AWS outbound web identity token, default5 minutesAWS role session from OIDC, default1 hourBedrock short-term API key, maximum12 hoursStored key on a 90-day rotation (illustrative)up to 90 daysStored key that’s never rotateduntil someone notices
How long a stolen credential keeps working. Federated credentials expire on their own; a stored key works until someone rotates it. Defaults from AWS documentation; the 90-day rotation is illustrative.

Every Cloud Speaks It

This isn’t one vendor’s feature. GitHub Actions can deploy to Azure the same way, through a federated credential on a Microsoft Entra app, and Microsoft’s case against the alternative is blunt. Client secrets and certificates “pose a security risk and have to be stored securely and rotated regularly,” and federation “eliminates the risk of leaking secrets or having certificates expire” (Microsoft). Google says the same about its own keys. “Service account keys are powerful credentials, and can present a security risk if they are not managed correctly,” and Workload Identity Federation “eliminates the maintenance and security burden associated with service account keys” (Google Cloud).

It works between the clouds too, in every direction. AWS and Azure workloads can authenticate to Google Cloud “without a service account key,” with Google verifying AWS identities through AWS’s own GetCallerIdentity API (Google Cloud). AWS can now issue identity tokens for its own workloads to present elsewhere, valid for five minutes by default, and its documentation adds, “We recommend shorter token lifetimes for increased security” (AWS). Microsoft lists AWS, Google Cloud, Kubernetes and GitHub Actions among the sources Entra will trust. Platforms outside the big three have joined in, too. A Vercel project can assume an AWS role with a trust policy that names its team, project and environment (Vercel).

AI Keys Are Keys

The newest keys in most companies are the ones for AI, and they’re worth stealing in a way older keys weren’t. In May 2024, Sysdig’s threat research team described attackers who had taken cloud credentials from a vulnerable Laravel server and used them to run hosted language models at the victim’s expense, an attack they called LLMjacking. “If undiscovered,” they estimated, it “could result in over $46,000 of LLM consumption costs per day for the victim” (Sysdig).

The providers know it. Amazon Bedrock offers API keys for convenience, and its own documentation draws the line. A long-term key “creates an IAM user with attached policies” and is “Recommended only for exploration,” while a short-term key lasts at most 12 hours and is the one recommended for production (AWS). Microsoft is more direct about Azure OpenAI. “We recommend keyless authentication using Microsoft Entra ID” (Microsoft). Setting disableLocalAuth to true turns keys off entirely (Microsoft), though cached gateways can keep accepting old keys for “up to several hours” after.

So an app on Vercel can call Bedrock by assuming a role, and a GitHub Actions job that runs evaluations against Azure OpenAI can authenticate through a federated credential, with no model key stored anywhere. That matters more for AI than for anything else, because the code calling the model is increasingly an agent, and an agent holding a key holds everything the key can do.

Where Federation Goes Wrong

The fair counterargument is that OIDC doesn’t remove trust, it moves it into a policy, and a loose policy is worse than a key. A leaked key gives access to whoever finds it. A trust policy that forgets to check the subject gives access to everyone. In July 2023, Datadog Security Labs showed exactly that, AWS roles trusting GitHub’s issuer without restricting which repository the token came from, so that “a GitHub Action from any GitHub repository can assume the role and retrieve credentials for it” (Datadog Security Labs). AWS has since closed the easy version of the mistake. IAM now checks that a role trusting GitHub has a sub condition, and “that its value is not solely a wildcard character (* and ?) or null,” whenever the trust policy is created or updated (AWS). Roles created before the check aren’t covered.

A token can also be stolen from a running job, the same way a key can. The difference is what the thief gets. A token is scoped to one repository and branch, one audience and one role, and it expires in minutes or hours whether anyone notices or not.

That isn’t a rebuttal to federation. It’s the case for it. The thing left to protect is a policy anyone on the team can read, review and test, not a secret that has to stay hidden forever. Every compliance framework I’ve worked under, SOC 2 at Litehouse and CMMC at GARDE1, asks some version of the same two questions about secrets, who can read them and how often they’re rotated. With federation, the honest answer to both is that there’s nothing to read and nothing to rotate.

If I were connecting two systems today, I’d start by asking whether either one could issue an identity token, and nearly everything now can. I’d pin every trust policy to an audience and an exact subject, by ID where the issuer allows it. I’d turn off key authentication where the service lets me, Azure OpenAI’s disableLocalAuth and a deny on Bedrock’s long-term keys among them. And where a key truly can’t be avoided, I’d treat it as the exception it is, short-lived, vaulted and on a calendar.

The safest secret is the one you never stored. OIDC means never storing it.

Sources

  1. CircleCI, “CircleCI incident report for January 4, 2023 security incident,” January 12, 2023
  2. Codecov, “Bash Uploader Security Update,” April 15, 2021
  3. GitHub Advisory Database, “tj-actions/changed-files,” CVE-2025-30066, March 15, 2025
  4. OpenID Foundation, “OpenID Connect Core 1.0”
  5. GitHub Docs, “OpenID Connect”
  6. GitHub Changelog, “Immutable subject claims for GitHub Actions OIDC tokens,” April 23, 2026
  7. Microsoft Learn, “Workload identity federation”
  8. Google Cloud, “Workload Identity Federation”
  9. Google Cloud, “Configure Workload Identity Federation with AWS or Azure VMs”
  10. AWS, “Getting started with outbound identity federation”
  11. Vercel, “Connect to Amazon Web Services (AWS)”
  12. Sysdig, “LLMjacking: Stolen Cloud Credentials Used in New AI Attack,” May 6, 2024
  13. AWS, “Generate Amazon Bedrock API keys”
  14. Microsoft Learn, “Switch between OpenAI and Azure OpenAI endpoints”
  15. Microsoft Learn, “Disable local authentication in Azure AI Services”
  16. Datadog Security Labs, Christophe Tafani-Dereeper, “Exploring GitHub-to-AWS keyless authentication flaws,” July 27, 2023
  17. AWS, “Create a role for OpenID Connect federation”