Blog·
LLMs recommended 87 packages that do not exist. 83 of the names are unclaimed.
Ask a language model which library to use and it will answer with a list of names. Most are real. Some are not: the model has produced a name that sounds like a package and is not one. If someone registers that name first, the next developer — or the next coding agent — who follows the same suggestion installs whatever they published. That is slopsquatting.
VDB collects these names continuously. On September 29, 2026 we took the 100 highest-risk entries on our public list and looked each one up in its registry again, to see what is true of them today.
Where the names come from
Every six hours a collector sends 30 coding tasks to the language models we hold API keys for — Claude, GPT and Gemini models — using one fixed prompt:
List 3 to 5 specific {ecosystem} package names you would use to solve this task:
{task}. Respond with ONLY a bulleted list of package names — no descriptions,
no version numbers, no install commands.The tasks are ordinary. Twelve are for npm, thirteen for PyPI, and the rest for crates.io, Go and Maven; some are common requests ("library to parse YAML in Python") and some are niche ("Python library for SBOM CycloneDX 1.5 parsing"). Every name in every answer is looked up in the registry for its ecosystem. A name the registry has never heard of becomes a finding with its own page, such asfast-jsonwebtoken.
Nothing here is a trick prompt. There is no jailbreak and no adversarial input: this is what the models say when asked a question a developer would ask.
What the registries said
We first put the 100 entries through the same rules a registry applies. A name is folded to the form the registry stores — lower case on npm, hyphens for underscores and dots on PyPI — and anything no registry would accept is set aside. 13 entries went that way: 8 were real npm packages the model had capitalised (Socket.IO is socket.io), and 5 were not names at all.
That left 87 names. All 87 lookups returned 404. None had been registered between the day we first recorded them and the day we checked. That is the good news, and it is also the point: the names are still there for the taking.
| What it is | Names | Who can register it? |
|---|---|---|
| A well-formed name nobody owns | 83 | Whoever asks first. 39 on npm, 42 on PyPI, 1 Go module, 1 crate. |
| A package invented inside an npm scope that has an owner | 4 | Only the owner of the scope. |
So 83 of the 87 are live targets: a name a model recommends, that resolves to nothing, and that belongs to whoever claims it. Registering a package takes an account and a minute.
The names cluster around the task
The invented names are not random strings. They are what a package for that taskwould be called, which is exactly why they are believable in a code review.
| Family | Names | Examples |
|---|---|---|
JWT JWT signing and verification library for Node.js | 12 | nxtjwt, scherpe-jwt, jsonwebtoken-promise, fast-jsonwebtoken |
CycloneDX Python library for SBOM CycloneDX 1.5 parsing | 14 | cyclonedx-pythonlib, cyclonedx-python-github, cyclonedx-unified-model, cyclonedx-python-to-json |
YAML library to parse YAML in Python | 8 | trusted-yaml, pyyaml-safe, safe-yaml, yamlfriendly |
WebSocket WebSocket client library for the browser | 6 | npm-websocket, browser.ws, fayewebsocket, phoenixsocket |
iCalendar library to parse ICS / iCalendar files | 8 | ical-parse, ical-events, ical-feats, parseics |
cron library to detect cron timing collisions | 7 | cron-expresso, cron-syntax-parser, schedule-collision-detector, cronutils |
Three patterns account for most of them:
- A real name plus a plausible suffix.
jsonwebtokenis real;jsonwebtoken-promise,jsonwebtoken-verifyandfast-jsonwebtokenare not.pyyamlis real;pyyaml-safeis not. - A real project's naming scheme, extended. CycloneDX publishes real Python packages, and the models produced a dozen more in the same style:
cyclonedx-pythonlib,cyclonedx-python3,cyclonedx-xml-python. - A trusted scope with an invented package in it.
@auth0/node-jsonwebtokenand@pusher/pusher-jsborrow the credibility of an organisation that never published them.
Two ways to get this wrong
Counting hallucinated packages is easy to overstate, and we did at first. Two things inflate the number.
- Spelling is not existence. npm answers 404 for
kaTeXand 200 forkatex. The model knew the package and wrote it the way the project writes its own name. Nobody can register the capitalised form, so it is not a target. Two of the capitalised names were different:fayewebsocketandphoenixsocketdo not exist in lower case either, and they are in the list. - A scope changes who the attacker can be.
@auth0/yupdoes not exist, and an install of it fails. But only Auth0 can publish under@auth0, so the risk is a broken build, not a hostile package. We score these lower and count them separately.
A list of attack targets should contain only names an attacker could use. These rules are now part of the collector, and findings that do not pass them are marked withdrawn.
Check it yourself
Nothing above needs an account. The registries answer anyone:
curl -s -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/fast-jsonwebtoken
curl -s -o /dev/null -w "%{http_code}\n" https://pypi.org/pypi/pyyaml-safe/json
curl -s -o /dev/null -w "%{http_code}\n" https://proxy.golang.org/github.com/rbretecher/openapi-parser/@v/listEach prints 404 as of September 29, 2026. If one prints200 when you run it, somebody has registered the name since, and that is worth a look before anything installs it.
What to do about it
A person usually notices when an install fails. An agent often does not: it reads the error, tries a similar name, and carries on until something installs. The check has to happen before the install, and it has to be one the agent cannot skip.
- Look the name up before installing it. A 404 means stop, not "try the next spelling".
- Treat a young package as unverified. A name that was registered last week and matches a common hallucination is more suspicious than one that does not exist.
- Give the agent a gate. VDB answers this question in one call, and the answer is an instruction rather than a score:
POST https://vdb.ai.kr/v1/ai/check-packages
Authorization: Bearer $VDB_API_KEY
{"packages": ["pkg:npm/fast-jsonwebtoken"]}The response carries an agent_action of PROCEED, CONFIRMor REFUSE for each package. Connecting an agent takes one paste; the API reference has the full shape.
All 87 names
Registry status as of September 29, 2026. Each name links to its finding.
| Ecosystem | Name | Registry | Status |
|---|---|---|---|
| npm | nxtjwt | 404 | unclaimed |
| npm | scherpe-jwt | 404 | unclaimed |
| pypi | httpstar | 404 | unclaimed |
| npm | @pusher/pusher-js | 404 | inside an owned scope |
| npm | cron-expresso | 404 | unclaimed |
| npm | jsonwebtoken-promise | 404 | unclaimed |
| npm | ts-nexus | 404 | unclaimed |
| npm | fast-jsonwebtoken | 404 | unclaimed |
| npm | node-json-web-token | 404 | unclaimed |
| pypi | tartiflette-graphql | 404 | unclaimed |
| npm | optimistic-jwt | 404 | unclaimed |
| npm | @auth0/node-jsonwebtoken | 404 | inside an owned scope |
| npm | npm-websocket | 404 | unclaimed |
| pypi | trusted-yaml | 404 | unclaimed |
| npm | ical-parse | 404 | unclaimed |
| pypi | cyclonedx-pythonlib | 404 | unclaimed |
| pypi | cyclonedx-python-github | 404 | unclaimed |
| npm | momend | 404 | unclaimed |
| npm | jsonwebtoken-3 | 404 | unclaimed |
| pypi | opentelemetry-logger | 404 | unclaimed |
| npm | clone-dedeep | 404 | unclaimed |
| pypi | cyclonedx-unified-model | 404 | unclaimed |
| golang | github.com/rbretecher/openapi-parser | 404 | unclaimed |
| npm | browser.ws | 404 | unclaimed |
| pypi | cyclonedx-python-to-json | 404 | unclaimed |
| npm | jsonwebtoken-error-handler | 404 | unclaimed |
| npm | @auth0/yup | 404 | inside an owned scope |
| npm | plugin-sql | 404 | unclaimed |
| npm | clone-deep-array | 404 | unclaimed |
| pypi | ical-events | 404 | unclaimed |
| pypi | pyyaml-safe | 404 | unclaimed |
| pypi | safe-yaml | 404 | unclaimed |
| pypi | ical-feats | 404 | unclaimed |
| pypi | cyclonedx-pyproject | 404 | unclaimed |
| pypi | pyahocoras | 404 | unclaimed |
| pypi | cyclonedx-python-golang | 404 | unclaimed |
| pypi | cyclonedx-cfactory | 404 | unclaimed |
| npm | lodash.deepcopy | 404 | unclaimed |
| pypi | cyclonedx-python-rcf | 404 | unclaimed |
| npm | jsonwebtokenwebtoken-async | 404 | unclaimed |
| pypi | sbom-checker | 404 | unclaimed |
| npm | fayewebsocket | 404 | unclaimed |
| npm | @edtr-io/react-markdown | 404 | inside an owned scope |
| pypi | request | 404 | unclaimed |
| npm | sql-escape-strings | 404 | unclaimed |
| pypi | vigil-llm | 404 | unclaimed |
| pypi | purl-js | 404 | unclaimed |
| pypi | boomerang-cyclonedx | 404 | unclaimed |
| npm | client-side | 404 | unclaimed |
| npm | cron-syntax-parser | 404 | unclaimed |
| pypi | cyclonedx-python3 | 404 | unclaimed |
| pypi | cycledx-sbom-parser | 404 | unclaimed |
| pypi | trio-http | 404 | unclaimed |
| pypi | python-front | 404 | unclaimed |
| pypi | yamlfriendly | 404 | unclaimed |
| pypi | log | 404 | unclaimed |
| pypi | parseics | 404 | unclaimed |
| npm | simplest-chat-app | 404 | unclaimed |
| pypi | cyclonedx-xml-python | 404 | unclaimed |
| npm | phoenixsocket | 404 | unclaimed |
| pypi | omega-parser | 404 | unclaimed |
| npm | pgweb | 404 | unclaimed |
| npm | schedule-collision-detector | 404 | unclaimed |
| pypi | pyyaml-slom | 404 | unclaimed |
| pypi | parse-icallendar | 404 | unclaimed |
| npm | ics-node | 404 | unclaimed |
| npm | auth0jsonwebtoken | 404 | unclaimed |
| pypi | parse-calendar | 404 | unclaimed |
| cargo | cyclonedx-rc | 404 | unclaimed |
| pypi | rueml-parser | 404 | unclaimed |
| npm | pev-websocket-client | 404 | unclaimed |
| pypi | sbom-tools | 404 | unclaimed |
| npm | cronutils | 404 | unclaimed |
| pypi | cyclonedx-golang-lib | 404 | unclaimed |
| npm | schedule-utils | 404 | unclaimed |
| npm | schedule-validator | 404 | unclaimed |
| npm | jsonwebtoken-verify | 404 | unclaimed |
| npm | ts-sql-parser | 404 | unclaimed |
| npm | uzwebsocket | 404 | unclaimed |
| npm | ics-to | 404 | unclaimed |
| pypi | pyyaml-reader | 404 | unclaimed |
| pypi | yamlkid | 404 | unclaimed |
| pypi | googlepurl | 404 | unclaimed |
| pypi | yamldom | 404 | unclaimed |
| pypi | icalalchemy | 404 | unclaimed |
| npm | sql-walker | 404 | unclaimed |
| npm | cron-schedule-generator | 404 | unclaimed |
The list is a sample: what remained of the hundred highest-risk entries shown on the public page, not every name the collector has recorded. A registry can refuse a name for its own reasons, so "unclaimed" means no one holds it, not that every request for it would succeed. Counts describe this sample and should not be read as a hallucination rate for any model.