{"slug": "ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open", "title": "AI Keeps Recommending Fake Packages. I Checked Which Ones Are Still Sitting Open.", "summary": "A developer who maintains the package-registry-scraper on Apify re-checked the live npm and PyPI registries to verify whether previously reported AI-hallucinated package names are still exploitable. The npm package unused-imports has been a security holding package since October 2025, roughly seven months after its malicious versions were published, while react-codeshift remains a researcher-registered placeholder and the original PyPI proof-of-concept huggingface-cli now returns 404 on both the JSON API and simple index. The findings show that published slopsquatting reports can be stale by the time they are read.", "body_md": "I maintain a scraper that reads npm, PyPI and crates.io for a living — side\n\nproject, `package-registry-scraper`\n\non Apify. This week I wasn't debugging the scraper. I was trying to answer a\n\nsmaller, dumber question: when people say \"AI keeps recommending packages\n\nthat don't exist,\" and security researchers have already written about the\n\nspecific fake names — are those names *still* sitting there, right now,\n\nexactly as dangerous as the reports say?\n\nThe honest answer turned out to be \"it depends which registry you ask, and\n\nthe reports are already a little out of date.\"\n\nSlopsquatting isn't new — the term describes an attacker registering a\n\npackage name that an LLM hallucinates, so that whoever's agent tries to\n\n`npm install` or `pip install` it next gets the attacker's code instead of a\n\n`unused-imports` on npm — AI tools apparently hallucinate this instead of\nthe real `eslint-plugin-unused-imports`, and it was reportedly weaponized.` react-codeshift` on npm — hallucinated, then `huggingface-cli` on PyPI — the original 2023/2024 proof-of-concept: a\nresearcher uploaded an empty package under a name ChatGPT kept inventing,\nand real developers installed it thousands of times.\nNone of that is new reporting. What I wanted to know was the current state —\n\nnot what a blog post said six or twelve months ago, but what the registry's\n\nown API says if you ask it today.\n\n```\ncurl -s https://registry.npmjs.org/unused-imports | python3 -m json.tool\n```\n\nRight now, `unused-imports` resolves to version `0.0.1-security`, described\n\nas a \"security holding package,\" with `maintainers: []`. npm has clearly\n\nacted on it. But here's the part that surprised me: the `versions{}` object\n\nin that response has exactly **one** entry — the holding package. The real\n\n`1.0.0` and `9.9.0` releases are gone from it entirely. Yet the `time{}`\n\nobject still remembers them: `1.0.0` published 2025-03-17, `9.9.0` published\n\n2025-09-15, the security hold applied 2025-10-27. So the malicious package\n\nlived for roughly seven months before npm acted, and when npm finally did\n\nact, it erased the version payloads but left a paper trail of timestamps for\n\nversions that no longer resolve to anything.\n\nThat's one source of the discrepancy with what I'd read going in: a report I\n\nfound said this package was \"still live, pulling ~233 weekly downloads, as\n\nof early February 2026.\" Checked live, right now, it's been a dead security\n\nplaceholder since October 2025 — months before that report's own date. I'm\n\nnot assuming the report was wrong when it was written; I'm saying don't\n\ntrust a stat like that without re-checking it, because the window it\n\ndescribes can already be closed by the time you read about it.\n\n```\ncurl -s https://registry.npmjs.org/react-codeshift | python3 -m json.tool\n```\n\nThis one matched the reporting exactly: real package, one version, the\n\nmaintainer is `debugducky@ceriksen.com` — Charlie Eriksen is a named security\n\nresearcher at Aikido Security — description \"Placeholder to prevent\n\ndependency confusion,\" homepage pointed at Aikido's own threat-intel page.\n\nHe apparently found AI coding agents still trying to install it daily after\n\nit had spread through 237 forked repos, and grabbed the name himself rather\n\nthan wait for someone worse to. Confirmed, live, no surprises.\n\nThis is the one that actually stopped me. `huggingface-cli` was the package\n\nfrom the *original* slopsquatting demonstration — the one that kicked off\n\nthe whole research area, with a researcher's empty decoy package reportedly\n\npulling five-figure download counts over a few months back in 2023-2024.\n\n```\ncurl -s https://pypi.org/pypi/huggingface-cli/json\n# {\"message\": \"Not Found\"}\n\ncurl -s https://pypi.org/simple/huggingface-cli/\n# 404\n```\n\nBoth PyPI's JSON API and its plain package index say the same thing: nobody\n\nholds this name. I checked the underscore variant too (`huggingface_cli`),\n\nin case PyPI had normalized it differently — also 404. The most famous\n\nhallucinated-package proof-of-concept in the field is sitting on an open\n\nregistry, available to literally anyone who registers it next, including\n\nsomeone with worse intentions than the original researcher had.\n\nnpm's answer to \"a hallucinated name got attention\" is a tombstone nobody\n\ncan overwrite. PyPI's answer, at least for this one name, was apparently to\n\nlet the decoy package lapse and the name go back in the pool. That's not a\n\nbug report about PyPI generally — I only checked one name — but it's a\n\nconcrete, structural difference between how the two registries leave a\n\nknown-dangerous name after the dust settles, and it's checkable by anyone\n\nwith `curl` and two minutes.\n\nI checked three names, not a representative sample — this is not \"PyPI is\n\nworse than npm at handling this,\" it's \"these two specific names currently\n\nlook different, and the difference is worth knowing if you're the one\n\ndeciding whether to feel safe.\" I also can't tell you *why* `huggingface-cli`\n\nwas freed on PyPI — expired hold, policy decision, the original researcher\n\nabandoning it, or something else. PyPI's public API doesn't expose a\n\ntakedown history the way npm's `time{}` ghost-timestamps do, so I genuinely\n\ndon't have a way to check that live. And the \"233 weekly downloads\" figure I\n\ncontradicted is itself secondhand — I didn't verify it was ever true, only\n\nthat it isn't true of the current state.\n\nI keep the full three-registry check — endpoints, what a security hold looks\n\nlike on each one, and how to script this yourself before you install\n\nwhatever your AI assistant just suggested — in\n\n[`noble-ronin/package-hallucination-check`](https://github.com/noble-ronin/package-hallucination-check).\n\n**If your AI assistant has ever suggested installing a package you'd never heard of — did you check whether it actually existed before you ran the\ninstall, or after something went wrong?**", "url": "https://wpnews.pro/news/ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open", "canonical_source": "https://dev.to/ronin13/ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open-47c3", "published_at": "2026-10-12 07:33:34+00:00", "updated_at": "2026-10-12 07:40:10.386903+00:00", "lang": "en", "topics": ["ai-safety", "ai-agents", "ai-tools", "developer-tools"], "entities": ["npm", "PyPI", "crates.io", "Apify", "unused-imports", "react-codeshift", "huggingface-cli", "Charlie Eriksen"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open", "markdown": "https://wpnews.pro/news/ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open.md", "text": "https://wpnews.pro/news/ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open.txt", "jsonld": "https://wpnews.pro/news/ai-keeps-recommending-fake-packages-i-checked-which-ones-are-still-sitting-open.jsonld"}}