Back to Cheat Sheets

🔏 Improper Artifact Integrity Validation

OWASP Top 10 CI/CD Security Risks - CICD-SEC-9

CRITICAL RISK

📋 What Is It?

Improper Artifact Integrity Validation occurs when a delivery pipeline consumes and ships an artifact - source, a dependency, a container image, a build output, an IaC plan - without verifying that it is exactly the artifact that was supposed to be produced, by the process that was supposed to produce it. Every step is a hand-off: source moves from SCM to the runner, dependencies are pulled from registries, outputs are pushed to a store, and a deployer pulls them into production. Wherever a hand-off happens with no integrity check, an attacker who can influence the resource in transit or at rest can substitute a malicious version, and the pipeline will faithfully build, sign off on, and deploy it. The risk is the absence of a verifiable chain of custody.

CICD-SEC-9 OWASP Rank
Provenance Focus Area
Supply Chain Blast Radius

⚠️ Common Exploits

  • Mutable-Tag Substitution: Re-point :latest/:prod to a malicious image
  • Unsigned-Image Deployment: Cluster admits any image, signed or not
  • Poisoned Dependency: Floating version installed with no hash enforced
  • Cache / Mirror Poisoning: One tampered entry served to every consumer
  • Signed Backdoor: Build-time tampering signed with the real key (SolarWinds-class)
  • Provenance-Free Promotion: Any artifact in the "approved" registry ships
  • Signature Never Verified: Signing done but the check is missing or decorative

🔴 Attack Flow

1. Locate an unverified hand-off
↓
2. Position to influence the artifact (token, MITM, cache)
↓
3. Substitute or tamper (swap image, re-point tag)
↓
4. Let trust carry it - no integrity gate rejects it
↓
5. IMPACT: Arbitrary code in prod / backdoor reaches every customer!

❌ Vulnerable Code

# Build and push to a mutable tag, no signature, no provenance - name: Build and push run: | docker build -t registry.example.com/api:prod . docker push registry.example.com/api:prod # Anyone who can push to :prod can substitute the image - nothing notices # Dockerfile - base image can change under you FROM python:3.12-slim ← mutable, not pinned # Kubernetes - ":prod" is a moving pointer, cluster runs whatever it resolves to spec: containers: - name: api image: registry.example.com/api:prod # Deploy - no admission policy verifies signature or provenance kubectl apply -f deploy.yaml # Dependencies - floating range, no hashes: the index decides what you get pip install requests npm install ← integrity not enforced

✅ Secure Code

# GitHub Actions - keyless signing of the DIGEST (no long-lived key) permissions: id-token: write # OIDC identity for keyless signing packages: write steps: - uses: sigstore/cosign-installer@v3 - name: Sign the digest keylessly run: cosign sign --yes registry.example.com/api@${{ steps.build.outputs.digest }} - name: Emit SLSA provenance (proves source + builder, not just bytes) run: cosign attest --yes --predicate provenance.slsa.json \ --type slsaprovenance registry.example.com/api@sha256:<digest> # Consumer / deploy - VERIFY against the expected signer identity, fail closed cosign verify \ --certificate-identity-regexp '^https://github.com/acme/api/.+' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ registry.example.com/api@sha256:<digest> || exit 1 # Reference by immutable digest everywhere; enforce dependency hashes FROM python:3.12-slim@sha256:0d1f3c9e<...full-digest...> pip install --require-hashes -r requirements.lock npm ci # aborts on lockfile integrity mismatch # Admission policy (Sigstore policy-controller): signed + attested images ONLY kind: ClusterImagePolicy # unsigned/unattested images DENIED - fail closed

✓ Prevention Checklist

  • Sign every artifact and image (Sigstore/cosign), sign the digest
  • Verify signatures against an expected signer identity, fail closed
  • Generate SLSA/in-toto provenance and check it at deploy
  • Enforce policy-as-code over provenance (builder, source, branch)
  • Reference artifacts by immutable digest, never a mutable tag
  • Enforce admission control: only signed + attested images run
  • Enforce dependency hashes (--require-hashes, npm ci, go mod verify)
  • Treat caches and mirrors as untrusted until verified
  • Protect signing keys: keyless OIDC or KMS/HSM-backed
  • Validate IaC: locked plan + policy gate, apply only reviewed plan
  • Generate an SBOM per artifact as a signed attestation
  • Reconcile "what runs" against "what has valid provenance"

🔍 Detection & Tools

Signing & Provenance:

Sigstore / cosign in-toto / SLSA Rekor transparency log Syft (SBOM) SLSA provenance generator

Verification & Admission:

Sigstore policy-controller Kyverno verifyImages OPA / Gatekeeper Conftest Trivy

How to Test:

  • Check if deploy manifests reference tags vs. @sha256: digests
  • Confirm signatures are verified (not just produced) before deploy
  • Try deploying an unsigned image; confirm admission denies it
  • Verify provenance signer identity is pinned, not "any valid"
  • Check dependency installs enforce lockfile hashes
  • Look for a Rekor entry matching each released artifact

🌍 Real-World Incident Classes

  • Build-system compromise → signed backdoor (SolarWinds-class): An attacker injects malicious code during the build, after source review but before signing; the artifact is signed with the vendor's legitimate key and distributed normally, so every consumer that trusts the signature installs the backdoor. Signing attests the bytes, not the integrity of the process - which is why build-time provenance (SLSA) exists.
  • Mutable-tag / registry substitution: Deployments reference an image by a moving tag; an attacker with push access or a registry/mirror position re-points it, and the next rollout or autoscale pull runs the substituted image with no config diff to catch it.
  • Poisoned dependency / cache consumed without verification: A build resolves dependencies without enforcing pinned hashes or publisher signatures; a typosquat, hijacked maintainer account, or poisoned cache entry is compiled into the artifact and shipped as "trusted output."

Note: these are described as mechanism classes, not with invented figures. The durable takeaway is that a single unverified hand-off can lead to arbitrary code in production or a downstream supply-chain breach.

📌 Quick Tips

  • DO NOT reference images by mutable tag
  • DO NOT sign without verifying (and pinning the signer)
  • DO NOT admit unsigned/unattested images to prod
  • DO pin everything by @sha256: digest
  • DO generate and require SLSA provenance
  • DO enforce dependency hashes and verify mirrors
  • DO fail closed at the admission gate

📜 Compliance

Related Standards:

  • SLSA Build L2/L3 - Provenance & isolation
  • NIST SSDF (SP 800-218) / 800-161
  • EO 14028 Software supply-chain security
  • in-toto Supply-chain attestation framework
  • SOC 2 CC8.1 - Change management