---
name: pipeline-security
description: >-
  Security review, hardening, and vulnerability scanning for Buildkite pipelines
  and self-hosted agents (CI/CD). Use when auditing a .buildkite/pipeline.yml or
  agent setup, hardening CI/CD, or answering "is this pipeline/agent secure?".
  Covers secrets in YAML, OIDC vs static creds, plugin pinning & RCE, untrusted
  fork/PR builds, agent hooks as security controls (pre-bootstrap job rejection,
  allowed-plugins, no-command-eval, no-local-hooks, signed pipelines), Docker
  socket/privileged steps, queue/cluster isolation, and injection via build
  metadata. Runs an executable scanner plus gitleaks when available.
user-invocable: true
argument-hint: [path]
allowed-tools: Read, Grep, Glob, Bash
---

# Buildkite pipeline & CI/CD security review

You are a security engineer auditing a Buildkite **pipeline and its agents**. The
guiding rule: secrets never live in YAML or logs, code from untrusted sources
never reaches secrets or privileged infrastructure, and the agent enforces what
it will run.

> Verify specifics against the docs:
> - Securing the agent: https://buildkite.com/docs/agent/v3/securing
> - Enforcing security controls: https://buildkite.com/docs/pipelines/best-practices/security-controls
> - Signed pipelines: https://buildkite.com/docs/agent/v3/signed-pipelines
> - Agent hooks: https://buildkite.com/docs/agent/hooks
> - OIDC: https://buildkite.com/docs/pipelines/security/oidc

## Threat model
- **Pipeline YAML and `pipeline upload` can come from untrusted contributors**
  (fork/PR builds). Assume an attacker can propose steps, commands, env, and
  plugin refs.
- **Agents are shared, privileged infrastructure** — they hold (or can mint)
  secrets and can reach your cloud/registries and other jobs' state.
- **Plugins and the pipeline are a supply chain** — a moved tag or smuggled step
  is RCE on your agents.

## How to run the scan
```bash
bash "${CLAUDE_SKILL_DIR}/scripts/scan-pipeline.sh" <repo-or-pipeline-path>
```
Static checks (unpinned/mutable plugins, inline secrets, Docker socket,
`privileged`, `propagate-environment`, build-metadata injection, `curl|bash`)
plus **gitleaks** when installed. Exits non-zero on any HIGH (`--no-fail` to
report-only). Wire it into CI. Full catalog: `reference/vulnerability-catalog.md`.

## 1. Secrets
- **Never** in `pipeline.yml`, step `env:`, or `command` strings; never `echo`ed.
- Prefer **OIDC** (short-lived, keyless) over static credentials; bind the cloud
  trust policy to specific claims (org / pipeline slug / branch).
- Otherwise fetch at runtime from a managed store (AWS/GCP Secrets Manager,
  Vault) or the Buildkite secrets feature, in an `environment`/`pre-command`
  hook, scoped to where needed. Rely on agent **redaction** as a backstop, not a
  primary control.

## 2. Supply chain — pin everything
- **Pin plugins** to a tag or commit SHA (`org/plugin#v1.2.3`). An unpinned or
  `#main` ref is remote code execution on your agents when the tag moves.
- Pin container images by digest for deploy/security-critical steps.
- **Signed pipelines**: sign steps on upload so agents verify they weren't
  tampered with or smuggled between pipelines; a mismatch makes the agent refuse
  the job.

## 3. Agent hooks as security controls (enforced on the agent, not the repo)
This is the strongest lever and can't be bypassed by pipeline authors:
- **`pre-bootstrap` hook** — runs **before checkout and before env vars load**,
  and can **accept or reject the job**. Use it to enforce allow-lists: only
  approved repositories, commands, and plugins (inspect `BUILDKITE_REPO`,
  `BUILDKITE_COMMAND`, `BUILDKITE_PLUGINS`); exit non-zero to reject.
- **Allowed-plugins** — an agent `environment` hook that vets `BUILDKITE_PLUGINS`
  against an allow-list (see Buildkite's `buildkite-allowed-plugins-hook-example`).
- **`no-command-eval`** — disallow arbitrary commands; only run checked-in
  scripts. (No effect if you ship a custom command hook.)
- **`no-plugins`** — disable plugins entirely on sensitive agents.
- **`no-local-hooks`** — disable repo-local hooks so your agent-level policy
  can't be evaded by `.buildkite/hooks/*` from the repo.
- Understand **hook trust levels**: *agent hooks* are trusted (host-controlled);
  *repo hooks* (`.buildkite/hooks/*`) and *plugin hooks* execute code influenced
  by the repo/PR — untrusted on fork builds.

**Runnable, tested examples** are in `templates/agent-hooks/` (a `pre-bootstrap`
repo/command/plugin allow-list gate and an `environment` allow-list gate, with
bats tests). Full guidance + a self-check in `reference/agent-hooks-security.md`.

## 4. Untrusted (fork / public PR) builds
- Don't expose secrets/OIDC to fork PR builds; gate secret-using steps with `if:`
  excluding third-party PRs, or run them on a separate cluster/queue with no
  privileged creds.
- `pipeline upload` from untrusted code can inject steps — run it on a restricted
  queue; prefer signed pipelines.
- Require a `block` approval before any privileged step on untrusted input.

## 5. Isolation & privilege
- Use **clusters/queues** to separate trust levels (hardened `deploy` queue vs
  `default`); restrict which pipelines may target privileged queues.
- Avoid mounting the **Docker socket** and `--privileged`; both are agent-level
  root. If unavoidable, isolate on an untrusted-work queue and document it.
- Apply `concurrency_group` to deploys; ephemeral agents; clean the checkout
  between jobs.

## Report format
For each finding: **severity, location, why it matters, concrete fix**; then an
overall verdict driven by the highest unresolved severity. Triage scanner output
in context rather than dumping it.

## Related
- `buildkite-plugin-security` — the plugin-code side of the same review.
- `pipeline-architecture` — building the pipelines you're auditing.
