Malicious packages, how they work and how to catch them
Malicious packages don't have bugs, they are the attack. How they get into projects, why install scripts matter, how to check your lockfile against known malicious versions, and what to do if you find one.
Oct 30, 2026 · 3 min read
A vulnerable package is a mistake in otherwise honest code. A malicious package is code written to attack the machines that install it. It steals tokens, cloud credentials and crypto wallets, or opens a backdoor. The response is different too. You don't wait for a patch. You remove it and assume the machine is compromised.
How malicious packages get in
- Typosquatting. A package named one keystroke away from a popular one, waiting for a typo in
npm installorpip install. - Compromised maintainer accounts. An attacker phishes or takes over a real maintainer's account and publishes a poisoned version of a legitimate, widely used package. Every project that updates within the window installs it. This is how the event-stream (2018) and ua-parser-js (2021) incidents happened, and how the chalk and debug compromise and the self-spreading "Shai-Hulud" worm hit npm in 2025.
- Dependency confusion. A public package published under the name of a company's internal package, in the hope that a build resolves the public one first.
- Plain fakes. Packages that promise something useful and ship a payload, often promoted in tutorials, issues or AI-generated code suggestions.
Why install scripts matter
npm runs preinstall, install and postinstall scripts from every package in the tree, including transitive ones. Python packages built from source run code at build time. In both cases the malicious code runs the moment you install, before your application starts and before most security tools look at anything.
For npm you can stop lifecycle scripts from running:
npm ci --ignore-scripts
or make it the default for a machine or a project:
npm config set ignore-scripts true
Some legitimate packages need their install scripts, such as those with native modules. So this works best in CI, combined with an explicit allowlist, rather than as a blanket rule on every laptop.
Check a lockfile against known malicious versions
When a malicious package is found, it is reported to the registry and catalogued. The OpenSSF Malicious Packages project publishes these reports in the OSV format, with IDs starting MAL-, and they are searchable through the OSV API like any other advisory.
To check one package version:
curl -s -d '{"package": {"ecosystem": "npm", "name": "ua-parser-js"}, "version": "0.7.29"}' \
https://api.osv.dev/v1/query | jq '.vulns[]? | {id, summary}'
To check a whole project, osv-scanner reads lockfiles directly:
osv-scanner scan source -r .
An advisory-based check like this catches known malicious versions after they have been reported. That is usually hours to days after publication. Tools that analyse package code at install time, such as registry firewalls, can catch some attacks earlier. The two approaches complement each other.
If you find one
Treat it as an incident, not a dependency update:
- Remove it. Delete the package from your manifest and lockfile and reinstall. If it is transitive, find what pulls it in (
npm ls <name>,pip show, oruv tree --invert --package <name>) and pin or replace the parent. - Assume every machine that installed it is compromised. That includes developer laptops, CI runners and build servers.
- Rotate what those machines could reach: npm and PyPI tokens, GitHub tokens and SSH keys, cloud credentials, and any secrets in environment variables or
.envfiles. - Look for persistence and spread. Some payloads publish new versions of other packages the stolen token can reach, or plant code in other repositories the same account can push to.
- Check when it arrived. Your lockfile history tells you which commit introduced it, and therefore which builds ran it.
Malware that isn't in a package
Some campaigns skip the registry and put the payload straight into a repository: a modified build config, a VS Code tasks.json that runs on folder open, or a fake font file. Checking dependencies alone won't find these. Lockhound checks the files these attacks are known to hide in, on every scan and pull request, alongside known malicious package versions from the OpenSSF database.
Read next
- Which vulnerable dependencies to fix first, using CISA KEV and EPSSCVSS tells you how bad a vulnerability could be, not whether anyone is exploiting it. Here is how to add CISA's Known Exploited Vulnerabilities catalog and FIRST's EPSS scores to your triage, with commands you can run today.
- Know when a malicious package is installed anywhere you workLockhound flags known malicious package versions from the OpenSSF database across npm, PyPI, Composer, RubyGems, Go, Cargo and Maven, and checks your repositories for malware committed into build files and editor tasks.