GitHub Actions is now part of the software supply chain. It builds packages, signs releases, deploys infrastructure, and often holds the credentials attackers want most.

That is why recent supply chain attacks against tj-actions/changed-files, trivy-action, Nx, npm packages, PyPI packages, and other CI/CD paths matter. GitHub’s own 2026 Actions security roadmap describes the pattern clearly: attackers abuse untrusted code execution, mutable dependencies, over-permissioned tokens, secrets, and weak runner observability.

This GitHub Actions security checklist gives engineering and AppSec teams a practical audit path. Start with the first five controls, then work through the full checklist by risk area.

GitHub Actions security checklist

The five controls to do first

If you only have time for one pass, prioritize these:

  1. Set default GITHUB_TOKEN permissions to read-only.
  2. Pin third-party actions to full commit SHAs, not tags or branches.
  3. Avoid pull_request_target for public repositories and fork PRs.
  4. Treat all PR titles, branch names, issue bodies, labels, and commit messages as untrusted input.
  5. Use OIDC for cloud access instead of long-lived static secrets.

These five controls reduce the most common GitHub Actions security failures: poisoned pipeline execution, secret theft, mutable action compromise, and excessive token access.

1. Lock down organization and repository defaults

  • Set workflow token permissions to read-only by default. GitHub recommends least privilege for GITHUB_TOKEN, then elevating permissions only where a job needs them in the workflow file.
  • Disable “Allow GitHub Actions to create and approve pull requests.” A compromised workflow should not be able to approve its own changes.
  • Restrict allowed actions and reusable workflows. Prefer GitHub-owned actions, verified creators, and an explicit allowlist for third-party actions that your team has reviewed.
  • Require security review for .github/workflows/. Add CODEOWNERS coverage for workflow files and pair it with branch protection or repository rulesets.
  • Use repository rulesets for sensitive branches. Require PR review, block force pushes, require status checks, and dismiss stale approvals when new commits are pushed.

The goal is to make insecure workflow changes visible before they can reach a branch that runs privileged automation.

2. Make workflow permissions explicit

Use a deny-by-default or read-only pattern:

permissions: {}

jobs:
  build:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<full-commit-sha>
  • Declare permissions: at the workflow or job level. Avoid relying on repository defaults.
  • Grant write scopes only to the job that needs them. A release job may need contents: write; a test job usually does not.
  • Use id-token: write only for jobs that use OIDC. Do not grant it globally.
  • Separate read-only validation from privileged publishing. Build and test untrusted code in one workflow, then pass only safe metadata to a privileged workflow if needed.

This maps directly to the principle behind GitHub Security Lab’s guidance on preventing pwn requests: untrusted code and privileged credentials should not share the same execution context.

3. Avoid dangerous triggers and untrusted execution paths

The most dangerous GitHub Actions mistakes happen when untrusted input reaches a privileged workflow.

  • Avoid pull_request_target in public repositories. If you must use it, never check out or execute code from the PR head before a trusted maintainer has reviewed it.
  • Guard workflow_run workflows. Check the upstream event before taking privileged actions, especially if the upstream workflow can be triggered by a fork PR.
  • Audit issue_comment, pull_request_review, and pull_request_review_comment triggers. These events can be influenced by external users and may run with elevated context.
  • Use pull_request for untrusted fork code. It withholds secrets and write permissions for external forks by default.
  • Close or rebase stale PRs after fixing a vulnerable workflow. Older PRs may still reference the vulnerable workflow version that existed when they were opened.

OpenSSF’s GitHub workflow attack vector guidance describes this as privileged workflows running untrusted code. OWASP categorizes the same class of risk as Poisoned Pipeline Execution.

4. Prevent script injection in workflow steps

Any value controlled by a contributor or issue author is untrusted. That includes:

  • Branch names
  • PR titles and bodies
  • Issue titles and bodies
  • Labels and comments
  • Commit messages
  • Artifact contents from untrusted workflows

Do not interpolate these values directly into run: blocks:

# Unsafe
- run: echo "Branch is ${{ github.head_ref }}"

# Safer
- run: echo "Branch is $BRANCH"
  env:
    BRANCH: ${{ github.head_ref }}
  • Pass untrusted context through environment variables. Quote shell variables when using them.
  • Prefer purpose-built actions over inline shell when parsing untrusted input. Action inputs are treated as data instead of shell syntax.
  • Never write untrusted data to GITHUB_ENV or GITHUB_PATH. These files can affect later steps in the job.
  • Validate artifact contents before using them in privileged workflows. Treat artifacts from PR workflows as attacker-controlled data.

GitHub’s secure use reference recommends environment variables for inline scripts and full review of how secrets and context values are handled.

5. Pin and review every third-party action

Mutable references are convenient, but they are also a supply chain risk.

  • Pin third-party actions to a full commit SHA. A tag like @v4 can be moved; a full SHA gives you an immutable reference.
  • Verify that the SHA belongs to the original action repository. Watch for imposter commits from forks.
  • Prefer GitHub-owned or verified creator actions. Use unverified actions only after source review.
  • Minimize transitive action dependencies. A pinned top-level action can still call a nested action by mutable tag.
  • Use Dependabot, Renovate, or pinning tools to update SHAs through reviewed PRs. Add a minimum release age for updates where your tooling supports it.
  • Enable dependency graph and Dependabot alerts for actions. GitHub can surface advisories for actions used in workflows.

GitHub is working on workflow-level dependency locking, but until lockfiles are generally available, SHA pinning and reviewed updates are the strongest practical controls.

6. Reduce secret exposure

Secrets are the prize in most CI/CD compromises. Treat every secret in a workflow as if it can become reachable by any step in the same job.

  • Scope secrets to environments when possible. Use environment protection rules and required reviewers for production secrets.
  • Pass secrets only to the step that needs them. Avoid job-level env: blocks for sensitive values.
  • Never pass secrets as command-line arguments. Use environment variables so values do not appear in process listings.
  • Do not echo secrets, encoded secrets, or transformed secrets. GitHub log masking is not guaranteed for every transformation.
  • Avoid secrets: inherit in reusable workflows. Pass only the named secrets the callee requires.
  • Rotate and delete stale secrets. Remove credentials that no workflow needs.

For application code checks inside GitHub Actions, pair secret hygiene with Corgea AI SAST and the Corgea GitHub Action so vulnerabilities found in PRs produce actionable fixes before merge.

7. Prefer OIDC over long-lived cloud credentials

OIDC lets GitHub Actions exchange a short-lived identity token for cloud credentials at runtime. That removes static AWS, Azure, or GCP keys from GitHub secrets.

  • Configure cloud trust policies with repository, branch, environment, and workflow constraints.
  • Grant id-token: write only to the job that exchanges the token.
  • Limit cloud roles to the exact deployment or publishing operation.
  • Use environment approvals for production OIDC jobs.

Short-lived, scoped credentials do not prevent every compromise, but they sharply reduce the value of a stolen secret.

8. Harden runners and network access

GitHub-hosted runners are ephemeral by default. Self-hosted runners are not.

  • Do not use self-hosted runners for public repositories. Fork PRs can execute attacker-controlled code.
  • Use ephemeral or just-in-time runners for sensitive private workflows. Destroy the runner after one job.
  • Separate runner groups by trust level. Do not mix public, private, release, and production workloads on the same runner pool.
  • Restrict runner network egress. Allow only package registries, artifact stores, cloud APIs, and internal services required by the job.
  • Monitor runner registrations and repository creation events. Unexpected self-hosted runners and public repos are common persistence and exfiltration signals.

GitHub’s roadmap includes native runner egress controls and Actions Data Stream telemetry. Until those controls are available in your environment, treat runners as sensitive infrastructure and monitor them like production systems.

9. Secure artifacts, caches, and release workflows

Artifacts and caches often bridge jobs, workflows, and trust boundaries.

  • Avoid broad artifact uploads such as path: .. Upload explicit files or directories and exclude .env, keys, credentials, and generated configs.
  • Set persist-credentials: false on actions/checkout unless later steps need the checkout token.
  • Extract untrusted artifacts outside the workspace. Use a temporary directory and validate expected filenames and formats.
  • Do not cache secrets or release credentials. Assume cache contents may be readable by workflows you did not expect.
  • Avoid caches in privileged release workflows. Cache poisoning can cross workflow boundaries.
  • Use provenance and attestations for packages where supported. Scope trusted publishing to a specific workflow file and protected branch.

Release workflows deserve the strictest review because a compromised release job can push malicious packages to every downstream user.

10. Add continuous detection for workflow risks

Configuration hardening is not a one-time project. Every new workflow, action, and secret can reopen the attack surface.

  • Run workflow linting for risky triggers, unpinned actions, and unsafe interpolation. Tools such as zizmor and OpenSSF Scorecard can catch common GitHub Actions security issues.
  • Enable code scanning for workflow vulnerabilities. GitHub supports CodeQL coverage for common workflow patterns.
  • Review Actions dependency changes in PRs. Treat action updates like dependency updates in application code.
  • Alert on failed attempts to use blocked actions or unpinned references. These events can reveal policy gaps.
  • Document an incident playbook. Include steps to disable workflows, revoke tokens, rotate secrets, block actions, and inspect runner activity.

If you already use CI for SAST, dependency scanning, and remediation, extend that same discipline to workflow YAML. Corgea’s guide to integrating static analysis into CI/CD is a good starting point for making security checks visible in pull requests.

A practical rollout plan

Use this order if you are hardening many repositories:

  1. Inventory workflows and secrets. Identify workflows with write permissions, secrets, release jobs, self-hosted runners, and public fork triggers.
  2. Apply organization defaults. Set read-only token permissions, action restrictions, CODEOWNERS, and PR approval protections centrally.
  3. Fix high-risk workflow patterns. Prioritize pull_request_target, workflow_run, untrusted interpolation, GITHUB_ENV, and broad secret exposure.
  4. Pin actions and add update automation. Convert mutable action references to full SHAs and manage updates through PRs.
  5. Move cloud credentials to OIDC. Start with production deployment and package publishing workflows.
  6. Add continuous monitoring. Use workflow linting, dependency alerts, audit logs, and runner registration alerts.

Bottom line

GitHub Actions security is supply chain security. A workflow can publish packages, mint cloud credentials, modify code, and deploy production. That makes workflow YAML part of your trusted computing base.

The path to a safer setup is straightforward: keep untrusted code away from privileged credentials, pin what you run, minimize token permissions, replace static secrets with OIDC, harden runners, and continuously scan workflow changes.

Use this checklist during quarterly repository reviews, after supply chain incidents, and before adding any workflow that can release software or access production credentials.