PRACTITIONER GUIDE | INCIDENT RESPONSE
Practitioner GuideUpdated 14 min read

GitHub Actions OIDC Token Compromised: Detection, Scope Assessment, and Rotation Playbook

84
malicious @tanstack package versions published in 6 minutes using a stolen GitHub Actions OIDC token in May 2026
3,800
GitHub internal repositories accessed via stolen developer credentials from compromised Actions workflows
22 min
window during which developers installing @tanstack packages on May 11 had credentials stolen

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

A stolen GitHub Actions OIDC token is a time-limited but high-value credential. The token itself expires in minutes, but if the attacker used it to assume an IAM role or publish to a package registry, the downstream access they gained, AWS credentials, npm publish rights, container registry write access, may persist long after the token expired. The May 2026 TanStack attack demonstrated this exactly: a stolen Actions OIDC token was used to publish 84 malicious package versions in 6 minutes, and the resulting credential theft from developer machines persisted for days.

How OIDC Token Theft Happens in Practice

GitHub Actions OIDC works by having GitHub issue a short-lived JWT that your workflow can exchange with a cloud provider for cloud credentials. The attack pattern observed in the TanStack incident exploited the pull_request_target trigger: a workflow that runs in the context of the base repository (with access to secrets and OIDC token issuance) can be triggered by a pull request from a fork. If that workflow checks out and executes code from the fork, the fork's code runs with the base repository's permissions.

A second vector is direct memory scraping on the Actions runner. The payload that hit TanStack read /proc/{PID}/mem on the Linux runner to extract the OIDC token from the node runtime's memory while the workflow was executing. This requires code execution on the runner, achieved by injecting a malicious dependency that the workflow installs as part of its normal operation.

A third vector is GitHub Actions cache poisoning: a malicious actor poisons the Actions cache with a modified dependency that executes on cache restore, before the OIDC token exchange has occurred.

Step 1: Detect Whether Your Workflows Were Hit

Check your GitHub audit log immediately. Navigate to your organization's Settings > Audit log and filter for these events:

  • workflow_run.completed for any workflow using the id-token permission in the last 30 days
  • pull_request_target triggers from external contributors
  • package.published events, especially rapid sequential publishes (the TanStack attack published 84 versions in 6 minutes)

For the OIDC-specific check, pull your workflow files and look for jobs with:

permissions:
  id-token: write

Any workflow with this permission can request an OIDC token. Review every such workflow for the past 30 days of run history.

Also check your cloud provider. In AWS, look for AssumeRoleWithWebIdentity calls in CloudTrail with the principalId pattern matching your GitHub Actions OIDC provider (token.actions.githubusercontent.com). Filter for calls that occurred outside your normal deployment windows.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Step 2: Assess What the Token Could Have Accessed

An OIDC token grants exactly what your IAM trust policy allows. Retrieve the trust policy for every IAM role your workflows can assume:

aws iam get-role --role-name YourDeployRole --query 'Role.AssumeRolePolicyDocument'

For each role, check whether the trust policy uses condition keys to restrict which repositories and branches can assume it. A correctly scoped trust policy looks like:

"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:sub": "repo:yourorg/yourrepo:ref:refs/heads/main"
  }
}

A dangerously broad trust policy might only check the repository, not the ref, allowing any branch or pull request to assume the role. If your policies are not ref-scoped, assume every AWS action the role permits was potentially available to the attacker.

Document the full set of AWS permissions attached to each assumable role. This is the blast radius of a successful OIDC token theft.

Step 3: Rotate Credentials in the Right Order

Rotate in order of blast radius, not alphabetical order of service.

npm publish tokens first, if the attacker published packages, every downstream consumer is at risk. Revoke your npm automation token immediately (npmjs.com > Access Tokens), generate a new one, update it in GitHub Secrets.

AWS credentials second, revoke any long-lived IAM access keys that the compromised role could have generated. If the role had iam:CreateAccessKey permission, the attacker may have created persistent credentials. Check for unexpected access keys: aws iam list-access-keys for every IAM user, and check CloudTrail for CreateAccessKey events.

Container registry third, if workflows push to ECR, GHCR, or Docker Hub, rotate those credentials and audit recent image pushes for unexpected layers.

GitHub deploy keys and PATs last, audit every token with repo write access in your organization. Any token stored as a GitHub Secret in an affected repository should be considered potentially exposed.

Step 4: Harden OIDC Configuration to Prevent Recurrence

Three controls close the most common attack paths:

Pin all third-party Actions to commit SHA, not a tag. Tags are mutable, a compromised action maintainer can point a tag at malicious code. Pinning to a full commit SHA ensures you run exactly the code you audited:

uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

Add StepSecurity Harden-Runner to every job. This GitHub Action monitors runner activity in real time, blocks outbound network requests to unexpected destinations, and detects file system writes to sensitive paths. It would have caught the /proc/mem scraping in the TanStack attack.

Restrict OIDC token claims at the provider. In AWS, update your IAM trust policy conditions to require both repo and ref match. In addition, use the sub claim format that includes environment when possible (repo:org/repo:environment:production) to prevent development branches from assuming production roles.

Detecting Future Compromises in GitHub Audit Logs

Set up automated monitoring for these GitHub audit log events using the GitHub Audit Log Streaming feature (available on Enterprise) or the audit log API:

  • Unusual package publish frequency: more than 3 publishes in 60 seconds from a workflow
  • pull_request_target workflow runs triggered by first-time contributors
  • Workflow runs that request id-token permissions from non-default branches
  • Secret access events (secret.access) outside normal deployment windows

Pair this with CloudTrail alerts in AWS for AssumeRoleWithWebIdentity calls from your OIDC provider at unusual times or from workflows you did not expect.

The bottom line

The OIDC token itself expires in minutes, but what an attacker does with it can persist for months. The response priority is not revoking the token, it is already expired, but auditing every downstream action it enabled: package publishes, IAM role assumptions, and credential creation. Harden trust policy conditions and pin action versions before your next deployment.

Frequently asked questions

How long is a GitHub Actions OIDC token valid?

GitHub Actions OIDC tokens have a maximum validity of 10 minutes. However, the AWS credentials or other cloud credentials obtained by exchanging the token typically have their own validity period, often 1 hour for STS AssumeRole credentials, or longer if the session duration is configured generously.

What is pull_request_target and why is it dangerous?

pull_request_target is a GitHub Actions trigger that runs in the context of the base repository rather than the fork, giving it access to base repository secrets and the ability to request OIDC tokens. If a workflow using pull_request_target checks out and executes code from the pull request (the fork), it runs untrusted code with privileged access. This is the Pwn Request attack pattern.

Can I see what an OIDC token was used to access after the fact?

Yes, through your cloud provider's audit logs. In AWS, CloudTrail records every AssumeRoleWithWebIdentity call including the source IP, the role assumed, and every subsequent API call made using the resulting credentials. Search CloudTrail for your GitHub OIDC provider ARN as the principalId to find all relevant activity.

Should I disable OIDC in GitHub Actions after a compromise?

Not necessarily, OIDC is more secure than storing long-lived credentials as secrets, because OIDC tokens are short-lived. The issue is misconfigured trust policies, not OIDC itself. After a compromise, tighten your trust policies to restrict which repositories and refs can assume which roles.

How do I audit which workflows use the id-token permission?

Run: find .github/workflows -name '*.yml' | xargs grep -l 'id-token: write' to find all workflow files requesting OIDC capability in your repository. For an organization-wide audit, use the GitHub REST API to list all workflow files across repositories and grep for the permission.

How do you restrict AWS OIDC trust policies so a compromised repository cannot escalate to roles used by other repositories or branches?

Scope every IAM role's trust policy to the exact sub claim format GitHub OIDC uses: repo:ORG/REPO:ref:refs/heads/BRANCH for branch-specific access and repo:ORG/REPO:environment:ENVIRONMENT for environment-gated deployments. Never use wildcards in the sub claim condition. Create separate IAM roles for each deployment context: a role for pull request validation (read-only permissions), a role for staging deployment (limited write scope), and a role for production deployment (restricted to the specific S3 buckets, ECR repositories, or ECS services that role requires). If a production repository is compromised, the attacker's OIDC token is only valid for that repository's specific role -- not for roles used by other repositories. Review IAM roles with github as the OIDC provider quarterly and confirm every trust policy has an explicit sub condition with no wildcards in the repository or ref portion.

Sources & references

  1. GitHub Actions OIDC documentation
  2. StepSecurity Harden-Runner
  3. TanStack supply chain attack analysis, Decryption Digest

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.