Supply Chain Attacks: Containers, Packages, and What to Do About Them
Software supply chain attacks don’t exploit your code—they exploit your trust. Rather than breaking through your defenses directly, attackers compromise the tools, registries, and packages you pull in and rely on. The attack surface is everything upstream of your own code, and it’s much larger than most teams realize.
What Makes Supply Chain Attacks Different
A traditional attack tries to break into your system. A supply chain attack waits for you to invite it in. By the time you’re affected, you’ve already run the malicious code—as part of your build pipeline, your container startup, or your app’s install script.
This makes them particularly insidious:
- Detection is hard. The malicious code often looks legitimate. It came from a trusted source. Your dependency resolved successfully. Your build passed.
- Blast radius is wide. One compromised package can affect every project that depends on it, across every organization.
- Your security posture doesn’t matter. You could have perfect code with zero CVEs and still be exploited through a dependency you didn’t write and never audited.
Container Supply Chain Attacks
Containers have their own supply chain, and it starts at the base image.
Compromised Base Images
When you write FROM ubuntu:22.04 or FROM node:20-alpine, you’re implicitly trusting Docker Hub (or whichever registry you’re pulling from), the image maintainer, and every layer in that image. Any of these can be compromised.
The most common vector is a poisoned public image. Attackers publish images with names similar to popular ones (typosquatting), or in some cases compromise the account of a legitimate maintainer and push a backdoored update. When you pull and run it, you’re running their code with the trust level you’d give any other container in your system.
What to do:
- Pin to a digest, not a tag. Tags like
latestor even22.04are mutable. The same tag can point to different image content over time. A digest (sha256:abc123...) is immutable. UseFROM ubuntu@sha256:...in production Dockerfiles. - Use minimal base images. Less software means fewer attack surfaces. Alpine, distroless, and scratch images dramatically reduce what an attacker has to work with if they do get in. Distroless images in particular contain no shell, no package manager, and no utilities—making post-exploitation much harder.
- Pull from a private registry you control. Mirror the public images you depend on into your own registry (AWS ECR, GCP Artifact Registry, Harbor, etc.). Vulnerability scanning, policy enforcement, and image promotion pipelines all live in your registry—not Docker Hub’s.
- Scan images before deployment. Tools like Trivy, Grype, and Snyk Container can scan images for known CVEs in both OS packages and language-level dependencies. Wire this into your CI pipeline and block on high/critical severity.
Build-Time Attacks
Attacks don’t only happen at the image level. Your build process itself is a supply chain. If an attacker compromises a package you install during a build (e.g., a RUN pip install -r requirements.txt step), the resulting image is tainted even if your base image was clean.
What to do:
- Lock all dependencies. Use lockfiles (
package-lock.json,poetry.lock,requirements.txtwith pinned versions). Commit them. Never run installs without them. - Separate build and runtime images. Use multi-stage Dockerfiles. Your build stage can have compilers, package managers, and dev tooling. Your runtime stage should only have what’s needed to run the application. This limits what ends up in the final image even if something is installed at build time.
- Verify checksums of downloaded artifacts. If your Dockerfile fetches binaries (e.g.,
curl https://... | bashorwget ... && chmod +x), that’s a red flag. At minimum, verify a checksum against a known-good value. Better: don’t fetch arbitrary binaries at all.
Package Manager Supply Chain Attacks
Package managers (npm, PyPI, RubyGems, crates.io, etc.) are the canonical supply chain risk for application code.
Typosquatting and Dependency Confusion
Typosquatting is straightforward: an attacker publishes reqeusts on PyPI, counting on developers to mistype requests. It can sit dormant for years before being used maliciously.
Dependency confusion is more sophisticated. It exploits the way package managers resolve packages when both a public registry and a private registry are configured. In 2021, security researcher Alex Birsan published a paper demonstrating that many package managers, when given both a public and private source, will prefer the higher-versioned package—regardless of which registry it came from. By publishing packages with the same name as a company’s internal packages (found via leaked package.json files, job postings, etc.) at a higher version, he got code execution inside dozens of large companies.
What to do:
- Scope your private packages. In npm, always use scoped packages (
@yourcompany/package-name) for internal packages. Configure your registry to only serve your scope from your private registry, and public packages from npm’s registry—never mix resolution. - Use dependency confusion mitigations. Configure npm, pip, or your package manager to prefer internal packages or to block internal package names from being resolved from public registries.
- Audit your transitive dependencies. You chose your direct dependencies. But every dependency has its own dependencies. Tools like
npm audit,pip-audit, andcargo auditcheck your full dependency tree against known vulnerability databases.
Compromised Maintainer Accounts
Even legitimate packages can be compromised. An attacker who gains access to a maintainer’s npm token or PyPI credentials can push a malicious release. This is what happened with event-stream in 2018—a popular npm package was transferred to a new maintainer who added a backdoor targeting a specific Bitcoin wallet.
What to do:
- Require 2FA for publishing. PyPI and npm both now require 2FA for critical package maintainers. For your own packages, enforce this.
- Enable Sigstore or provenance attestations. npm (as of npm 9) and PyPI both support publish provenance—a signed attestation that links a specific package version to the exact build and repository commit that produced it. Consumers can verify that the package wasn’t modified between source and registry.
- Watch for unexpected dependency updates. Renovate and Dependabot can notify you of updates. Before merging automated updates to sensitive packages, review the changelog and diff. A suspicious version bump (especially a patch version that adds new behavior) is worth scrutinizing.
Debian Packages: A Different Threat Model
Debian’s packaging system (apt, .deb) is worth thinking about separately, because its threat model differs from PyPI or npm in important ways.
Why Debian Is (Mostly) Different
Debian packages go through a structured review process. Packages in the official Debian or Ubuntu repositories are:
- Maintained by known, accountable individuals with GPG-signed uploads.
- Reviewed by a team before entering the main archive.
- Distributed via mirrors that are cryptographically signed by the distribution’s archive key.
When you run apt install curl, your system checks that the package index is signed by Ubuntu’s or Debian’s official key, and that the package itself matches the signed index. A compromised mirror cannot serve you a modified package without breaking the signature.
This means the trust anchors are the distribution maintainers and the archive signing key—not individual package publishers the way PyPI or npm work. The attack surface is narrower.
Where Debian Still Has Risk
That doesn’t mean Debian is immune:
- Third-party repositories (PPAs, custom apt sources). When you add a PPA or a vendor’s apt repo (e.g.,
deb [signed-by=...] https://packages.vendor.com/...), you’re trusting that vendor’s signing key and their security practices. These are not subject to Debian’s review process. A compromised vendor repo can serve malicious packages. - Upstream source tarballs. Debian packages wrap upstream software. If upstream is compromised (as nearly happened with the XZ Utils backdoor in 2024, which targeted the Debian/RPM packaging path specifically), the Debian packaging itself may not catch it. Debian maintainers can’t audit all upstream code, only the packaging layer.
- Outdated packages in stable releases. Debian stable prioritizes stability over recency. Packages often lag behind upstream by months or years. Known CVEs in upstream may be present in the Debian package version even after upstream has patched them. Debian backports security fixes, but this is imperfect.
- Container images using Debian base layers. When you use
debian:bookwormorubuntu:24.04as a base image and then runapt install ..., all the above applies—plus you need to keep the image updated. Unlike a running system that gets security updates viaunattended-upgrades, a container image is static. Stale images accumulate CVEs.
How to Think About Debian Packages in Practice
The practical frame: Debian’s official repos are trustworthy. Everything else is a third-party supply chain.
- Treat any non-official apt source (PPAs, vendor repos) with the same skepticism you’d treat a PyPI or npm package.
- For containers, rebuild your images regularly to pick up OS-level security patches, or use a tool like Docker Scout or Trivy to track when your base images fall behind.
- Watch the XZ Utils incident closely as a case study—it showed that the attack targeted specifically the Debian/RPM build path (via systemd’s
sd_notifyintegration), meaning understanding the packaging ecosystem’s details is itself a security concern.
Common Remediations: A Summary
| Threat | Mitigation |
|---|---|
| Poisoned base images | Pin to digest; use private registry mirror |
| Malicious build-time installs | Lockfiles; multi-stage builds; no curl | bash |
| Typosquatting | Audit deps; use scoped packages |
| Dependency confusion | Scope private packages; configure registry precedence |
| Compromised maintainer | Provenance attestations; review updates before merging |
| Stale container OS packages | Regular image rebuilds; scan for OS CVEs |
| Third-party apt repos | Treat as untrusted third-party; audit signing key ownership |
The Underlying Principle
Supply chain security isn’t a feature you add—it’s a discipline you build into your development workflow. The packages you pull, the images you run, the registries you trust: each is an implicit security decision. Making those decisions explicit—through pinning, scanning, provenance verification, and private mirrors—turns an invisible attack surface into a managed one.
The goal isn’t to eliminate all third-party code (that’s impossible), but to ensure you know what you’re running, where it came from, and that it hasn’t changed unexpectedly since you last verified it.
Comments
Came here from LinkedIn or X? Join the conversation below — all discussion lives here.