CI/CD Pipeline Security

By Davy Rogers

Your pipeline has more access than any human. Here's how not to hand it over.

CI/CD has access to source code, secrets, build infra, production. Compromise it, attackers own everything downstream.

Threat model

  • Compromised dependency: Malicious package runs during install, exfiltrates env vars
  • Malicious PR: Attacker modifies CI config, extracts secrets
  • Stolen CI credentials: Attacker triggers builds with modified code
  • Supply chain injection: Modified build script, poisoned cache

Runners

Ephemeral: Fresh environment per job, destroyed after. Prevents secrets leaking between jobs, persistent malware.

Self-hosted restrictions: Isolate in dedicated network, run jobs in containers, rotate credentials.

Minimal permissions:

permissions:
  id-token: write  # OIDC instead of long-lived keys
  contents: read

Secrets

Scoped: Per-environment, per-branch, per-job. Lint job doesn't need DB creds.

PR restrictions: Never expose secrets to forked PR builds. pull_request_target with PR checkout = attacker's code runs with your secrets.

Pipeline config

Protect CI config: Require review for .github/workflows/ changes.

Pin action versions:

# BAD - tag can be moved
- uses: actions/checkout@v4

# GOOD - immutable
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11

Build integrity

Reproducible builds: Same source = same artefact. Use lock files.

Provenance: Record source commit, builder identity, timestamp, artefact hash.

Deployment

Require approval for prod. Canary deployments. Ensure rollback capability.

The takeaway

Ephemeral runners. Scope secrets. Never expose to forked PRs. Pin versions to SHAs. Require prod approval. Your pipeline should be as hardened as production.

Want a professional to look at it?Get an AppSec Health Check.