Malicious Package Detection: Spot Supply Chain Attacks
Last verified on Nov 8, 2026.
Your application is mostly other people's code. A typical install pulls in hundreds of packages, each with its own maintainers, publish credentials and install scripts. An attacker who takes over one maintainer account, publishes a lookalike package name or slips a change into a build can reach every project that installs the next version. MITRE ATT&CK describes this as manipulating software dependencies and development tools before a final consumer receives them, and names popular pip and npm packages as targets.
You cannot read every dependency. You can detect when something about one of them changes in a way that matters, and respond before the new version reaches production.
What the evidence looks like
Three kinds of evidence are available without special tooling.
Changes in your own dependency files. A lockfile diff shows exactly what changed in a build:
"left-pad-ish": { "version": "2.4.1" }
+ "left-pad-lsh": { "version": "1.0.0", "scripts": { "postinstall": "node setup.js" } }
- "json-utils": { "version": "3.1.0" }
+ "json-utils": { "version": "3.1.1" }This is an example. The new package has a name one letter away from a known one and an install script. The npm documentation lists preinstall, install and postinstall among the lifecycle scripts that run when a package is installed, so an install script is code that runs on your machines and CI runners the moment you install.
Advisories and malware reports. The OSV project provides an API: a query with a package name, ecosystem and version returns known vulnerabilities, and the OpenSSF malicious-packages repository holds reports of malicious packages in OSV format. The npm audit command reports known vulnerabilities, and GitHub's Dependabot raises alerts for vulnerable dependencies.
Behavior of builds. A CI job that suddenly connects to an unfamiliar host, reads environment variables it never read before or runs for much longer than usual after a dependency bump.
How to detect it step by step
1. Inventory what you ship. Parse lockfiles on every commit and store the full list of package, version and source for each service. Without it, advisories are unanswerable: "are we affected?" becomes a week of searching.
2. Diff dependencies on every pull request. Flag new top-level packages, new transitive packages, version jumps across several majors and changes of package source. Flag any package with an added or changed install script.
3. Check each change against known bad lists.
curl -s -X POST https://api.osv.dev/v1/query \
-d '{"package":{"name":"json-utils","ecosystem":"npm"},"version":"3.1.1"}'An empty answer does not prove safety, but a hit is an immediate stop.
4. Look at the package, not only the advisory. Useful signals are a version published hours ago, a new maintainer, a repository link that does not match, a name close to a popular package and the absence of provenance. The npm registry supports provenance statements, which publicly link a package to its source code and build instructions, so a popular package that suddenly publishes without one stands out.
5. Watch the build environment. Log outbound connections from CI and build hosts, with the destination and the process. A new destination during install is the clearest sign something is running that you did not write. Consider disabling install scripts for builds that do not need them.
6. Delay adoption of brand new versions. Require a minimum age, such as a few days, before automated updates are merged. Most malicious versions are found and pulled quickly, so waiting removes a large share of the risk at the cost of a short delay on real fixes.
Thresholds and pitfalls
There is no single number. Rank findings: a hit in a malware report or a new install script from an unfamiliar package is a block. A moderate vulnerability in a package that is only used in tests is a ticket.
Common problems:
- Advisory is not malice. Vulnerability feeds report accidental flaws. A malicious package is a different and rarer case that needs its own list.
- Alert fatigue. Hundreds of low findings train people to ignore the one that matters. Prioritize by reachability and by whether the package runs in production.
- Feed lag. A new attack is not in any list on the first day. The behavioral checks above work when the lists do not.
- Transitive depth. You may never have chosen the package. Keep the dependency path in each finding.
- Legitimate ownership changes. Maintainers do hand projects over. Treat a change as a reason to look, not as proof.
What to do when it fires
If a malicious version was installed, assume anything the build or host could read is exposed: tokens, cloud credentials and signing keys. Rotate them, pin or remove the version and rebuild from a clean state. Use your inventory to list every service and image that contains it, and your outbound logs to see whether anything connected where it should not have.
If it is only suspicious, hold the update and read the diff between versions. Related: risky code covers flaws in your own commits, new advisories covers published vulnerabilities, exposure covers what is reachable and security issues is the broader group. See the GitHub integration for repository events and AWS for build-host network logs.
Frequently Asked Questions
How do I detect a malicious npm or PyPI package?
Diff your lockfile on every pull request, flag new packages and new install scripts, check versions against OSV and malware report lists, and watch outbound connections from build hosts.
What is typosquatting?
It is publishing a package whose name is a small misspelling of a popular one so that people install it by mistake. Name similarity to popular packages is a useful flag in dependency diffs.
Do vulnerability scanners find malicious packages?
Mostly not. Vulnerability feeds report accidental flaws. Malicious package reports are a separate list, and new attacks may not be in any list on the first day.
Should I delay new package versions?
A short minimum age, such as a few days, before automated updates merge removes much of the exposure to freshly published malicious versions. Allow urgent security fixes through manually.
Sources
- Compromise Software Dependencies and Development Tools: adversaries manipulate dependencies and tools before receipt by a final consumer; pip and NPM packages as targets (accessed Nov 8, 2026)
- npm lifecycle scripts include preinstall, install and postinstall that run during install (accessed Nov 8, 2026)
- OSV query API takes a package name, ecosystem and version (accessed Nov 8, 2026)
- Repository of reports of malicious packages in the OSV format (accessed Nov 8, 2026)
- npm audit reports known vulnerabilities in dependencies (accessed Nov 8, 2026)
- Dependabot alerts for vulnerable dependencies (accessed Nov 8, 2026)
- npm provenance publicly links a package to its source code and build instructions (accessed Nov 8, 2026)