CI/CD Pipeline Security Template
A template for securing build and deployment pipelines against credential leaks, tampering, supply chain attacks, and unauthorized deployments.
Overview
CI/CD pipelines are a juicy target: they’ve got access to your source code, your build secrets, and the road into production. A compromised pipeline can slip malware into your builds, exfiltrate data, or deploy changes nobody approved. This template defines controls to protect code integrity, runner security, secrets, and deployment approvals. For the bigger picture, see the CI/CD Security guide; for the secrets half of the problem, Docker secrets management covers the container angle.
When to Use
- When you’re standing up a new CI/CD platform.
- Reviewing or improving an existing pipeline.
- When a supply chain security audit is coming up.
- After a build system compromise or unauthorized deployment.
- Integrating DevSecOps controls into engineering workflows.
Prerequisites
- A version control system with branch protection and audit logging.
- A CI/CD platform — GitHub Actions, GitLab CI, Azure DevOps, Jenkins, whatever you run.
- A secret management solution for pipeline credentials.
- A code review and approval step before changes merge.
- Ownership from platform engineering, security, and release management.
Solution
Template
1. Source Control Security
| Control | Requirement | Verification |
|---|---|---|
| Branch protection | Required reviews before merge to main | Repository settings |
| Signed commits | Require verified commits for privileged accounts | Git configuration |
| Access control | Least-privilege access to repositories | RBAC review |
| Audit logging | All pushes, merges, and permission changes logged | Platform logs |
| Dependency pinning | Lockfiles and pinned versions for reproducible builds | Repository files |
| Secret scanning | Automated detection of secrets in commits | Pre-commit hooks + CI |
2. Pipeline Configuration
| Control | Requirement | Verification |
|---|---|---|
| Immutable pipeline definitions | Pipelines stored as code and reviewed | Repository files |
| No secrets in code | Secrets loaded from vault, CI variables, or OIDC | Secret scanning |
| Input validation | Pipeline parameters validated and sanitized | Code review |
| Self-hosted runner isolation | Production runners isolated from dev runners | Runner configuration |
| Ephemeral runners | Fresh runner per build to reduce persistence | Runner settings |
| Pipeline provenance | SLSA provenance generated for artifacts | Attestation tool |
3. Secrets Management
| Secret Type | Storage | Rotation | Scope |
|---|---|---|---|
| Cloud credentials | External vault or OIDC | 90 days | Per environment |
| Container registry tokens | Vault or short-lived CI tokens | 90 days | Per pipeline |
| Signing keys | Hardware-backed or KMS | 180 days | Limited service accounts |
| API keys | Vault or secret manager | 90 days | Minimum required permissions |
| Database passwords | Vault dynamic secrets | 24 hours | Per pipeline run |
4. Build Security
| Control | Requirement | Verification |
|---|---|---|
| Dependency scanning | All dependencies scanned for known CVEs | Scanner in CI |
| Static analysis | SAST run on every pull request | CI job |
| Container image scanning | Base image and layers scanned before push | Registry scan |
| Reproducible builds | Same source produces same artifact | Build verification |
| Artifact signing | All artifacts signed with build identity | Signature verification |
| SBOM generation | Bill of materials generated per build | CI output |
5. Deployment Security
| Control | Requirement | Verification |
|---|---|---|
| Deployment gates | Manual or automated approval before production | Pipeline rules |
| Environment separation | Production credentials not available in dev | Secret scoping |
| Rollback plan | Automated rollback trigger on failure | Pipeline definition |
| Immutable deployments | Artifacts deployed by reference, not rebuilt | Deployment logs |
| Drift detection | Unauthorized production changes detected | Monitoring tool |
| Audit trail | Who deployed what, when, and why | Deployment logs |
6. Incident Response
| Scenario | Response | Owner |
|---|---|---|
| Secret leaked | Rotate secret, revoke tokens, audit usage | Security team |
| Malicious commit | Revert, investigate, revoke credentials | Platform team |
| Compromised runner | Terminate runner, rebuild, review logs | Platform team |
| Unauthorized deployment | Rollback, freeze pipeline, audit | Release manager |
| Tampered artifact | Block deployment, trace provenance | Security team |
Explanation
Pipeline security is supply chain security applied to the machine that ships your code. Protect the source, the build, and the deploy path and you’ve cut off most of the ways malicious code reaches production. The template maps every control to how you verify it, so it doubles as an audit artifact.
GitHub Actions Security Configuration
name: Secure CI Pipeline
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
packages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/ci-role
aws-region: us-east-1
# No static keys - OIDC only
- name: Build and sign
uses: sigstore/cosign-installer@v3
- run: |
cosign sign-blob --yes artifact.tar.gz
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: spdx-json
output-file: sbom.spdx.json
- name: Scan dependencies
uses: github/codeql-action/init@v3
- run: npm audit --audit-level=high
- uses: github/codeql-action/analyze@v3
SLSA Provenance Example
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "artifact.tar.gz",
"digest": { "sha256": "abc123..." }
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"builder": { "id": "github-actions" },
"buildType": "https://github.com/actions/runner",
"invocation": {
"configSource": {
"uri": "git+https://github.com/org/repo",
"digest": { "sha1": "commit-hash" }
}
},
"materials": [
{ "uri": "git+https://github.com/org/repo", "digest": { "sha1": "commit-hash" } }
]
}
}
Pipeline Security Audit Checklist
| Control | Verified | Notes |
|---|---|---|
| No long-lived secrets in CI | ||
| OIDC for cloud auth | ||
| Actions pinned to SHA | ||
| Minimal permissions per job | ||
| Branch protection on main | ||
| Signed artifacts verified | ||
| SBOM generated per build | ||
| Dependency scan in pipeline | ||
| Runners isolated per env | ||
| Audit log retention > 90 days |
Real-World Pipeline Attacks
These controls are not hypothetical hygiene: they map to incidents that already happened.
SolarWinds (2020). Attackers got into the build system itself and pushed a backdoored Orion update to thousands of organizations, signed and shipped through the official channel. It’s the canonical case for why build provenance and reproducible builds matter: the malicious artifact passed every consumer-side check because the compromise happened upstream.
Codecov (2021). A modified bash uploader script inside Codecov’s CI tooling exfiltrated environment variables, including secrets, from thousands of customer pipelines for two months. The exact control that would have caught it: pinned, hashed dependencies and alerts on unexpected outbound traffic from runners.
tj-actions/changed-files (2025). A popular GitHub Action was compromised and pushed a malicious release that leaked CI secrets in workflow logs. Teams that pinned actions to commit SHAs were immune by default. Tags moved to the malicious commit; SHAs didn’t.
PHP git server (2021). Attackers pushed backdoored commits directly to PHP’s own repository, impersonating core maintainers. The response is the interesting part: PHP dropped its self-hosted git for GitHub with mandatory 2FA. Sometimes the fix is outsourcing the trust boundary to a platform with better controls.
The pattern across all four: the pipeline is trusted by default, and that trust is exactly what attackers exploit. That’s what this template is for: it makes the trust explicit and verifiable instead of assumed.
Variants
- GitHub Actions security checklist: Focuses on actions pinning, workflow permissions, and reusable workflows.
- GitLab CI security template: Includes CI/CD job token scopes, protected runners, and compliance pipelines.
- Jenkins hardening template: Covers plugin management, agent isolation, and Groovy sandboxing.
- Container-native pipeline: Emphasizes image signing, registry scanning, and Kubernetes admission.
- High-compliance pipeline: Adds SLSA Level 3, dual approval, and signed SBOMs for regulated environments.
What works
- Store pipeline definitions as code and review them like application code.
- Use short-lived credentials and OIDC instead of long-lived secrets.
- Scan dependencies before merging and before deploying.
- Sign artifacts and verify signatures before deployment.
- Separate build and production environments physically or logically.
- Require human approval for production deployments.
- Generate and retain SBOMs for every release.
- Watch pipeline activity for anything unusual — a build that suddenly takes twice as long or runs at 3 AM is worth a look.
Common Mistakes
- Storing secrets in environment variables or pipeline files.
- Using third-party actions without pinning or reviewing them.
- Allowing any branch to deploy to production.
- Running production and dev workloads on the same runner.
- Skipping security scans for hotfix deployments.
- Not rotating pipeline credentials after a compromise.
- Trusting artifacts without signature verification.
Troubleshooting
- A secret shows up in a build log: rotate it immediately, and assume it’s compromised the moment it prints. Then find the code path that logged it (usually an unmasked echo or an error dump) and add the value to the log masking list.
- A dependency scan fails on a transitive CVE you can’t fix: check whether the vulnerable code path is actually reachable. If it isn’t, document the accepted risk with a review date and pin an override; if it’s reachable, bump the parent dependency or vendor a patch.
- Provenance verification fails at deploy time: don’t deploy. A mismatched attestation means the artifact isn’t what CI built. Diff the digest against the CI artifact store and find out where the substitution happened before you ship anything.
- An approval gate gets bypassed: check who has admin rights on the environment protection rules. Gates fail open more often from misconfigured permissions than from attackers.
- A third-party action publishes a malicious release: if you pin to commit SHAs this can’t reach you. Tags move; SHAs don’t. If you’re on a tag, pin the known-good SHA immediately and audit what ran in the window.
See Also
- SLSA framework — slsa.dev: the supply-chain levels the provenance controls in this template come from
- Sigstore cosign documentation: keyless artifact signing used in the workflow above
- Security hardening for GitHub Actions: GitHub’s own guidance on workflow permissions, OIDC, and third-party actions
- OWASP CI/CD Security Top 10: the threat list these controls map to
- in-toto attestation framework: the standard underneath SLSA provenance statements
- CI/CD Security guide: the long-form companion to this template
- Docker secrets management recipe: the container-runtime side of secrets handling
Companion code: CI/CD pipeline security resources: the controls template, hardened workflow, SLSA provenance example, and audit checklist from this page.
Frequently Asked Questions
What is the biggest risk in CI/CD?
Credential theft from a runner or a pipeline file — once an attacker has your deploy keys they don't need to touch your application at all; they just ship their own code as if it were yours.
How do we balance security with fast deployments?
Automate the checks and keep them fast, then reserve human approval for production only. Shift-left scanning gives developers feedback in minutes, which is what makes the security gate something they will actually keep.
What is SLSA provenance?
SLSA is the framework everyone means when they talk supply chain security. Provenance records how an artifact was built: source repo, build command, dependencies. A tampered or rebuilt artifact doesn't match its own paper trail.
How do we secure secrets in CI/CD pipelines?
Stick them in a dedicated secrets manager: HashiCorp Vault, AWS Secrets Manager, or GitHub Actions secrets. Never hardcode them in pipeline files or env vars, and prefer OIDC for cloud auth over static keys. Rotate quarterly and after any suspected compromise. On self-hosted runners, make sure secrets get scrubbed from logs and the runner itself is ephemeral.
What is the difference between SAST, DAST, and SCA?
SAST analyzes source code for vulnerabilities without running it, DAST pokes a running application from the outside, and SCA scans your dependencies for known CVEs. You want all three in the pipeline: SAST on every PR, SCA on every merge, DAST against staging.
Should we use self-hosted or cloud-managed runners?
Cloud-managed runners (GitHub-hosted, GitLab SaaS) are ephemeral and isolated by default, which shrinks the attack surface for free. Self-hosted runners earn their keep for private network access or specialized hardware, but they need hardening: ephemeral instances, no shared state between jobs, and network segmentation between build and prod. And never run production deploys on the same runner as untrusted PR builds.
How do we implement dual approval for production deployments?
Have two different team members approve manually before anything hits production. Environment protection rules in GitHub Actions or protected environments in GitLab both do this. The approvers shouldn't be the person who triggered the run. Log every approval with timestamp, user, and reason for the audit trail.
What is an SBOM and why do we need it?
An SBOM (Software Bill of Materials) is a machine-readable inventory of all components in a software artifact, including transitive dependencies, versions, and licenses — so you can scan for vulnerabilities, check licenses, and actually see what's in your supply chain. Generate one per build with syft, trivy, or GitHub's dependency graph, keep it next to the artifact, and retain it for as long as that software is deployed.
What is cosign and how does it work?
Cosign, from the Sigstore project, signs and verifies container images and blobs. Its keyless mode uses the OIDC tokens your CI provider already issues, so there's no signing key to manage or leak. Signatures land in the Rekor transparency log, which makes them publicly verifiable. Sign artifacts after build and verify before deploy.
How do we handle secrets for self-hosted runners?
Run ephemeral runners that die after each job and inject secrets at runtime from your secrets manager — never leave them on the runner disk. Mask secret values out of pipeline logs, rotate runner credentials regularly, and audit who touched the runner. For sensitive environments, give each one its own runner pool behind network isolation.
Related Resources
Container Security Baseline Template
A baseline template for hardening container images, runtimes, and orchestration configurations across environments.
DocRBAC Policy Template
A template for defining role-based access control policies, including roles, permissions, assignment rules, and review cadence.
DocSecret Rotation Schedule Template
A template for tracking and scheduling the rotation of API keys, passwords, certificates, and other secrets across systems.
DocDependency Vulnerability Report Template
A template for documenting security findings in dependencies, including severity, impact, and remediation steps for engineering teams.
GuideCI/CD Security: Harden Your Pipelines and Prevent Supply
A practical guide to securing CI/CD pipelines: secrets management, least-privilege runners, artifact signing, dependency scanning, and defending against supply chain attacks.
RecipeDocker Secrets Management Without Hardcoding Credentials
Inject secrets into containers using Docker secrets, env files, and external secret managers without hardcoding them in images.