Supply Chain
Dependency confusion, typosquatting, malicious post-installs, SBOMs, sigstore.
Supply Chain
Modern software is mostly other people's code. A typical Node app installs ~1,000 transitive packages on npm install; a typical Python app, a few hundred. Each one runs install-time hooks, executes at runtime, and has the same blast radius as your own code. Supply-chain attacks are attacks on those packages.
The five attack classes
1. Dependency confusion
You have a private package @yourorg/internal-utils registered in your internal registry. Someone publishes a public package called internal-utils (no scope) on npm. Your build configuration is wrong; the public registry takes priority for unscoped names. Your CI pulls the malicious public copy.
The 2021 disclosure by Alex Birsan demonstrated this against PayPal, Apple, Microsoft, Yelp, Uber, and 30+ others — by simply uploading package-x to public npm with version 99.99.99 and waiting for builds to grab it.
Defense:
- Always scope private packages:
@yourorg/name, never bareinternal-utils. - Pin the registry per scope in
.npmrc:@yourorg:registry=https://your-registry. - Pre-claim the bare names on public npm with empty placeholder packages.
- Use a single-registry proxy (Verdaccio, Artifactory, JFrog) that fails closed.
2. Typosquatting
Attacker registers lodahs, requets, momentlibrary — names one keystroke off real popular packages. Some developer typos. The malicious package ships a payload (often crypto-stealer, sometimes credential-exfil).
The 2021 ua-parser-js campaign — the real package, hijacked via a maintainer account compromise — installed crypto-miners and password-stealers across millions of CI runs.
Defense:
- Pre-commit lint that rejects unknown package names not in your allowlist.
- Socket.dev / Snyk / npm audit's "popular packages first" heuristics.
- Code review of every new dependency added to
package.json.
3. Namespace hijacking / maintainer takeover
A package is popular. The original maintainer hands it off (or has their account stolen). The new maintainer adds a malicious release.
event-stream (2018): A maintainer transferred ownership to a stranger; the stranger added flatmap-stream as a dependency, which contained a Bitcoin-wallet exfiltration payload targeting Copay. ~8M downloads/week of event-stream.
ua-parser-js (2021): Account compromise. Malicious payload installed crypto-miners on CI runners.
Defense:
- Lockfile pins exact versions; new releases don't automatically install.
- Dependabot / Renovate update PRs trigger code review on every bump.
- Sigstore / npm provenance (
npm publish --provenance) — verify the artifact was built by the expected CI from the expected source repo. - Watch for unusually broad post-install scripts.
4. Malicious post-install scripts
npm install runs postinstall hooks of every package, with full developer-machine privileges. Some packages need this for legitimate native compilation; many don't.
A malicious post-install can exfil ~/.aws/credentials, ~/.ssh/, or any env vars.
Defense:
npm install --ignore-scriptsin CI for the test phase; only run scripts in the trusted build phase.- pnpm's
onlyBuiltDependenciesconfig restricts which packages can run scripts. - Sandboxed CI runners (containerised, ephemeral, no long-lived secrets).
5. Vulnerability churn (CVE fatigue)
Not really an attack — a defense problem. Modern dependency trees produce constant CVE alerts: most low-severity, many false-positive, occasional critical. Teams disable Dependabot because the noise overwhelms the signal.
Defense:
- Triage CVEs by reachability, not just severity: a "critical" in a dev-only dep is not the same as in a runtime hot path. Tools like Snyk and Endor Labs do reachability analysis.
- Automate the boring updates: Renovate with auto-merge for patch versions of trusted packages.
- Reserve human review for major version bumps and any security-flagged update.
Lockfiles, in detail
A lockfile (package-lock.json, yarn.lock, pnpm-lock.yaml, Pipfile.lock, poetry.lock, Cargo.lock, go.sum) records the exact resolved version of every dependency, transitive included, plus integrity hashes.
Without it, your build is non-deterministic. Two engineers run npm install an hour apart and get different transitive trees. CI runs differently from local. A package is yanked, a new one ships, your build breaks at 4am.
With it, npm ci (or yarn install --frozen-lockfile / pnpm install --frozen-lockfile) refuses to install anything not in the lockfile. Builds are reproducible. CVE attribution is precise.
Commit it. Always. Even for libraries (which historically said "don't commit lockfiles"), npm now recommends committing for libraries too — the lockfile drives your CI runs, not your published artifact.
SBOMs
A Software Bill of Materials is the inventory: every package, version, license, hash, source URL. Two formats:
- CycloneDX (OWASP) — widely used, supports build pipelines and security tooling.
- SPDX (Linux Foundation) — older, license-focused, also widely used.
When CVE-2021-44228 (log4shell) hit, the teams that knew which of their 200 services had log4j as a transitive of a transitive of... in less than 30 minutes, were the teams that had been generating SBOMs for every release. The rest spent 3 days grep'ing build logs.
Generate one per release. Archive it. Tools: Syft, cyclonedx-cli, sbom-tool.
Sigstore
Signing your build artifacts so consumers can verify provenance. Sigstore uses OIDC (Google, GitHub, etc) to bind a signature to a verified identity, with a transparency log for non-repudiation.
$ cosign sign --identity-token $OIDC_TOKEN gcr.io/myorg/myapp:v1.2.3
$ cosign verify --certificate-identity-regexp ".*@myorg\\.com$" \\
--certificate-oidc-issuer https://accounts.google.com \\
gcr.io/myorg/myapp:v1.2.3
npm's built-in --provenance flag does the same for published packages — the consumer can verify "this package was built by this GitHub Action in this repo at this commit."
SLSA — the framework for thinking about all of this
Supply-chain Levels for Software Artifacts (slsa.dev) is Google's maturity model:
- L1: Build process is documented.
- L2: Build runs in a hosted service (GitHub Actions / GitLab CI / etc).
- L3: Build is hardened — non-falsifiable provenance.
- L4: Strong against insider threats (two-party review, hermetic builds).
Most teams are L1 or L2. Aim for L3 — sigstore + a clean hosted CI is most of the way there.
What "right" looks like
Local dev:
- npm/yarn/pnpm with committed lockfile
- .npmrc pinning the registry
- pre-commit hook scanning for new packages
CI/CD:
- npm ci (lockfile-only install)
- --ignore-scripts in test phase
- SBOM generated and archived per build
- Dependabot/Renovate PRs auto-merge patch, hold major for review
- Container images signed with cosign
- Public packages published with --provenance
Operational:
- SBOMs archived for 7+ years
- "are we affected by CVE-X" answerable in <5 minutes
- Quarterly dependency-aging review for outdated transitives
What goes wrong (real, recent)
- xz-utils backdoor (2024): A multi-year campaign of social engineering ended with a backdoored release of xz that targeted SSH on Debian/Fedora. Caught by chance — a Postgres engineer noticed unusual SSH login latency. Industry-changing wake-up call about long-tail maintainer trust.
color.js/faker.js(2022): Author intentionally sabotaged his own packages with infinite loops to protest unpaid open-source labor. ~20M weekly downloads broken overnight.tj-actions/changed-files(2025): A widely-used GitHub Action got compromised; downstream repos exfiltrated secrets. Pin third-party Actions to commit SHA, not version tag, to limit blast radius.
The unifying lesson: trust is transitive, breakage is transitive, attacks are transitive. Your supply chain is everyone you depend on, and everyone they depend on, recursively. Treat it like that.
Tools in the wild
6 tools- serviceGitHub Dependabotfree tier
Automated PRs that bump dependencies on CVE alerts; run alongside your lockfile.
- serviceRenovatefree tier
Smarter dependency updater; configurable scheduling and grouping.
- cliSigstore (cosign / sigstore-py)free tier
Sign + verify build artifacts with OIDC-backed identities.
- cliSyftfree tier
Generates CycloneDX/SPDX SBOMs from container images and source trees.
- cliGrypefree tier
CVE scanner that consumes SBOMs from Syft.
- service
Behavior-based npm dependency analysis — flags new install scripts, network calls, suspicious patterns.