Exploiting CI/CD Pipelines: AWS Account Takeover via GitHub Actions OIDC
Continuous Integration and Continuous Deployment (CI/CD) pipelines have become the backbone of modern software delivery. The same automation that helps teams ship quickly also concentrates power: a single workflow can build artifacts, deploy to production, rotate secrets, and manage infrastructure.
For years, the most common way to connect GitHub workflows to AWS was to generate long-lived IAM user access keys and store them as GitHub Secrets. That approach is easy to implement, but it is structurally risky. If a repository is compromised, a maintainer account is hijacked, or sensitive logs leak environment variables, those static credentials can be reused until they are rotated, which can turn a single mistake into a large breach.
To reduce the blast radius of leaked keys, many teams adopted OpenID Connect (OIDC) for GitHub Actions. OIDC enables workflows to request short-lived credentials from AWS at runtime, without storing static AWS keys in GitHub at all.
This shift improves security when implemented correctly, but it also introduces a new class of failures. The strength of OIDC depends on strict IAM trust policy conditions that validate who is allowed to assume an AWS role. When those trust policies are misconfigured, attackers can sometimes assume production roles from their own repositories and pivot into AWS.
Understanding GitHub Actions OIDC Architecture for AWS
To understand the failure modes, it helps to understand the intended authentication path.

The OIDC authentication flow
- A workflow requests an identity token. A GitHub Actions job with the correct permissions asks GitHub’s OIDC provider for a JSON Web Token (JWT). The provider is
token.actions.githubusercontent.com. - GitHub issues a JWT with claims. The JWT includes claims that describe the workflow context, such as organization, repository, and the reference that triggered the run. The most important claim for access control is typically the subject claim (
sub). - The workflow calls AWS STS. The job sends the JWT to AWS Security Token Service (STS) using
AssumeRoleWithWebIdentity. - AWS evaluates the role trust policy. AWS compares the JWT’s claims against the IAM role’s trust policy conditions.
- AWS issues temporary credentials. If the policy conditions match, STS returns short-lived credentials to the GitHub runner.
A good mental model is that OIDC moves the secret from “stored in GitHub” to “encoded in policy logic.” If policy logic is too permissive, the system can still be compromised.
The Core Vulnerability: Misconfigured AWS IAM Trust Policies
The key security control in this model is the IAM trust policy on the target role. The trust policy must ensure the presented JWT was minted for a workflow you expect.
In practice, misconfigurations often happen during setup and testing. Teams may begin with a permissive trust policy “to get it working,” then forget to tighten it. This is where the most serious failures appear.
The Danger of Wildcard (repo:*) OIDC Configurations
A common anti-pattern is to validate the sub claim using a wildcard that effectively matches any repository. A simplified example looks like this:

This is catastrophic because repo:* does not restrict access to a specific GitHub organization or repository. It can allow any repository on GitHub.com, including attacker-controlled repositories, to produce a JWT that satisfies the role trust conditions.
In other words, a production role can unintentionally become a publicly assumable identity.
Step-by-Step Attack Workflow: Hijacking AWS OIDC Roles
From an attacker’s perspective, exploitation is straightforward once the role ARN is known and the trust policy is permissive.
Step 1: Reconnaissance (finding the target role ARN)
Role ARNs are frequently treated as non-sensitive identifiers. They often appear in places like:
- Public GitHub repositories
- CI/CD workflow files (for example,
.github/workflows/deploy.yml) - Terraform files and documentation snippets
- JavaScript bundles, configuration files, or build outputs
A leaked role reference might look like this:
arn:aws:iam::123456789012:role/ProdDeploymentRole
The attacker’s goal is not to steal a secret key, but to obtain the role ARN and rely on the trust policy being overly broad.
Step 2: Crafting the malicious workflow
The attacker creates a new public repository under their control and adds a GitHub Actions workflow that:
- Requests an OIDC identity token (
id-token: write) - Calls AWS STS to assume the victim role
- Runs benign verification and enumeration commands
A representative exploit workflow could be:
name: AWS-OIDC-Hijack
on: [push]
permissions:
id-token: write
contents: read
jobs:
exploit:
runs-on: ubuntu-latest
steps:
- name: Assume Victim's AWS Role
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/ProdDeploymentRole
aws-region: us-east-1
- name: Verify identity and enumerate access
run: |
echo "Checking identity..."
aws sts get-caller-identity
echo "Listing S3 buckets..."
aws s3 ls
Step 3: Execution and cloud pivot
With the workflow in place, the attacker triggers it by pushing a commit.
If the target AWS role’s trust policy matches a wildcard like repo:*, AWS will accept the attacker’s token and issue STS credentials. At that point, compromise scope depends on what the role can do:
- Read or write S3
- Deploy or modify infrastructure
- Read secrets from parameter stores
- Assume additional roles

Even limited permissions can be enough for escalation if the environment contains other misconfigurations.
Advanced CI/CD Attacks: Compromising Self-Hosted GitHub Runners
OIDC trust policies are not the only CI/CD risk. Many organizations also run self-hosted GitHub Actions runners on AWS EC2 to reach private networks, internal package registries, or VPC-only services.
Self-hosted runners can dramatically increase impact because untrusted build code runs on infrastructure that may have:
- Attached IAM instance profiles
- Network access to internal systems
- Cached artifacts, tokens, and configuration files
Executing Malicious Pull Request (PR) Attacks on Runners
A common pattern in open-source and some enterprise setups is to automatically run workflows on pull requests.
If a workflow runs untrusted PR code on a self-hosted runner, an attacker can submit a PR that modifies build steps (for example in Makefile, package.json scripts, or custom build tooling). When the runner executes the workflow, the malicious code executes inside the organization’s environment.
Stealing AWS Credentials via EC2 Instance Metadata Service (IMDS)
On EC2, the instance metadata service (IMDS) can expose credentials for the instance profile.

If IMDS is accessible and unprotected (or IMDSv1 is enabled), attackers can query it from the runner machine. A typical metadata extraction sequence looks like this:
# Extract the IAM role name
ROLE_NAME=$(curl -s <http://169.254.169.254/latest/meta-data/iam/security-credentials/>)
# Extract the temporary credentials for that role
curl -s <http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME>
If the workflow logs are accessible to the attacker (which is common in public repos), printing the output to logs can become a quiet credential exfiltration channel.
Self-hosted runners should be treated like production servers. Running untrusted PR code on them is closer to granting remote code execution than it is to “running a test build.”
Bug Bounty Best Practices: Crafting a Safe AWS Takeover PoC
If you are testing these issues under a bug bounty program or coordinated disclosure rules, demonstrating impact matters, but so does safety.
A good proof of concept should:
- Avoid accessing customer data. Do not download S3 objects, dump databases, or retrieve user records.
- Use identity and read-only signals. Prefer commands like
aws sts get-caller-identityand limited enumeration such asaws s3 lsoraws iam get-role(only if allowed). - Capture the minimum evidence required. Include the role ARN, the failing trust condition, and the exact GitHub location where the ARN or configuration was exposed.
The strongest reports usually combine:
- Clear reproduction steps
- Minimal but undeniable evidence of role assumption
- A concrete remediation recommendation (with a secure trust policy example)
Defense and Remediation: Securing GitHub Actions and AWS CI/CD
Defending against these attacks is achievable, but it requires treating IAM trust policies and runner boundaries as first-class security controls.
Enforce strict OIDC subject (sub) claims
Avoid wildcards and match the repository and reference explicitly.
A safer pattern is to use StringEquals and bind the role to:
- The expected audience (
aud), commonlysts.amazonaws.com - The exact repository
- The branch or tag reference you deploy from
A secure example:
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:YourOrg/YourProductionRepo:ref:refs/heads/main"
}
}
This ensures that only workflows running in the intended repository (and ideally only the deployment branch) can assume the role.
Enforce IMDSv2 on self-hosted runners
IMDSv2 requires a session token obtained via a PUT request before metadata can be accessed. This blocks many simplistic credential theft scripts that rely on unauthenticated curl calls.
While IMDSv2 is not a complete defense against a determined attacker with code execution, it is an important hardening step that prevents “easy wins” and reduces accidental exposure.
Isolate untrusted code
Separate trusted deployment workflows from untrusted contributions:
- Do not run workflows from forks on self-hosted runners automatically
- Require manual approval for external contributors
- Use GitHub environments and protection rules for deployment steps
- Consider separate AWS roles for CI (build/test) and CD (deploy)
Automating CI/CD Vulnerability Discovery with Jsmon
A recurring challenge in real-world assessments is finding the first breadcrumb that makes exploitation possible: role ARNs, internal repository references, or webhook endpoints.
While Jsmon is often used to discover hidden API routes and endpoints in JavaScript applications, it can also help identify CI/CD-related leakage.
Integrating Jsmon into Your Bug Bounty Reconnaissance Workflow
Jsmon can be used to continuously monitor public-facing JavaScript bundles for infrastructure references, including:
- Hardcoded AWS ARNs (for example, patterns matching
arn:aws:iam::) - Internal Git URLs and endpoints left behind during debugging
- CI/CD webhooks or deployment service URLs
This matters because client-side bundles sometimes carry configuration that was never meant to be public, especially during staging-to-production migrations.
Pivoting from Leaked AWS ARNs to IAM Role Assumption
Once Jsmon flags a potential role ARN, an attacker can plug it into a malicious GitHub Actions workflow and test whether the backend IAM trust policy is vulnerable to a wildcard sub condition.
Defenders can apply the same idea in reverse:
- Monitor bundles for ARN patterns before attackers do
- Treat leaked ARNs as a sign of potential misconfiguration
- Use detection to drive rapid trust policy validation
Conclusion: Navigating the Expanding DevOps Attack Surface
Modern CI/CD pipelines sit directly on the boundary between code and cloud. OIDC-based authentication is a major improvement over long-lived access keys, but it changes the core security question from “Where are the secrets stored?” to “Who can satisfy the trust policy?”
For security teams and DevOps engineers, the lesson is clear:
- OIDC is only as strong as the IAM conditions that constrain it.
- Self-hosted runners must be isolated from untrusted code.
- Automation and detection (including scanning for leaked ARNs) reduce time-to-fix.
As more organizations adopt identity-based, short-lived authentication in their pipelines, attackers will continue to look for the same gap: configuration that was designed for convenience, then accidentally promoted to production.
References and Further Reading
- Wiz Research: A security community success story of mitigating a misconfiguration
- Tinder Tech Blog: Identifying vulnerabilities in GitHub Actions & AWS OIDC Configurations
- Rezonate Blog: From GitHub To Account Takeover: Misconfigured Actions Place GCP & AWS Accounts At Risk
- Appsecco Blog: Exploiting IAM Security Misconfigurations (Part 1)
- Urshila Ravindran: Compromising the code inside CI/CD Pipeline Attacks
- AWS DevOps Blog: CloudFormation scanning CI/CD pipeline
- Silverfort Blog