# Cicd

> 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.

- Skill: `purpleailab/cicd` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/cicd`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/cicd/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/cicd

---


# CI/CD Pipeline Exploitation — Category Overview

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-Skills

| 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. | `load_skill("/skills/standard/exploit/cicd/poisoned-pipeline-execution/SKILL.md")` |
| **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. | `load_skill("/skills/standard/exploit/cicd/github-actions-injection/SKILL.md")` |
| **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). | `load_skill("/skills/standard/exploit/cicd/self-hosted-runner-abuse/SKILL.md")` |
| **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. | `load_skill("/skills/standard/exploit/cicd/cicd-secrets-exfil/SKILL.md")` |

## Provider fingerprinting

```bash
# 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
```

## Workflow file recon (GitHub Actions example)

```bash
# 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
```

## Risky-surface triage

| 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:|write-all' .github/workflows/` |

## Secret-exposure surface

```bash
# 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
```

## Tooling

| 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 |

## Decision gate

Before any active test, confirm in writing:

1. The target program explicitly allows CI/CD testing (many programs scope this out — "infrastructure", "third-party CI" exclusions are common).
2. PoC will use a **beacon-only** payload (DNS / HTTP callback). Do not exfiltrate real secrets to attacker-controlled storage; print the first 4 chars of a token to prove access, then stop.
3. Any malicious branch / PR / fork is clearly labeled `security-research` and is deletable on request.
4. You will not push to protected branches, publish artifacts, or assume cloud roles. Prove the *capability*, do not exercise it.

## Cross-references

- Supply-chain catalog: `/skills/standard/exploit/supplychain/SKILL.md`
- OPSEC + scope discipline: `/skills/shared/opsec/SKILL.md`
- Cloud OIDC follow-on: cloud / IAM enumeration skills after a successful OIDC token exchange

