CI/CD pipeline attack category — poisoned pipeline execution, GitHub Actions expression injection, self-hosted runner abuse, secrets/OIDC exfil. Routing skill: fingerprint the CI provider + workflow surface, then load the matching leaf.
73
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Routing skill for attacks against build / deployment pipelines. Pipelines run attacker-influenced code (PRs, dependencies, build scripts) with privileged tokens, cloud OIDC, and write access to artifacts that downstream consumers trust. Compromise of a single pipeline often equals compromise of every release built by it.
| Sub-Skill | Covers | When to Load |
|---|---|---|
| poisoned-pipeline-execution | Direct + Indirect PPE: injecting commands via attacker-controllable build files (Makefile, package.json scripts, build.gradle, Dangerfile, .pre-commit-config.yaml), pull_request_target abuse, fork-PR build triggers, dependency/test-script execution. | Target builds untrusted PR code; you can land a malicious file or dependency. |
| github-actions-injection | ${{ }} template injection via untrusted context (issue/PR title, body, branch name, commit message) into run: steps, pull_request_target + PR-head checkout, GITHUB_TOKEN permission abuse, artifact/cache poisoning, action pinning (tag vs SHA). | Target uses GitHub Actions and accepts external contributors. |
| self-hosted-runner-abuse | Non-ephemeral runner persistence, fork-PR job execution on self-hosted, runner-label targeting, secret theft from runner env, lateral movement into internal network / cloud metadata. | Target advertises self-hosted runners (workflow runs-on: label or public job logs). |
| cicd-secrets-exfil | Extracting CI secrets / OIDC tokens: echo / printenv exfil, masking bypass (base64, char-split), OIDC -> cloud role assumption, GITHUB_TOKEN / CI_JOB_TOKEN scope abuse, cache / artifact secret leakage. | You already have code execution in a CI job; need to convert it into durable cloud / registry access. |
# GitHub Actions
ls -la <REPO>/.github/workflows/ 2>/dev/null
gh api "repos/<OWNER>/<REPO>/actions/workflows" --jq '.workflows[] | {name,path,state}'
# GitLab CI
test -f <REPO>/.gitlab-ci.yml && echo "GitLab CI"
ls <REPO>/.gitlab/ci/ 2>/dev/null
# Jenkins
test -f <REPO>/Jenkinsfile && echo "Jenkins"; find <REPO> -name 'Jenkinsfile*' -type f
# CircleCI / Buildkite / Drone / Azure / Travis
for f in .circleci/config.yml .buildkite/pipeline.yml .drone.yml azure-pipelines.yml .travis.yml; do
test -f "<REPO>/$f" && echo "$f"
done
# Provider hints in README / badges
grep -RhoE 'github\.com/[^/]+/[^/]+/actions|gitlab\.com/[^/]+/[^/]+/-/pipelines|circleci\.com/gh/|app\.travis-ci\.com' <REPO> 2>/dev/null | sort -u# Local checkout
find <REPO>/.github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) -print
# Remote, no clone needed
gh api "repos/<OWNER>/<REPO>/contents/.github/workflows" --jq '.[].name'
# Pull every workflow at HEAD
for wf in $(gh api "repos/<OWNER>/<REPO>/contents/.github/workflows" --jq '.[].path'); do
echo "=== $wf ==="
gh api "repos/<OWNER>/<REPO>/contents/$wf" --jq '.content' | base64 -d
done| Signal | Why it matters | Grep |
|---|---|---|
pull_request_target: trigger | Runs with write GITHUB_TOKEN + secrets while checking out attacker code | grep -rE '^\s*pull_request_target\s*:' .github/workflows/ |
actions/checkout with ref: ${{ github.event.pull_request.head.sha }} under pull_request_target | Pulls attacker code with privileged context | grep -rE 'pull_request\.head' .github/workflows/ |
${{ github.event.* }} interpolated into a run: block | Expression injection sink (issue/PR body, title, branch name, commit msg) | grep -rE '\$\{\{\s*github\.event\.(issue|pull_request|comment|head_commit)' .github/workflows/ |
runs-on: [self-hosted, ...] on public repo | Self-hosted runner reachable from fork PRs | grep -rE 'runs-on:\s*(\[\s*self-hosted|self-hosted)' .github/workflows/ |
Third-party action pinned by tag (@v3) instead of SHA | Action repo / tag retargeting → silent supply-chain | grep -rE 'uses:\s*[^/]+/[^@]+@v?[0-9]' .github/workflows/ |
permissions: block missing or write-all | Token over-scoped; abuse to push to protected branches, publish releases | `grep -rE 'permissions: |
# Secrets and OIDC references — every line is a potential exfil target once you have RCE in a job
grep -rnE 'secrets\.|vars\.|env\.[A-Z_]+_TOKEN|env\.[A-Z_]+_KEY|id-token:\s*write|configure-aws-credentials|google-github-actions/auth' .github/workflows/
# Exposed in public job logs (replays)
gh run list --repo <OWNER>/<REPO> --limit 20 --json databaseId,name,status,conclusion
gh run view <RUN_ID> --repo <OWNER>/<REPO> --log | grep -iE 'token|secret|password|aws_|gcp_|azure_' | head| Tool | Use |
|---|---|
gh (GitHub CLI) | Recon, workflow listing, run/log inspection, PR creation |
gato / gato-x (praetorian-inc) | GitHub Actions attack toolkit — self-hosted runner enum, PPE, secret exfil chains |
actionlint | Local lint that flags many injection sinks — defenders use it; you can read its rules to find sinks |
octoscan / zizmor | Static scanners for GitHub Actions security issues |
tj-actions-changed-files CVE class (CVE-2025-30066) | Known compromised-action precedent; pattern to look for in any third-party action |
Burp Collaborator / interactsh | DNS / HTTP beacon for non-destructive PoC of code execution on a runner |
Before any active test, confirm in writing:
security-research and is deletable on request./skills/standard/exploit/supplychain/SKILL.md/skills/shared/opsec/SKILL.mde34afba
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.