The one-sentence version
Over 90% of modern software code comes from third-party dependencies. If even a single package is poisoned, every project using it will be compromised. Common methods include: Typosquatting (registering a name that differs by only one letter from a famous package), Account Takeover (stealing or tricking maintainers out of their accounts), Long-term Dormancy (gaining trust as a contributor over several years before planting a backdoor), and Dependency Confusion (tricking build tools into downloading a malicious package with the same name from a public repository instead of an internal one). The 2024 xz-utils backdoor is the most alarming example: an attacker spent about two years becoming a maintainer and nearly succeeded in introducing a backdoor into mainstream Linux distributions.
This article has three layers:
- Layer 1: The Intuition: A restaurant's ingredients come from hundreds of suppliers
- Layer 2: Real-World Use: event-stream, ua-parser-js, xz-utils, dependency confusion
- Layer 3: Principles and Engineering Key points
Layer 1: The Intuition
1.1 Hundreds of Suppliers
A restaurant's dishes rely on ingredients from hundreds of suppliers, and those suppliers have hundreds of suppliers of their own. If any single one of them adulterates the raw materials, every restaurant using that batch of materials will face trouble.
An ordinary JavaScript project has dozens of direct dependencies, which can expand to thousands of indirect dependencies. You may have never actually read the code for a single one of them.
1.2 Four Methods of Poisoning
| Method | Analogy |
|---|---|
| Typosquatting | Opening a shop called "Coca-Colla" |
| Account Takeover | Stealing the official seal of a legitimate supplier |
| Long-term Dormancy | Applying to work for a legitimate supplier, working for two years to become a manager, and then adulterating the raw materials |
| Dependency Confusion | Your company has an internal recipe called "Secret Sauce," but someone registers a product with the same name externally, and the procurement officer buys the wrong one |
Layer 2: Real-World Use
| Event | Method | Process |
|---|---|---|
| event-stream (2018) | Maintenance Handover | The original maintainer had no time to maintain the package and handed this npm package, which is downloaded millions of times weekly, to a stranger who offered to help. The new maintainer added a dependency containing malicious code specifically targeting a Bitcoin wallet application to steal users' wallets. |
| ua-parser-js (2021) | Account Takeover | The maintainer's npm account was compromised, and the attacker published versions containing a miner and a password stealer. |
| Dependency Confusion (2021) | Same-named Public Package | Security researchers discovered that many companies' build systems, when looking for internal packages, would prioritize downloading a higher version number package with the same name from public repositories. They used this (in a harmless way) to enter the internal networks of dozens of large companies, earning significant bug bounties. |
| xz-utils (2024, CVE-2024-3094) | Long-term Dormancy | A developer using a pseudonym spent about two years contributing code to this fundamental compression library to gain the trust of maintainers, eventually planting a complex backdoor targeting SSH within the release package (rather than the source repository). Microsoft engineer Andres Freund discovered it after noticing that SSH logins were about half a second slower and CPU usage was abnormal. By then, the backdoor had already entered the testing versions of several distributions. |
The xz incident exposed a structural problem in open-source infrastructure: a vast number of critical components are maintained by one or two exhausted volunteers.
Layer 3: Principles and Engineering
3.1 Installation is Execution
Many package managers allow packages to run scripts during installation (such as npm's postinstall or Python's setup.py). This means: npm install a certain package is equivalent to executing the code written by the author on your computer under your identity. You don't even need to actually call the package.
Countermeasure: Disable installation scripts in CI and in environments where they are not needed (such as npm ci --ignore-scripts), and only allow them for packages that strictly require them.
3.2 Lockfiles and Hashes
| Measure | Function |
|---|---|
Lockfiles (package-lock.json, poetry.lock, Cargo.lock) |
Fix the exact version of every dependency (including indirect dependencies) |
| Integrity Hash | The lockfile records the hash of each package; it is verified after downloading to prevent the same-version file in the repository from being replaced |
Use ci instead of install in CI |
Installs strictly according to the lockfile; errors out if the lockfile and manifest are inconsistent |
| Updates must be reviewed | Dependency updates should go through human-reviewed PRs to check the content of changes, rather than being merged automatically |
3.3 Preventing Dependency Confusion
- Internal packages should use organization scopes (such as
@company_name/package_name) and register that scope in public repositories; - Configure package managers: internal scopes should only be pulled from private repositories;
- Use a private mirror (proxy) through which all public packages pass, allowing for centralized auditing, caching, and blocking.
3.4 Identifying Suspicious Packages
| Signal | Description |
|---|---|
| Name is extremely similar to a famous package | typosquatting |
| Recently released, low download count, yet introduced by your dependency | Might be a newly planted malicious dependency |
| Recent change in maintainer | The lesson from event-stream |
| New version suddenly adds installation scripts, network requests, or obfuscated code | High risk |
| Published package content is inconsistent with the source repository | The xz backdoor existed only in the release package |
There are specialized tools and services (such as Socket, OpenSSF Scorecard, and various platform-specific dependency scanners) that can automatically check for these signals.
3.5 SBOM and Long-term Measures
- SBOM (Software Bill of Materials): Records all components and versions included in a product. When an event like xz occurs, it can answer "Are we affected?" within minutes;
- Build Provenance (such as SLSA, Sigstore): Proves that a release package was indeed produced by a specific source commit within a specific build system, preventing "inconsistency between release package and source code";
- Support key open-source maintainers: Fundamentally reduces the risk point of "exhausted independent maintainers."
Summary
| Layer | Key points |
|---|---|
| Risk | Thousands of dependencies; any single one being poisoned will cause a compromise |
| Methods | Typosquatting, Account Takeover, Long-term Dormancy, Dependency Confusion |
| Key Fact | Installation is execution |
| Countermeasures | Lockfiles + Hashes; Disable installation scripts; Scopes + Private mirrors; Review updates; SBOM and Provenance |