# Cicd Pipeline Hardening

> Design or harden a CI/CD pipeline so it's fast, reproducible, and can't be turned into a supply-chain attack vector. Use when setting up CI for a repo, when builds are slow/flaky, or when a security review flags the pipeline. Covers stage ordering, caching, least-privilege tokens, pinned actions, artifact provenance, and safe deploy gates. GitHub Actions-first, portable.

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

---


# cicd-pipeline-hardening

A CI/CD pipeline runs your code *and* has credentials to your infrastructure — it's both a productivity tool and one of the highest-value attack surfaces you own. This skill builds one that is fast (cached, parallel, fails early), reproducible (pinned, hermetic), and hard to abuse (least-privilege, pinned third-party actions, provenance on artifacts).

## Use when
- Setting up CI for a new repo.
- Builds are slow, flaky, or wildly non-reproducible.
- A security review flagged the pipeline (over-privileged tokens, unpinned actions, secrets in logs).

## Stage ordering — fail cheap, fail first
Order stages by cost so the pipeline fails on the cheapest signal:
1. **Lint + format check** (seconds) — reject style/obvious errors before spending compute.
2. **Typecheck** (fast) — catch contract breaks.
3. **Unit tests** (fast, parallelizable).
4. **Build** — produce the artifact once; later stages reuse it, never rebuild.
5. **Integration/e2e tests** (slow) — only after the fast gates are green.
6. **Security scans** (SAST, dependency audit, secret scan) — can run in parallel with tests.
7. **Publish/deploy** — only on the default branch, only after everything above passes.

Run independent stages in parallel; make expensive stages depend on cheap ones passing. A PR that fails lint should never reach e2e.

## Reproducibility
- **Pin everything.** Lockfiles committed and `--frozen`/`ci` installs (`npm ci`, `pip install --require-hashes`, etc.). Pin the runner image and toolchain versions — "latest" is a time bomb.
- **Build once, promote the same artifact.** Build the image/bundle a single time, then promote *that exact artifact* through staging → prod. Never rebuild per environment — a rebuild can produce a different artifact than the one you tested.
- **Hermetic where you can.** Restrict network during build; vendor or cache dependencies so a flaky registry doesn't fail your build and an outage doesn't block a deploy.

## Caching (the biggest speed win)
- Cache the dependency store (`~/.npm`, `~/.cache/pip`, Go/Cargo caches) keyed on the lockfile hash — restores on cache hit, rebuilds only when deps change.
- Cache build outputs where the toolchain supports it (Turborepo/Nx remote cache, Docker layer cache with `cache-from`/`cache-to`).
- Key caches precisely: too-broad keys serve stale caches; too-narrow keys never hit. Include the lockfile hash + OS + tool version in the key.

## Supply-chain hardening (this is where pipelines get owned)
| Risk | Fix |
| --- | --- |
| Third-party action runs arbitrary code with your token | **Pin actions to a full commit SHA**, not a tag — tags are mutable and can be re-pointed at malicious code. |
| Over-privileged `GITHUB_TOKEN` | Set `permissions:` to least privilege (default `read-all`), grant `write` only to the specific job that needs it. |
| Long-lived cloud credentials in secrets | Use **OIDC federation** — short-lived tokens minted per run, no static cloud keys stored in CI. |
| Untrusted PR code reading secrets | `pull_request` from forks runs without secrets by default — keep it that way; never use `pull_request_target` to hand secrets to fork code. |
| Secrets printed to logs | Never `echo` a secret; rely on the platform's masking; audit steps that might interpolate a secret into output. |
| Tampered build artifacts | Generate provenance/SLSA attestation and sign artifacts (cosign); verify signatures at deploy. |
| Malicious dependency | Run a dependency audit + secret scan in CI; fail on known-critical vulns. |

## Deploy gates
- **Protect the default branch:** required status checks, required review, no direct pushes.
- **Environments with approvals** for production — a human (or a policy) gates the prod deploy; staging can be automatic.
- **Deploys are reversible:** every deploy has a rollback path (previous artifact promotable, or automated rollback on health-check failure). Never a one-way door.
- **Smoke-test after deploy** and auto-rollback on failure, rather than discovering the breakage from users.
- Concurrency control so two deploys don't race the same environment.

## Procedure
1. Order stages cheap→expensive; parallelize the independent ones; gate expensive on cheap.
2. Pin runner, toolchain, actions (by SHA); commit lockfiles; use frozen installs.
3. Add dependency caching keyed on lockfile hash; enable layer/remote build cache.
4. Set `permissions:` least-privilege per job; move cloud creds to OIDC; verify no secrets reach fork PRs or logs.
5. Build the artifact once; promote it through environments; sign + attest.
6. Add branch protection, prod approval gate, post-deploy smoke test, and a rollback path.
7. Verify: a red lint fails in seconds; a clean run is cached and fast; `permissions` and pins pass a security scan.

## Definition of done
- Fails on the cheapest failing signal; independent stages run in parallel.
- Fully pinned (runner, tools, actions-by-SHA, lockfiles); one artifact promoted, not rebuilt.
- Least-privilege tokens; OIDC for cloud; no secrets exposed to fork PRs or logs.
- Artifacts signed/attested; deploys gated, smoke-tested, and reversible.

