CI/CD pipeline security review
When it applies
You can read a repo's CI config (GitHub Actions, GitLab CI, Jenkinsfile, CircleCI). Pipelines run with secrets and often on attacker-influenced input (PRs), making them a high-value, under-reviewed target.
Why it works
CI runs code with privileged tokens/secrets. Misconfigurations let a fork PR run in a trusted context, inject commands via untrusted inputs, or exfiltrate secrets — a supply-chain foothold. On public repos this can be exploitable by anyone who opens a PR.
Method
- Dangerous triggers:
pull_request_target/workflow_runthat check out and run PR code with secrets in scope → fork PRs can steal secrets or run arbitrary code (the classic GitHub Actions bug). - Script injection: untrusted data (
github.event.issue.title, PR body, branch name) used directly inrun:shells → command injection. Look for${{ github.event.* }}inrunblocks. - Secret handling: secrets echoed/logged, passed to third-party actions, or available to fork PRs;
overly broad
permissions:(defaultwrite), long-livedGITHUB_TOKEN. - Untrusted dependencies: third-party actions pinned to a mutable tag/branch (not a SHA), curl-pipe-to-shell steps, unpinned package installs → supply-chain.
- Self-hosted runners on public repos: fork PRs executing on your infra → RCE on the runner.
Gotchas
pull_request(safe-ish) vspull_request_target(dangerous) — the trigger name is the crux.- An action pinned to
@v3(tag) can be moved; require a full commit SHA for third-party actions. - Secret leakage often happens via a step passing
${{ secrets.X }}to an untrusted action.
Verify success
A concrete path where an outsider (fork PR / issue) can execute code with pipeline privileges or exfiltrate a secret, or a clear secret-leak/injection in the workflow.
References
GitHub Actions security hardening docs; "GitHub Actions pwn requests" (Nathan Davison); semgrep CI rules.