Key Takeaways
- Typos and AI hallucinations both hand attackers a usable package name, and neither one needs an exploit.
- Provenance proves where an artifact came from, not that the commit or account behind it was legitimate.
- Paying attention doesn’t scale, so push the work into pinned dependencies, deterministic routing, and script allowlists.
Have you ever thought about the length of the chain of people you are trusting every time you install a package? The person who wrote the code. The person who reviewed the code. The registry that hosts it. The CI runner that pulls it at 3 a.m. with nobody watching. Attackers are good at sniffing around package ecosystems, and they have gotten very good at finding the weakest link in that chain. Installing code faster than anyone can review it brings on a slew of issues.
Most software supply chain attacks work that way. They don’t need to break through a perimeter; they use a trusted path exactly as it was built to be used.
Here’s a quick tour of the ways that trust gets abused, and what actually helps.
Typos in the Packages
The oldest trick that doesn’t need an exploit. It just needs you to type fast and not look closely. An attacker publishes a package off by a letter or a hyphen and waits for somebody to install it by mistake.
The crossenv package is a well-known example. In July 2017, an npm user going by hacktask published around 40 lookalike packages; the standout was cross-env with the hyphen dropped. Miss that one character, and what you installed read your environment variables and posted them to a server the attacker controlled.
{
"name": "crossenv",
"scripts": {
"postinstall": "node package-setup.js"
}
}
npm’s incident report names the payload script package-setup.js. Simplified, it did this:
// Simplified illustration of what the script did.
// Reads the environment variables available to the
// install, then sends them to the attacker's server.
const vars = process.env;
sendToAttackerServer(vars);
On a developer machine or CI runner, those variables often hold registry tokens, cloud credentials, and database passwords, which is why they are worth taking. npm logged 679 downloads for crossenv but estimated no more than about 50 were real installations rather than mirrors.
The reason typosquatting works is that the fix isn’t a patch. The actual “fix” is paying attention, and we all know paying attention doesn’t scale. So move the work somewhere that does: pin dependencies, review lockfile changes as carefully as code changes, and resolve from an approved internal source.
Squatting with AI (Slopsquatting)
Slopsquatting is the same trick running on a different mistake. Instead of your typo, it relies on a coding assistant recommending a package that doesn’t exist, which hands an attacker the chance to register that name before a real project does.
In the USENIX Security 2025 study of package hallucinations, 16 models generated 576,000 code samples referencing 2.23 million packages. Across those tests, 19.7% of the references, 440,445 of them, named packages that do not exist, covering 205,474 unique invented names. When the researchers re-ran prompts that had already produced a hallucination, 43% of the invented names came back in all ten runs, which is what can make pre-registering them worthwhile.
The huggingface-cli case is a good example. Security researcher Bar Lanyado at Lasso Security noticed models recommending that name even though no such package existed on PyPI, so in December 2023 he registered an empty one. It collected more than 30,000 genuine downloads over three months, and the wrong install command turned up in the README of a public Alibaba research repo.
# What an AI assistant might confidently suggest
pip install huggingface-cli
# What actually installs the official Hugging Face CLI
pip install -U "huggingface_hub[cli]"
The second installs the official huggingface_hub package with its CLI extra. The first installs whatever distribution happens to be registered under a name a model invented. Lanyado’s was harmless on purpose. Somebody else’s would not have to be. So confirm the project exists, check the publisher and release history, and get it approved before it reaches a lockfile.
Dependency Confusion
Dependency confusion is a resolution failure. It shows up wherever a company runs private packages alongside public ones and the build can satisfy one name from more than one place.
The details differ by ecosystem. pip is the clearest case: give it an extra index with --extra-index-url and it considers candidates from every configured index, then picks the best match across all of them, which can mean a higher-versioned public package wins. npm resolves by scope and configured registry rather than merging feeds, so the exposure there usually comes from an unscoped internal name, or a proxy that blends public and private into one view with no namespace or priority policy behind it.
Alex Birsan demonstrated the problem in 2020 and 2021 after finding internal package names leaking into public repos and error messages. He published harmless packages under those names to npm, PyPI, and RubyGems with very high version numbers, and watched them get pulled into internal builds at more than 35 companies, including Apple, Microsoft, and PayPal, earning over $130,000 in bug bounties.
{
"name": "internal-auth-tool",
"version": "99.0.0"
}
The dangerous part isn’t the JSON. It’s a build whose configuration never binds that name to one trusted source. For npm, scope internal packages and pin the scope to the private registry.
# .npmrc
registry=https://registry.npmjs.org/
@your-org:registry=https://your-private-registry.example.com/
That pattern is npm-specific, and it holds only while registry routing, namespace ownership, and publishing permissions line up. Ecosystems without scopes need their own source pinning; for pip, that means not treating --extra-index-url as a private-first fallback.
Hijacked Maintainers
The smoke and mirrors vanish with this one. An attacker takes over the account or release path behind a package you already trust and ships a malicious update through the project’s normal release infrastructure. A trust exploit, not a code exploit.
On August 4, 2026, that is what happened to keyv and the rest of its caching family. Attackers compromised the maintainer’s GitHub account, pushed malicious commits to the main branch, and let the project’s own Actions workflow build and publish it. The worm, nicknamed ChainDrop, collected whatever credentials the compromised environments exposed, then used stolen npm tokens to republish itself into other maintainers’ packages. StepSecurity counted 444 packages and 2,212 poisoned versions within four hours; others logged higher totals later.
The poisoned releases carried valid provenance, and that was not a provenance failure. It attested, correctly, that the artifact was built by the project’s own workflow from its declared inputs. What it cannot tell you is whether the commit going in was legitimate, or whether the account behind it still belonged to its owner.
Many teams never had to choose keyv at all. flat-cache and file-entry-cache sit under ESLint, and keyv sits under those.
Preinstall and Postinstall Scripts
Install scripts are less a separate attack category than the execution path several of the others use. A package can declare preinstall, install, or postinstall work, and historically that ran during installation, before anyone read a line of it.
That timing is valuable. The script runs with the privileges of the developer or CI runner while the install looks routine. ChainDrop used a malicious install hook the same way.
The 2021 ua-parser-js compromise is the standard example. After the maintainer’s npm account was taken over, three malicious versions shipped with a preinstall hook that dropped a cryptocurrency miner on Linux and, on Windows, the miner plus credential-stealing malware. They were pulled within about four hours, fast by incident-response standards. The package was doing millions of downloads a week, so automated builds fetched them without anyone typing its name.
{
"scripts": {
"preinstall": "node setup.js"
}
}
The defaults are finally moving. pnpm has not run dependency build scripts without explicit approval since v10, with current releases recording those decisions in an allowBuilds map, and npm 12 blocks dependency install scripts not covered by allowScripts. Know how that default behaves. The script is skipped with a warning and the install still succeeds, so a native dependency can land without its binary ever being built. Once you approve the package, npm rebuild runs the skipped install work, and in CI --strict-allow-scripts turns an unreviewed script into a failed install rather than a quietly half-built one.
Older npm releases are not defenseless. They just default the other way.
npm install --ignore-scripts
That one is blunt. It breaks packages that legitimately compile at install time, and does nothing about malicious code that waits until your application imports it. Treat an explicit allowlist as the real answer.
Why This All Keeps Working
None of these attacks are solved by a single person or a single tool. They get harder when a team takes ambiguity out of the process: scoped private packages, deterministic registry routing, frozen installs from a reviewed lockfile, install scripts permitted by exception instead of by default, and AI dependency suggestions treated as unverified until somebody checks them.
But expecting every engineer to catch every bad package on every install is not a security policy. The package still has to come from somewhere, and that somewhere is part of your security boundary.
That is the thing that SecureChain is built for. Instead of pulling artifacts straight from a public registry, you point your package manager at a curated source where packages are rebuilt from verified source in isolated environments, screened for malware, and patched as new CVEs land, with signed builds and provenance for audit. It covers JavaScript, Python, Java, Go, Rust, and PHP, and developers stay on the package manager they know.
When the problem is an unmaintained package rather than a malicious one, Endless Lifecycle Support (ELS) backports security fixes to the versions you already run, so patches keep arriving after upstream support ends.
My Final Thoughts
Trust is a pillar in modern software development, but what gets exploited is blind trust. Attackers are looking for ways in through typos, AI, hijacked accounts, or the assumption that a familiar package is still the package you thought it was. We can’t control everything that happens on the open web.
One thing you can control is what ends up in your environment. Understand how these attacks work, lock down your build pipeline, manage your registry and script permissions, and put a vetted source between the public registries and production. The code you ship should be the code you meant to run.
