Securing GitHub Actions: Your Pipeline Is an Attack Surface
Here's a thought experiment: list everything your CI/CD pipeline has access to. If you're like most teams, that list includes your source code (write access), your production deployment credentials, your npm/Docker registry tokens, your database connection strings (for migrations), and possibly your cloud provider's IAM credentials. Now ask yourself: how much scrutiny do you give to the third-party GitHub Actions running in that pipeline?
If the answer is "not much," you're in good company. And you're also running one of the most attractive attack surfaces in modern software.
Why This Matters: Real Supply Chain Attacks
This isn't theoretical. Supply chain attacks via CI/CD have caused some of the most damaging security incidents in recent years:
- SolarWinds (2020): Attackers compromised the build pipeline and injected malware into software updates that shipped to 18,000+ organizations, including US government agencies. The attack went undetected for months.
- Codecov (2021): Attackers modified Codecov's Bash Uploader script — a CI tool used by thousands of companies — to exfiltrate environment variables (including secrets) from CI environments. This affected companies like Twitch, Hashicorp, and others.
- npm package attacks: Packages like and
event-streamwere compromised to exfiltrate environment variables during install scripts that run in CI.ua-parser-js
The common thread: CI/CD pipelines are high-value targets because they have broad access and run code from external sources with minimal verification.
1. Pin Action Versions to Commit SHA
This is the single highest-impact change you can make, and almost nobody does it.
# ❌ DANGEROUS — tag can be moved to point to malicious code
uses: actions/checkout@v4
uses: actions/setup-node@v4
# ✅ SAFE — pin to exact commit SHA
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0When you reference
actions/checkout@v4v4v4Pinning to a commit SHA means you're referencing an immutable snapshot. Even if the repository is compromised, your pipeline runs the exact code you reviewed.
Gotcha: SHA pinning means you won't automatically get security updates to the action. You need to periodically update the SHAs — tools like Dependabot and Renovate can automate this by opening PRs when new versions are available. It's a trade-off between supply chain security and update convenience, and for CI/CD, I'll take security every time.
2. Use Minimal Permissions
GitHub Actions workflows run with a
GITHUB_TOKEN# At the workflow level — start with read-only
permissions: read-all
jobs:
build:
permissions:
contents: read # Only read code
packages: write # Only what this job needs
# NOT included: actions:write, secrets:write, admin:writeEach job should declare only the permissions it actually needs. A build job that runs tests doesn't need write access to packages. A deploy job doesn't need write access to issues. The
permissionsGotcha: If you set
permissions: read-all3. Scan Dependencies in the Pipeline
Your pipeline should catch known vulnerabilities before they reach production. This complements (but doesn't replace) the dependency management practices from our secret management guide.
- name: Audit dependencies for known vulnerabilities
run: npm audit --audit-level=high
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
security-checks: 'vuln,secret'
severity: 'CRITICAL,HIGH'npm auditThe "it depends" part: Should you fail the build on every vulnerability? In an ideal world, yes. In practice, many npm audit findings are in dev dependencies or have no practical exploit path. I typically set
--audit-level=high4. Never Echo or Log Secrets
GitHub Actions masks secrets in logs (replacing them with
***# ❌ LEAKS the secret to logs
- run: echo "Deploying with key $API_KEY"
# ❌ ALSO DANGEROUS — curl output might include the key in error messages
- run: curl -v -H "Authorization: Bearer $API_KEY" https://api.example.com
# ✅ Use secrets in commands without logging them
- run: curl -s -H "Authorization: Bearer $API_KEY" https://api.example.com
env:
API_KEY: ${{ secrets.API_KEY }}Gotcha that I've seen in the wild:
set -xset -o xtraceset -xecho5. Restrict Third-Party Actions
Not all GitHub Actions are created equal. Some are maintained by GitHub, some by verified organizations, and some by random developers. You should limit which actions can run in your organization.
# In repo Settings → Actions → General → Actions permissions:
# "Allow actions created by GitHub" + "Allow actions by Marketplace verified creators"
# OR maintain an explicit allowlistFor organizations, GitHub allows you to restrict actions at the organization level. Use this. It's far easier to maintain an allowlist of approved actions than to audit every workflow in every repository.
Before adding any third-party action to your allowlist, check:
- Is it maintained by a known organization or individual?
- Does it have a significant user base?
- When was it last updated?
- Does its code do what it claims? (Yes, actually read the action.yml and the scripts it runs.)
6. Protect Your Workflow Files
If an attacker can modify your workflow files, all the other protections are moot. Use branch protection rules on your main branch:
- Require PR reviews for changes to
.github/workflows/ - Require status checks to pass
- Don't allow bypassing these rules, even for admins
Consider using GitHub's CODEOWNERS file to require security team review for any changes to workflow files:
# .github/CODEOWNERS
.github/workflows/ @your-org/security-teamThe Uncomfortable Truth About CI/CD Security
Most teams treat their CI/CD pipeline as a devops concern, not a security concern. The pipeline has access to everything — code, secrets, production infrastructure — but it's often the least audited part of the stack. A 30-minute review of your GitHub Actions workflows — checking for unpinned actions, excessive permissions, leaked secrets in logs, and unrestricted third-party actions — is one of the highest-ROI security activities you can do.
Do the audit. Pin the actions. Restrict the permissions. It's not exciting work, but the alternative is explaining to your CEO how an npm package maintainer in another country got access to your production database credentials.
Discussion
0 comments
Share your thoughts
No comments yet. Be the first to share your thoughts!