A malicious package is a dependency that does the job you installed it for while quietly doing something harmful as well, such as reading your secrets and sending them to an attacker. It is dangerous because it doesn't look dangerous: it has a sensible name, a nice description, plenty of downloads and an install that finishes without a single red error.
Why packages are a way in
Modern apps are mostly other people's code. A web app might list twenty packages in its package.json, and those pull in hundreds more. Registries such as npm and PyPI let anyone publish, which is what makes them so useful, and also what makes them a target.
Installing a package is an act of trust. Its code runs on your machine, or in your CI pipeline, with the same access you have: your files, your environment variables, your network. A malicious package doesn't need to break in. You invite it in through the front door.
This is a kind of supply chain attack: instead of attacking your app directly, the attacker poisons something your app is built from.
How malicious packages reach you
There are two common routes.
Typosquats: a name that looks almost right
An attacker publishes a package whose name is one slip away from a popular one: a doubled letter, two letters swapped, a hyphen instead of nothing. One typo in an install command and you get theirs:
npm install expresss # one 's' too manyThe fake often works, because it simply copies or wraps the real package. Your app runs, your tests pass, and nothing looks wrong. The harmful part runs alongside.
Takeovers: a real package turned bad
The more dangerous route is a real, trusted package that attackers take control of. They might steal a maintainer's login or publishing token, or break into the project's build pipeline, then publish a new version with malware inside. Everyone who installs or updates to that version gets it, and every usual signal of trust, from the name to the download count, is genuine.
The Shai-Hulud attack on TanStack in May 2026 is a recent example. Attackers got into TanStack's release pipeline on GitHub Actions and published malicious versions of dozens of its npm packages, including @tanstack/react-router, one of the most downloaded routing libraries for React. The malware looked for GitHub secrets, npm tokens, cloud credentials and SSH keys on any machine or CI runner that installed it. Shai-Hulud is a worm: stolen publishing tokens let it push infected versions of yet more packages, so it spreads from one project to the next.
What the hidden code can do
The most common trick is an install script. npm runs scripts named preinstall, install and postinstall automatically when a package is installed. A malicious package only needs one line in its own package.json:
{
"name": "nice-date-helper",
"scripts": {
"postinstall": "node setup.js"
}
}setup.js runs the moment you install the package, before you import anything from it. It can do what any Node.js script can do:
- Read env files. A
.envfile in your project often holds API keys and database passwords. - Steal tokens. Environment variables in CI hold publishing tokens and cloud credentials.
- Phone home. Send whatever it finds to a server the attacker controls.
- Spread. Use a stolen npm token to publish poisoned versions of your own packages.
Not every malicious package uses install scripts. Some hide the harmful code in the library itself, so it runs the first time your app calls it.
Protecting yourself
You can't read every line of every dependency by paw. No single step stops everything, so use several layers.
Before you install
- Check the exact name. Copy it from the project's official docs rather than typing it from memory.
- Look before you add. A brand-new package with one maintainer, no linked repository and a suspiciously high download count deserves a closer look.
- Ask whether you need it. Every dependency is more code you are trusting. Ten lines you write yourself can beat a package you'd have to vet. More on the cost of a big dependency tree in dependency hell.
When you install
- Commit your lockfile and install from it. A lockfile pins every package to an exact version and checksum. In CI,
npm ciinstalls exactly what the lockfile says, so a newly poisoned version can't sneak in unless you update to it. - Don't run install scripts you haven't approved. pnpm doesn't run dependency build scripts by default; you approve the packages that genuinely need them. With npm you can turn them off:
npm install --ignore-scripts- Wait before taking brand-new versions. Poisoned releases are usually spotted and pulled within hours or days. Holding back versions published in the last day or two avoids most of that window.
Let a tool watch the door
Malware is published faster than people can check it, so security tools help. Aikido's device protection, for example, watches what gets installed on a developer's machine, from npm packages to apps and browser extensions, and blocks anything known to be malicious before it installs. It can also refuse packages published only in the last 48 hours. Tools like this catch threats an advisory database hasn't listed yet, which is why they fit alongside, not instead of, the steps above.
Building these checks into your pipeline is a core idea of DevSecOps.
If you installed something bad
Treat the machine as compromised. Remove the package, then rotate every secret it could have reached: API keys in .env files, cloud credentials, npm and GitHub tokens, SSH keys. Deleting the package doesn't un-steal anything, so changing the credentials is what actually protects you. Check your accounts' logs for activity you don't recognise.
Common mistakes
- Trusting download counts and stars. A taken-over package has real numbers, and fakes can inflate theirs.
- Thinking 'the install worked, so it's fine'. Malicious packages are built to install cleanly.
- Waving through lockfile changes in review. A new or bumped dependency is new code in your app.
- Keeping long-lived secrets on developer machines. The fewer powerful tokens lying around, the less a malicious package can steal.
Key takeaways
- A malicious package looks helpful but runs harmful code, often the moment it is installed.
- It can read env files, steal tokens and send them to an attacker.
- Typosquats use names that look almost right; takeovers turn real, trusted packages bad, as in the Shai-Hulud attack on TanStack.
- Lock your dependencies, block unapproved install scripts and let a security tool block known-bad installs.
- If you installed one, rotate every secret it could have reached.