{"slug": "checking-whether-an-npm-package-exists-stopped-working", "title": "Checking whether an npm package \"exists\" stopped working", "summary": "A developer found that npm package existence checks are no longer effective against AI-hallucinated package names, which are being registered by attackers in a practice called 'slopsquatting'. The developer built a tool called 'vetdeps' that uses reputation-based checks and measured rules against real packages to achieve zero false positives. The tool also detected that 'unused-imports' and 'types-node' were confirmed malware.", "body_md": "Every npm supply chain postmortem ends with the same advice: verify the package before you install it.\n\nSo I looked at what the tools that do this actually check. Most of them check whether the package exists.\n\nThat check is dead. Here are three package names documented as AI hallucinations, against the npm registry right now:\n\n```\nreact-codeshift   HTTP 200\nreact-fetch-hook  HTTP 200\nunused-imports    HTTP 200\n```\n\nAll three exist. Somebody registered them. Every guard built on an existence check reports them **safe**.\n\nAI coding tools suggest package names that do not exist about **19.7%** of the time, measured across a 576,000 sample study. More usefully for an attacker, **43%** of those hallucinated names repeat across identical prompts.\n\nThat is the whole attack. The hallucinations are predictable, so you register the common ones and wait for someone to paste an install command. It has a name now: slopsquatting.\n\nAnd in 2026 the thing typing `npm install`\n\nis often an agent that never pauses to sanity check a name at all.\n\nNot existence. Reputation.\n\n| Package | First published | Downloads/week |\n|---|---|---|\n`react-codeshift` |\n2026-01-14 | 1 |\n`unused-imports` |\n2025-10-27 | 201 |\n`eslint-plugin-unused-imports` |\n2019-09-18 | 7,780,771 |\n`react-fetch-hook` |\n2018-12-18 | 15,329 |\n\nLook at that last row, because it is the hard part.\n\n`react-fetch-hook`\n\nsounds completely invented. It is seven years old, has 15,000 weekly downloads, and is entirely legitimate. Any rule of the form \"new or odd-sounding name equals bad\" flags it immediately.\n\nAnd a security tool that flags a legitimate package gets uninstalled the same day. False positives are not a tuning problem, they are the product failing.\n\nThis surprised me most.\n\n`unused-imports`\n\nand `eslint-plugin-unused-imports`\n\nare **fifteen edits apart**.\n\nEvery typosquat checker built on Levenshtein distance sails straight past the single most common real case. The rule that actually catches it is different:\n\nDoes the candidate, plus a known ecosystem prefix, equal a popular package?\n\n`unused-imports`\n\n+ `eslint-plugin-`\n\n= a package with 7.8 million weekly downloads. That is exactly the shape an LLM produces when it drops the ecosystem prefix, which it does constantly.\n\nI measured every rule against real packages before shipping. Two of them were wrong, and I would never have reasoned my way to either.\n\n**A fixed edit distance of 2 flagged znv as a typo of ajv.** Both real, both legitimate. Short names sit naturally close to each other, so a fixed threshold is meaningless on them. Allowed distance now scales with name length: under 5 characters requires an exact match.\n\n**Affix rules that stripped a suffix flagged chalk-cli as chalk.**\n\n`chalk-cli`\n\nis Sindre Sorhus's official CLI, published 2015. It also flagged `vite-plugin-vue`\n\nas `vue`\n\n. Of course it did: `<tool>-plugin-<x>`\n\nand `<tool>-cli`\n\nare just how the ecosystem names things. Affix rules now only The lesson generalises: if you write detection rules and do not measure them against real, legitimate packages, you are not building a security tool. You are building a noise generator.\n\nFinal measurement on a real 550 package project: **0 false positives**.\n\nWhile I was building this, `unused-imports`\n\ngraduated from *suspicious name* to **confirmed malware**. It now carries OSV advisory [ MAL-2025-48781](https://osv.dev/vulnerability/MAL-2025-48781).\n\nSo did `types-node`\n\n, with [ MAL-2024-12159](https://osv.dev/vulnerability/MAL-2024-12159).\n\nThe two names I had picked as *typosquat examples* turned out to be real, confirmed attacks.\n\n```\nnpx vetdeps\nnpx vetdeps unused-imports     # check one package before you add it\nnpx vetdeps init               # gate every npm install in this project\nnpx vetdeps init --agent       # stop your AI agent installing a bad package\nnpx vetdeps --ci               # fail the build on a critical finding\n```\n\nWhat a hit looks like:\n\n```\n  critical  @ctrl/tinycolor@4.1.1\n            known malicious package: MAL-2025-47141\n\n  1 critical, 0 warnings, 0 ok\n```\n\nTwelve packages already occupy this niche. The most successful has **202 weekly downloads**. Not one broke through.\n\nThey are all scanners you have to remember to run. That is a losing bet, because the habit that already exists always beats the habit you are asking someone to form. And the installer in 2026 is frequently not a human at all.\n\nSo `vetdeps`\n\ninstalls into the places where installs actually happen: a `preinstall`\n\ngate that fires on every `npm install`\n\n, and a Claude Code / Cursor hook that blocks an agent before the install runs.\n\n```\nnpx vetdeps init --agent\nBlocked by vetdeps. Do not install this.\n\n  critical  unused-imports\n            known malicious package: MAL-2025-48781\n\nCheck the package name against the real one before trying again.\n```\n\n`registry.npmjs.org`\n\nis skipped and never sent to a public API.Overselling a security tool is worse than not shipping one.\n\n`MAL-`\n\nis the only machine readable malware signal`event-stream`\n\nis the worked example.`npm audit`\n\n. This answers a different question: is this the package you meant?\n\n```\nnpx vetdeps\n```\n\n[github.com/Tisankan-dev/vetdeps](https://github.com/Tisankan-dev/vetdeps)\n\nIf it flags something legitimate in your project, I genuinely want to know. There is an issue template for exactly that, and I treat those as defects rather than tuning requests.", "url": "https://wpnews.pro/news/checking-whether-an-npm-package-exists-stopped-working", "canonical_source": "https://dev.to/tisankan/checking-whether-an-npm-package-exists-stopped-working-2j0e", "published_at": "2026-08-22 08:17:00+00:00", "updated_at": "2026-08-22 08:43:49.952004+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "developer-tools", "ai-agents"], "entities": ["npm", "vetdeps", "react-codeshift", "react-fetch-hook", "unused-imports", "eslint-plugin-unused-imports", "Sindre Sorhus", "OSV"], "alternates": {"html": "https://wpnews.pro/news/checking-whether-an-npm-package-exists-stopped-working", "markdown": "https://wpnews.pro/news/checking-whether-an-npm-package-exists-stopped-working.md", "text": "https://wpnews.pro/news/checking-whether-an-npm-package-exists-stopped-working.txt", "jsonld": "https://wpnews.pro/news/checking-whether-an-npm-package-exists-stopped-working.jsonld"}}