Field report
I scanned 174 public GitHub repositories looking for npm imports that AI tools made up. I didn't find any. What I did find was a package name that Lovable's project template had put into 404 public repos, and nobody had ever published it on npm. I reported it to Lovable, and they reserved the name 16 days later.
Method
If made-up imports are an AI problem, they should show up more often in projects built with AI. So I scanned two groups: popular JavaScript and TypeScript projects as a baseline, and recent repos whose README says they were built with an AI tool.
| Group | How they were chosen | Repos | Files |
|---|---|---|---|
| Established | Over 5,000 stars, JS or TS, e.g. axios, redux, date-fns, react-hook-form, socket.io | 47 | 10,894 |
| AI-assisted | Created after 2025-06, README says “vibe coded”, “built with Lovable” or “built with Cursor” | 127 | 12,879 |
For each repo I downloaded the tarball from the GitHub API, ran
ghostimport --json on it, and deleted it. Then I looked up every flagged name on
the npm registry by hand. That manual check ended up mattering most, because a lot of what the
tool flagged was wrong. More on that in Finding 03.
I only used public data: repo tarballs and code search through the GitHub API, and the public npm registry. I didn't run code from any of these repos, didn't register any package names, and the only project I contacted was Lovable, through their official disclosure program.
Finding 01
Honestly, this surprised me, and it undercuts how I had been pitching ghostimport. It makes
sense, though. If an AI invents a package name, npm install fails the first time
anyone runs it. Somebody notices, fixes it, and only then commits. By the time the code is on
GitHub, the mistake is long gone.
So scanning finished repos is the wrong place to look. The risky moment is between the agent writing the import and someone running the install. That's why ghostimport also runs as an MCP server and as an agent hook, and not only as a command-line scanner.
My first pass turned up @anthropic/sdk, @openai/api and
@langchain/agents. They look exactly like names a model would invent, and none of
them exist on npm. All three came from one repo: another open-source tool in this space, which
keeps fake package names in its test suite on purpose. After that, I opened every flagged file
before counting anything.
Finding 02
The name was lovable-agent-playwright-config. It showed up in two files,
playwright.config.ts and playwright-fixture.ts, and those files looked
almost the same in projects that had nothing else in common. They come from the template
Lovable uses when it generates a project.
When I scanned, nobody had ever published that package on npm. GitHub code search found it in
657 files across 404 public repos, always in one of those two files, either at the root or
inside frontend/ or client/. Code search doesn't cover private repos,
so the real number was higher.
A made-up import in one person's code just breaks their build. A made-up import in a template
gets copied into every project the template creates. This one also started with
lovable-, so it would have looked legitimate to anyone reading it. If a stranger
had registered the name first, installing it, which is exactly what a developer or an AI agent
does to fix a missing import, would have run their code. npm runs a package's
postinstall script automatically.
| Date | What happened |
|---|---|
| 2026-08-30 | Found it in the scan and reported it privately to Lovable through their disclosure program on HackerOne |
| 2026-09-13 | Published this report, leaving out anything that identified the issue |
| 2026-09-15 | Lovable registered the name as an empty placeholder and marked the report resolved |
The package is now an empty placeholder owned by Lovable, with just a README and no install scripts, so nobody else can take the name. I never registered it myself.
Thanks to the Lovable security team for fixing it. They also credited the report on their HackerOne thanks page.
$ npm view lovable-agent-playwright-config version description time.created version = '0.0.0' description = 'This package is not intended for use.' time.created = '2026-09-15T10:10:18.192Z'
Two other unpublished names in the scan were scoped packages, used by example projects in the same repo. Those were never at risk: the scope already has an owner on npm, and only the owner can publish packages under it.
ghostimport 0.5.2 got this wrong and flagged both as free to register. Since 0.6.0 it checks whether a scope has an owner before saying that.
Finding 03
This was the first time I had run ghostimport on a lot of real code, and it didn't go well. Version 0.5.1 flagged a missing package in almost one in five popular repos, and nearly all of those were false alarms. A security tool that noisy just gets switched off.
| Measure | v0.5.1 | v0.5.2 |
|---|---|---|
| Established repos with a flagged package | 19.1% | 12.8% |
| AI-assisted repos with a flagged package | 11.8% | 5.5% |
| Distinct wrong names flagged | 55 | 25 |
I traced the false alarms to six bugs, all fixed in 0.5.2:
| Bug | What went wrong |
|---|---|
| Root-only config | Nested package.json and tsconfig.json files in monorepos were ignored, so workspace packages looked missing |
| Scoped aliases | The alias @commands was compared against the full import @commands/types, so it never matched |
| Comment stripping | The JSONC parser removed https:// inside strings, which broke every Deno import map it read |
| Framework scopes | @theme/* and @site/* are handled by Docusaurus and @std/* by Deno, and none of them come from npm |
| Build output | Folders like .vercel were scanned, so bundled code looked like real imports |
| Non-JS files | shared.dart was read as a package name, because dots are allowed in npm names |
About two thirds of what still gets flagged is fake names in test fixtures, in projects that test import parsing. I decided not to skip test folders. AI agents write tests too, and a made-up import in a test file is still a made-up import.
Scan log
Each run, with the date and the tool version, so you can see where the figures above come from.
| Date | Corpus | Version | Result |
|---|---|---|---|
| 2026-08-30 | 174 repos · 23,773 files | v0.5.2 | 25 names flagged · 3 confirmed unpublished · 1 that anyone could have registered, now reserved by Lovable · 0 organic hallucinations |
| 2026-08-29 | 174 repos · 24,414 files | v0.5.1 | 55 names flagged · 30 false alarms traced to 6 bugs |
You don't need my data. Run the scanner on any repo and look up the names it flags on npm.
$ npx ghostimport@0.5.2 ./some-repo --json $ npm view <name-it-flagged> # a 404 means nobody has published it
Confirming the Lovable name took one command, and so did fixing it. Starter templates, scaffolding tools and docs examples all put package names into other people's code. If one of those names isn't registered, anyone can register it. Checking takes a minute.