Building a Zero-Trust DevSecOps Pipeline: From Code to Production
A comprehensive guide to implementing supply chain security, container signing, runtime protection, and policy-as-code in your CI/CD pipeline
The software supply chain has become a primary attack vector. From the SolarWinds compromise to the Log4Shell vulnerability, we've learned that security can't be an afterthought. Zero-Trust DevSecOps embeds security at every stage of the software delivery lifecycle — verifying every artifact, enforcing policies automatically, and detecting threats in real-time.
In this guide, I'll walk through building a production-grade zero-trust pipeline that blocked 2,400+ CVEs in its first year.

The Zero-Trust Security Model for CI/CD
Traditional security relies on perimeter defense — once inside the network, everything is trusted. Zero-Trust flips this: never trust, always verify. Applied to CI/CD, this means:
- Every code commit is signed and verified
- Every dependency is audited against known vulnerabilities
- Every container image is signed with cryptographic attestations
- Every deployment passes automated policy gates
- Every runtime behavior is monitored for anomalies
Stage 1: Source Security — Signed Commits and SBOM Generation
Security starts at the first keystroke. Require GPG-signed commits and generate Software Bill of Materials (SBOM) for every build:
name: Source Security
on: [push, pull_request]
jobs:
verify-signatures:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Verify commit signatures
run: |
git log --show-signature -1 HEAD | grep -q "Good signature"
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: spdx-json
output-file: sbom.spdx.json
- name: Upload SBOM as attestation
uses: actions/attest-sbom@v1
with:
subject-path: sbom.spdx.json
Stage 2: Build Security — SAST/DAST and Dependency Scanning
The build stage is where we catch vulnerabilities before they reach a container:
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: SAST - Semgrep Analysis
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
- name: Dependency Audit
run: |
npm audit --audit-level=high
# Generate vulnerability report
trivy fs --severity HIGH,CRITICAL --format sarif .
- name: Secret Detection
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
Key Insight: Fail Fast, Fix Fast
The critical principle is fail fast. Don't let vulnerable code progress through the pipeline. Set exit-code: 1 on security scans so builds break immediately when critical issues are found.
Stage 3: Registry Security — Image Signing with Sigstore
Container image signing with Sigstore/Cosign provides cryptographic proof of image provenance:
jobs:
build-and-sign:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- name: Build container image
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
- name: Push to registry
run: |
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Sign image with Cosign (keyless)
uses: sigstore/cosign-installer@v3
- run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Scan with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
format: sarif
Why Keyless Signing?
Traditional signing requires managing private keys — which become their own security liability. Sigstore's keyless signing uses short-lived certificates tied to OIDC identity (your GitHub Actions workflow identity). The signatures are recorded in a tamper-proof transparency log (Rekor), providing immutable audit trails.
Stage 4: Deployment Security — OPA Gatekeeper Policy Gates
Before any workload reaches the cluster, OPA Gatekeeper enforces organizational policies:
# policies/require-signed-images.rego
package kubernetes.admission
# Deny unsigned container images
deny[msg] {
container := input.request.object.spec.containers[_]
not has_valid_signature(container.image)
msg := sprintf("Image %v must be signed with Cosign", [container.image])
}
# Require images from approved registries only
deny[msg] {
container := input.request.object.spec.containers[_]
not approved_registry(container.image)
msg := sprintf("Image %v not from approved registry", [container.image])
}
# Enforce non-root containers
deny[msg] {
container := input.request.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := "All containers must run as non-root"
}
approved_registry(image) {
startswith(image, "ghcr.io/myorg/")
}
Deploy the constraint template:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: requiresignedimages
spec:
crd:
spec:
names:
kind: RequireSignedImages
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package requiresignedimages
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith(container.image, "ghcr.io/myorg/")
msg := "Only signed images from approved registries allowed"
}
Stage 5: Runtime Security — Falco Threat Detection
Even with perfect build-time security, runtime threats exist. Falco monitors kernel syscalls for anomalous behavior:
- rule: Unexpected Process in Container
desc: Detect processes not in the expected list
condition: >
spawned_process and container and
not proc.name in (node, npm, python, java) and
not proc.pname in (containerd-shim)
output: >
Unexpected process spawned in container
(user=%user.name command=%proc.cmdline container=%container.name)
priority: WARNING
- rule: Write to /etc from Container
desc: Detect writes to /etc directory in containers
condition: >
open_write and container and fd.directory = /etc
output: >
Write to /etc detected in container
(user=%user.name file=%fd.name container=%container.name)
priority: ERROR
- rule: Crypto Mining Detection
desc: Detect potential cryptocurrency mining
condition: >
spawned_process and container and
(proc.name in (xmrig, minergate, minerd) or
proc.args contains "stratum+tcp")
output: >
Potential crypto mining detected
(process=%proc.name args=%proc.args container=%container.name)
priority: CRITICAL
Results: Security at Scale
After implementing this pipeline for a fintech platform:
| Metric |
Before |
After |
| CVEs reaching production |
~200/year |
0 |
| Mean time to remediation |
30 days |
< 4 hours |
| Compliance audit findings |
Multiple |
Zero |
| Supply chain incidents |
Untracked |
Zero |
| Developer friction |
High (manual reviews) |
Low (automated gates) |
Key Lessons Learned
-
Shift left, but don't shift blame: Provide developers with fast feedback and auto-fix suggestions, not just failures.
-
Policy-as-code is non-negotiable: If a security rule isn't codified, it doesn't exist. Human review doesn't scale.
-
Runtime detection is your last defense: Build-time security catches known vulnerabilities; runtime security catches novel attacks.
-
Transparency logs create accountability: Every artifact's provenance is recorded immutably — critical for incident response and compliance.
-
Automate compliance evidence: SOC 2 and PCI-DSS audits become simple when every security control generates machine-readable evidence.
Getting Started
Start with the highest-impact, lowest-friction controls:
- Week 1: Enable Trivy container scanning in CI (catches 80% of known CVEs)
- Week 2: Add Cosign image signing (establishes provenance)
- Week 3: Deploy OPA Gatekeeper with basic policies (prevents misconfigurations)
- Week 4: Install Falco for runtime detection (catches novel threats)
The goal isn't perfection on day one — it's continuous improvement with automated guardrails that make the secure path the easy path.