Supply chain attacks against container pipelines have moved from opportunistic incidents to systematic, repeatable playbooks. Threat actors targeting the software build and delivery path no longer need to exploit running services — compromising a single upstream dependency or a CI credential gives them a foothold inside every organization that pulls that artifact. Understanding the distinct attack patterns helps defenders prioritize controls that actually interrupt the kill chain.
What follows is a taxonomy of five attack classes drawn from publicly documented threat research (MITRE ATT&CK for containers, CNCF security whitepapers, and academic supply chain studies). We're not attributing these to specific incidents or naming victim organizations. The patterns are real; the scenarios below are synthetic composites used to illustrate the attack surface.
Pattern 1: Dependency Confusion and Package Namespace Squatting
Many package managers — npm, PyPI, RubyGems — will prefer a public registry package over a private one when namespace resolution is ambiguous. An attacker who discovers that an internal package name (e.g., payments-core) is unpublished on the public registry can register that name with a higher version number. The build system resolves the public package, because the version is numerically greater than the private release, and executes the attacker's code during npm install or pip install.
Typosquatting is the simpler cousin: registering reqeusts instead of requests, betting that a developer will mistype the dependency name in a manifest. Both patterns exploit the gap between "package exists somewhere in the registry" and "package was reviewed and intentionally added."
Consider a hypothetical: a fintech platform team running ~80 Node.js microservices on EKS. Their private npm registry held a utility package named @internal/crypto-utils. When a new contractor spun up a fresh development environment, they accidentally omitted the @internal scope prefix in one package.json, pulling the public crypto-utils instead. Without SBOM generation at the CI layer, this substitution was invisible until a routine dependency drift check six weeks later.
SLSA provenance relevance: SLSA Level 2 build provenance records the exact source repository URI and commit hash for each artifact. It does not intrinsically prevent a confused dependency resolution — that requires lockfile enforcement and a verified private registry policy. SLSA is a build integrity control; registry namespace hygiene is a separate control layer.
Pattern 2: Build Environment Poisoning
The build environment — the CI runner, the base build image, the set of build tools — is itself an attack surface. If an attacker can introduce a malicious tool into the standard build container (e.g., by compromising a base image layer in ECR or Docker Hub), every project that inherits that base image gets a tainted build environment without any change to application source code.
This attack is particularly insidious because build tool compromise is below the artifact layer that most scanning tools inspect. A container image scanner looking at the output image may find nothing wrong — the malicious code lives in the build toolchain that produced the image, not in the final artifact.
SLSA Level 3 addresses this directly with the "isolated build" requirement: each build must run in an ephemeral environment that was not influenced by prior builds, with hermetic inputs declared explicitly. At SLSA Level 2, the provenance attests what ran the build (the builder platform identity) but doesn't guarantee isolation. Moving to Level 3 is operationally non-trivial — most teams using standard GitHub Actions or GitLab CI shared runners operate at Level 2 at best, because true build hermiticity requires either fully managed build services (like Google Cloud Build with verified provenance) or significant infrastructure investment.
We're not saying Level 2 is inadequate for most threat models — it provides meaningful protection against post-build tampering and establishes the attestation chain. The point is that Level 3 requires explicit architecture decisions, not just a tooling upgrade.
Pattern 3: CI Credential Exfiltration and Pipeline Hijacking
CI systems hold some of the most sensitive credentials in an engineering environment: registry push secrets, cloud provider keys, signing keys, deploy tokens. A common attack pattern targets the secrets store directly — either by exploiting a misconfigured secrets exposure in a public pull request pipeline, by compromising a developer's machine that has CI admin access, or by planting code in a dependency that exfiltrates environment variables at build time.
The MITRE ATT&CK for Containers framework (ATT&CK ICS matrix, TA0006 Credential Access, T1552.001 Credentials in Files) documents how env dumps inside container builds can extract registry credentials that persist as build logs. GitHub Actions specifically warns against printing secrets in logs — but third-party action dependencies can still exfiltrate them via outbound HTTP calls during the build step.
In-toto attestations and Sigstore's Rekor transparency log provide a partial defense here. When every build artifact is signed by a known key and the signature entry is recorded on Rekor, an attacker who steals a signing key and re-signs a tampered artifact creates a second Rekor entry for the same artifact digest — which becomes detectable via log monitoring. The Rekor append-only log means signatures can't be silently backdated. This doesn't prevent credential theft, but it limits the blast radius: a stolen signing key used maliciously leaves a verifiable record.
What Rekor Doesn't Cover
Rekor records signatures; it doesn't validate that the signing key itself was properly protected. If a CI pipeline signs artifacts using a machine identity (e.g., a GitHub Actions OIDC token via cosign keyless signing), the chain of trust depends on GitHub's OIDC provider being authoritative for that repository. An attacker with write access to the repository can trigger a legitimate signing workflow, producing a valid Rekor entry for a malicious build. This is the fundamental difference between "the build was signed" and "the build was produced from the claimed source with trusted inputs."
Pattern 4: Compromised Registry Layers and Base Image Drift
Container images are composites of layers. Pulling python:3.11-slim in January 2025 gives you a different set of packages than pulling the same tag in September 2025 — because tags are mutable. A base image that passes a security scan at build time may contain new CVEs by the time a new instance of the service pulls and starts it, even if no application code changed.
The more targeted variant of this pattern involves a compromised upstream image in a public registry. If a widely used community image (not an official image) gets a new maintainer who introduces a backdoor, every downstream consumer inheriting that layer gets the compromise on their next build cycle. This is distinct from a CVE in a shipped library — it's malicious code intentionally placed.
Image pinning to digests (SHA256) rather than tags is the standard mitigation: python@sha256:abc123... will always pull the same layer set, regardless of what the 3.11-slim tag points to. This trades security for freshness — a pinned digest doesn't automatically receive security updates. The operational practice is to run automated base image update PRs (Dependabot or Renovate) that update the pinned digest on a cadence, with each update triggering a full image scan before merge.
Build provenance at SLSA Level 2 records the base image digest used in the build. This means the provenance attestation itself becomes a queryable artifact: "which services were built using base image digest X?" When a malicious layer is discovered, containment becomes a matter of querying the provenance store rather than auditing individual Dockerfiles.
Pattern 5: Transitive Dependency Compromise
Direct dependencies get scrutinized. Transitive dependencies — the dependencies of your dependencies, sometimes 4-5 levels deep — often do not. The average production Node.js application has 400-600 transitive dependencies. A Python data processing service may pull in 200+ packages it never directly imports.
An attacker compromising a deeply transitive package takes the path of least resistance: find a low-visibility npm package with 50,000 weekly downloads, minimal maintainer activity, and no SBOM or provenance attached. Publish a new version with a post-install hook that runs at npm install time. The attack executes before any container scanning step, inside the developer's local environment or the CI runner.
SCA tools that scan the SBOM — not just the lock file — have an advantage here. A CycloneDX SBOM generated during the CI build captures the full resolved dependency graph, including transitive packages with their exact versions and checksums. Comparing this SBOM against a known-good baseline from the prior build surfaces transitive dependency drift: packages that changed between builds without any corresponding change to package.json or requirements.txt.
Dependency drift detection is not the same as vulnerability scanning. A new transitive dependency version might have no known CVE and still be malicious. The signal is change: something in the resolved graph is different from what was there before, and the change wasn't initiated by a developer action in the manifest. That anomaly deserves human review regardless of scanner output.
The Layered Defense Model
None of these five patterns is defeated by a single control. SLSA provenance addresses build integrity; it doesn't protect against confused dependency resolution. Package lockfiles prevent namespace squatting but don't detect compromised base images. Rekor's transparency log makes signing key abuse detectable but doesn't prevent a legitimate key from being used to sign a malicious build.
The practical control stack for a container pipeline looks like this: lockfile pinning and verified private registry scope (Pattern 1), ephemeral CI runners with explicit base image digests (Patterns 2 and 4), OIDC-based keyless signing via cosign with Rekor entry (Pattern 3), SBOM generation with drift comparison on every build (Pattern 5), and admission policy enforcement that blocks deployment of any workload without a valid in-toto attestation matching your SLSA policy. Each layer narrows the attack surface; none eliminates it entirely.
The teams that handle supply chain threats well treat provenance as plumbing, not a project. SBOM generation, cosign signing, and Rekor entries aren't one-time compliance checkboxes — they're per-build artifacts that have to be generated, stored, and queried for the control to be real. The operational investment is real, but so is the detection capability it buys.