The TLDR
When you run npm install or pip install, you’re executing code written by someone you’ve never met, hosted on infrastructure you don’t control, signed by keys you’ve never verified. Modern software is built on dependency trees hundreds of levels deep. A supply chain attack compromises one package and inherits access to every application that depends on it. SolarWinds compromised 18,000 organizations through a single build system. The event-stream npm attack reached millions of installations through one maintainer handoff. Your node_modules folder is the largest trust surface in your application.
The Reality
The average JavaScript project has over 1,000 transitive dependencies. The average Python project has hundreds. Each dependency is a piece of code that runs with the same permissions as your application — reading files, making network requests, accessing environment variables, executing system commands.
You audit your own code. You review your team’s pull requests. But the code in node_modules/ or site-packages/? That’s trusted implicitly. And that’s where supply chain attacks live.
The Scale
- npm hosts over 2 million packages with over 100 billion downloads per month
- PyPI hosts over 500,000 packages
- RubyGems, crates.io, Maven Central — every ecosystem has the same structure
- A single popular package can have tens of thousands of downstream dependents
How It Works
Dependency Confusion
Discovered by Alex Birsan in 2021, dependency confusion exploits how package managers resolve names between private (internal) and public registries.
Many companies have internal packages with names like company-utils hosted on private registries. If an attacker publishes a package with the same name on the public registry (npm, PyPI) with a higher version number, some package managers will prefer the public version.
The attacker’s payload runs during installation — preinstall scripts in npm, setup.py execution in pip. Birsan demonstrated this against Apple, Microsoft, and PayPal, gaining code execution on internal build systems.
Typosquatting
Register a package with a name close to a popular one:
lodash→lodashs,lodash-utils,1odashrequests→reqeusts,request,python-requests
Developers who mistype a package name or copy a wrong string install the malicious package instead. The malicious package often includes the legitimate package’s functionality (to avoid immediate detection) plus a payload.
PyPI and npm both regularly discover and remove typosquatting packages — but the window between publication and removal can be hours to days, during which thousands of installations occur.
Maintainer Compromise
The event-stream attack (2018): A popular npm package (event-stream, ~2 million weekly downloads) had a single maintainer who was burnt out. A new contributor offered to help. The original maintainer transferred ownership. The new maintainer added a dependency (flatmap-stream) that contained an encrypted payload targeting the Copay Bitcoin wallet, attempting to steal cryptocurrency private keys.
The attack was in production for two months before detection. It demonstrates the fundamental problem: open-source packages are maintained by volunteers with no security obligations, and ownership transfer has no verification process.
Build System Compromise
SolarWinds (2020): Attackers compromised SolarWinds’ build system and injected malicious code into the Orion software update. The update was digitally signed by SolarWinds (because it was built in their compromised build pipeline), so it passed all integrity checks. 18,000 organizations installed the compromised update, including the US Treasury, Department of Homeland Security, and Fortune 500 companies.
This is the nuclear option of supply chain attacks — compromise the build pipeline itself, and every artifact it produces is weaponized.
Codecov (2021): Attackers modified Codecov’s Bash Uploader script (used in CI/CD pipelines) to export environment variables — including CI secrets, API keys, and tokens — to an attacker-controlled server. The compromised script was active for two months.
Malicious Packages
Direct publication of malicious packages on public registries:
- Packages that steal environment variables (AWS keys, database credentials, API tokens) during installation
- Packages that install cryptominers
- Packages that open reverse shells
- Packages that exfiltrate source code from the build environment
MITRE ATT&CK T1195.002 (Supply Chain Compromise: Compromise Software Supply Chain) documents this technique class.
How It Gets Exploited
The CI/CD Vector
Build pipelines (GitHub Actions, Jenkins, GitLab CI) install dependencies from public registries. A compromised dependency gains access to:
- Source code repositories
- Build artifacts and signing keys
- Deployment credentials
- CI/CD secrets (API keys, tokens, cloud credentials)
The pipeline trusts its dependencies because it has to — the build won’t work without them.
The Transitive Trust Problem
You audit your direct dependencies. But each of those depends on other packages, which depend on other packages. A vulnerability or compromise five levels deep in the dependency tree affects your application, and you probably don’t even know the package exists.
The Log4Shell vulnerability (CVE-2021-44228) demonstrated this: Log4j was a transitive dependency in thousands of applications whose developers had never directly referenced it. Finding and patching every affected system took months across the industry.
What You Can Do
Dependency Management
- Lock your dependencies — use lock files (
package-lock.json,Pipfile.lock,Cargo.lock) to pin exact versions. Don’t allow ranges in production. - Audit your dependency tree —
npm audit,pip-audit,cargo audit. Run these in CI. - Minimize dependencies — before adding a package, ask: can I write this in 20 lines instead of adding a dependency with its own dependency tree?
- Review new dependencies — check the package’s maintainers, download count, last update, and GitHub activity before adding it.
- Use private registries with proxy/caching — Artifactory, Verdaccio, or similar. This prevents dependency confusion and gives you a chokepoint for scanning.
Build Pipeline Security
- Pin actions and tools in CI/CD to specific commit SHAs, not tags or branches
- Isolate build environments — don’t share secrets across jobs that don’t need them
- Use SLSA (Supply-chain Levels for Software Artifacts) framework to improve build provenance
- Verify signatures on dependencies where available (Sigstore, npm provenance)
- Rotate secrets regularly and audit access
Monitoring
- Dependabot / Renovate — automated dependency update PRs with security alerts
- Socket.dev — detects supply chain attacks in npm packages by analyzing behavior
- Snyk / Grype — vulnerability scanning for container images and dependencies
- SBOM (Software Bill of Materials) — maintain and publish an SBOM so downstream consumers know what’s in your software
The Cultural Fix
The open-source maintainer burnout problem is structural. The event-stream attack happened because a single exhausted volunteer transferred ownership to a stranger. If your business depends on open-source packages:
- Sponsor maintainers of critical dependencies
- Contribute security audits to packages you depend on
- Don’t assume someone else is handling security for the libraries you use
Sources & Further Reading
- MITRE ATT&CK T1195.002: Supply Chain Compromise — attack technique documentation
- NIST SSDF: Secure Software Development Framework — federal secure development guidance
- SLSA Framework — supply-chain integrity levels
- Log4Shell (CVE-2021-44228) — the transitive dependency crisis
- Alex Birsan: Dependency Confusion — the original dependency confusion research
- Socket.dev — supply chain attack detection for npm
- Sigstore — open-source signing and verification for software artifacts