Slopsquatting: The Supply Chain Attack That Begins Before the Package Exists
Sixteen code-generating models produced 205,474 package names that had never existed. Forty-three percent of them came back every single time the same prompt was re-issued. Every supply chain control built since 2016 protects a dependency graph; this attack happens one step earlier, at the moment a name is suggested, where no lockfile, hash or attestation is looking.
205,474 package names that have never existed on any registry, at any point in history. That is what fell out when Spracklen and colleagues generated 576,000 code samples from 16 code-generating models across Python and JavaScript, pulled the 2.23 million package references out of them, and checked each one against PyPI and npm. 440,445 references, 19.7 percent of the total, pointed at nothing (Spracklen et al., 2025, We Have a Package for You!, USENIX Security 25).
An invented import is normally a self-correcting error. The install fails, the developer swears, the name gets fixed. The entire supply chain security apparatus built over the last decade, lockfiles, integrity hashes, SBOMs, Sigstore, provenance attestations, rests quietly on that assumption: whatever else is wrong, at least the dependency in your manifest is a thing that someone published. Slopsquatting is the attack that removes the assumption. Register the name the models keep inventing, and the install stops failing.
Why this matters: Every control in the modern supply chain stack binds a name to bytes, or bytes to a publisher. None of them asks where the name came from. Slopsquatting attacks the step before the dependency graph exists, which means a fully pinned, fully hashed, fully attested build can be compromised end to end without a single control firing. The defence has to move upstream to adoption and downstream to containment, because the middle of the pipeline is structurally blind to it.
TL;DR
- The rate is measured and large. 19.7 percent of package references generated by 16 models did not exist; open-weight models hallucinated 21.7 percent of recommendations against 5.2 percent for commercial ones (Spracklen et al., 2025).
- Repeatability, not frequency, is what makes it an attack. Re-issue the prompt ten times and 43 percent of hallucinated packages come back all ten times; 58 percent come back more than once. An attacker needs a name that is stable, not a name that is common.
- Better models compress the range and leave the surface. A 2026 re-evaluation of five frontier code models measured 4.62 to 6.10 percent hallucination across 199,845 paired prompts, and isolated 127 names that all five models invent identically, 53 of which remained registrable (arXiv:2605.17062).
- Cross-model agreement is the wrong signal. Asking three models and taking the consensus concentrates trust on exactly the model-agnostic names an attacker has the strongest reason to have claimed.
- Lockfiles and attestations do not apply. Pinning protects the second install. Attestation proves who published a file, which PyPI's own documentation distinguishes from trustworthiness (PyPI, 2024). A slopsquatted package passes both.
- It has already landed without malice. A researcher registered
huggingface-clias an empty proof of concept in December 2023; it took more than 15,000 downloads in three months and ended up in Alibaba's GraphTranslator install instructions (The Register, 2024). - Agents remove the last human checkpoint.
ModuleNotFoundErrorin front of a person is a review gate. The same error in front of an autonomous coding agent is a prompt to runpip install.
At a Glance
flowchart LR
PROMPT["Developer prompt"] --> MODEL["Code model"]
MODEL --> NAME["Package name<br/>invented, plausible"]
NAME --> REG{"Registered?"}
REG -->|"no"| FAIL["Install fails<br/>error is the control"]
REG -->|"yes, by attacker"| INSTALL["Install succeeds"]
INSTALL --> EXEC["Install-time script runs<br/>as the developer"]
EXEC --> LOCK["Written to lockfile<br/>hashed, pinned, reproducible"]
classDef blue fill:#1e40af,stroke:#3b82f6,stroke-width:1px,color:#fff
classDef purple fill:#6d28d9,stroke:#a78bfa,stroke-width:1px,color:#fff
classDef slate fill:#334155,stroke:#64748b,stroke-width:1px,color:#e2e8f0
classDef emerald fill:#047857,stroke:#34d399,stroke-width:1px,color:#fff
classDef rose fill:#be123c,stroke:#fb7185,stroke-width:1px,color:#fff
class PROMPT blue
class MODEL purple
class NAME,REG slate
class FAIL emerald
class INSTALL,EXEC,LOCK roseRead the diamond carefully. Whether the build is compromised is decided entirely by a fact about the outside world, whether somebody registered a string, and nothing inside the developer's toolchain participates in that decision. The controls all live to the right of it, where they faithfully pin, hash and attest the attacker's package.
[IMAGE: Two side-by-side pipeline diagrams. Left, labelled "what the security stack watches": manifest, resolver, lockfile, hash check, signature, SBOM, scanner, each with an eye icon. Right, labelled "where slopsquatting happens": prompt, model, suggested name, with no eye icons at all and the first eye appearing only after the name enters the manifest. Caption: "The instrumented region starts one step too late."]
Before the Package Was Imaginary
Package-name attacks are old, and their history is a slow retreat from requiring user error.
The first generation needed a typo. Register reqeusts, wait for fingers to slip. Nikolai Tschacher's 2016 University of Hamburg thesis measured how well that works by uploading near-miss variants of 214 popular packages to PyPI, npm and RubyGems; the experiment reached more than 17,000 machines, roughly half of them running his code with administrative rights (Tschacher, 2016, Typosquatting in Programming Language Package Managers). Registries have fought edit-distance squatting ever since, with mixed success.
The second generation removed the typo and attacked the resolver instead. In February 2021 Alex Birsan published Dependency Confusion, showing that if a company used an internal package name that was unregistered publicly, most package managers would silently prefer a higher-versioned public package of the same name. He got code execution inside more than 35 organisations, Apple, Microsoft, PayPal, Shopify, Netflix, Tesla and Uber among them, and collected over 130,000 dollars in bounties (Birsan, 2021). Nobody mistyped anything; the tool chose wrong. The ML ecosystem learned this first-hand in December 2022, when a malicious torchtriton uploaded to PyPI shadowed the package shipped on PyTorch's own nightly index and exfiltrated host data from anyone who installed a nightly build between 25 and 30 December (PyTorch, 2022).
The third generation removes the resolver too. There is no real package to shadow and no similar name to squat, because the name was never real. Bar Lanyado, then at Vulcan Cyber, described AI package hallucination as an attack surface in mid-2023, and then did the honest thing and tested it: he registered huggingface-cli on PyPI in December 2023 as an empty package after watching models recommend it over and over. Three months later it had more than 15,000 real downloads, and Alibaba's GraphTranslator repository was telling users to install it (The Register, 2024). A hallucination had propagated into human-written documentation, where it stopped being a model artefact and became an ordinary instruction that would outlive any model.
Seth Larson of the Python Software Foundation gave the resulting attack its name in April 2025: slopsquatting.
timeline
title From typos to hallucinations
2016 : Tschacher demonstrates registry typosquatting at scale
: defence is edit distance and name similarity
2021 : Birsan publishes Dependency Confusion; 35-plus companies, resolver preference is the bug
2022 : torchtriton shadows PyTorch nightly index; ML ecosystem hit directly
2023 : Lanyado describes AI package hallucination and registers huggingface-cli as a proof of concept
2024 : huggingface-cli passes 15,000 downloads and enters Alibaba GraphTranslator instructions
2025 : Spracklen et al. measure 205,474 invented names across 16 models; Larson names slopsquatting
: Nx s1ngularity and Shai-Hulud show install-time execution at ecosystem scale
2026 : Frontier cohort rates fall to roughly 5 percent; 127 model-agnostic names remainEach generation needed less from the victim. Typosquatting needed a mistake, dependency confusion needed a misconfiguration, slopsquatting needs nothing at all. The developer types exactly what they were told.
[IMAGE: Three-panel comparison of the attack generations. Panel one, typosquatting: a keyboard with a slipped finger and the caption "requires user error". Panel two, dependency confusion: two registries with a resolver arrow pointing at the wrong one, caption "requires misconfiguration". Panel three, slopsquatting: a chat bubble containing a package name flowing straight into a terminal, caption "requires nothing". Caption: "Each generation of name attack asked less of the victim."]
How a Hallucination Becomes a Dependency
Why models invent package names in the first place
A package name is a high-entropy string with low semantic constraint. Nothing about pandas follows from what pandas does. The model has learned a distribution over names that co-occur with certain code shapes, and at generation time it produces the most plausible continuation, which is a name that looks like it belongs in that neighbourhood. There is no lookup, no oracle, and no signal in the objective that distinguishes a real name from a well-formed one.
The taxonomy in the USENIX study makes this concrete. The largest single category, 38 percent of hallucinations, is conflation: the model fuses two real names, or grafts a real project onto a real naming convention. huggingface-cli is the archetype. Hugging Face is real, the -cli suffix convention is real, and the composite reads correctly to any Python developer who has installed a hundred packages. The remaining categories are names from other ecosystems (an npm package imported in Python) and names with no obvious ancestry.
Conflation is the dangerous category precisely because it is well-formed. A defender scanning for suspicious-looking imports finds nothing to flag.
Why the inventions repeat
This is the finding that turns an error rate into an attack. The authors took prompts that had produced a hallucination and re-issued each one ten times. 43 percent of hallucinated packages appeared in all ten responses. 58 percent appeared more than once. Only 39 percent never recurred (Spracklen et al., 2025).
Stability of this kind follows from the mechanism. If a name is where probability mass sits for a given prompt neighbourhood, sampling will keep landing there. Temperature changes how often you fall off the peak; it does not move the peak. Which means an attacker's workflow is not "monitor developers and react" but something closer to reconnaissance against a fixed target:
- Choose a topic area with heavy AI-assisted usage.
- Generate thousands of realistic prompts.
- Extract every package reference.
- Filter to names absent from PyPI and npm.
- Re-query each name's prompt ten times, keep the ones that recur.
- Register them.
Steps 1 through 5 cost inference. Step 6 costs nothing. There is no exploit development, no CVE, no race against a patch. The resulting names sit on the registry indefinitely, and they are correct-looking enough that a developer who does glance at them sees nothing wrong.
The window nobody instruments
Trace the lifecycle of a dependency and mark where each control operates.
Everything to the right of the first install is about consistency: that what you get tomorrow matches what you got today, and that the file came from the publisher it claims. Both properties hold perfectly for a malicious package. The lockfile faithfully pins it. The hash faithfully matches. If the attacker published through GitHub Actions, PEP 740 gives them a valid Sigstore-backed attestation, and PyPI's documentation is explicit that this establishes integrity, "not necessarily trustworthiness" (PEP 740; PyPI, 2024).
The unguarded window is exactly one event wide: the transition from a name in a chat response to a name in a manifest. It is short, it is high-frequency, and until recently nothing logged it.
Install is the payload, not import
The second structural fact is that the malicious code does not need your program to import it. npm install and pip install execute publisher-supplied lifecycle scripts with the invoking user's full environment: shell, dotfiles, SSH keys, cloud credentials, network. Every large ecosystem incident of 2025 ran through this channel rather than through application code.
So the attack completes at the moment of the failed-then-succeeding install. The developer sees a package install and a script run, notices nothing, corrects course when the API does not match their expectations, and removes the dependency an hour later. The credentials left three minutes after installation.
[IMAGE: Annotated terminal transcript. A model's chat response suggesting an import, then pip install succeeding, then a two-line install log, with a red callout arrow to the log line reading "arbitrary code, publisher's, running as you, before any test". Caption: "The compromise completes before the dependency is ever imported."]
Seeing It in Motion
sequenceDiagram
participant D as Developer
participant M as Code model
participant R as Registry
participant A as Attacker infra
D->>M: "parse these logs in Python"
M-->>D: snippet importing an invented name
D->>R: install the invented name
R-->>D: package found, version 0.1.2
Note over R,D: registered three months earlier<br/>by an attacker running the harvest loop
D->>D: install script executes as developer
D->>A: credentials, tokens, environment
A-->>A: replay against cloud and registry APIs
D->>D: API does not match, dependency removed
Note over D: removal happens after exfiltration<br/>the lockfile entry was valid throughoutThe sequence has no step at which a defender's tool is asked a question it can answer. The registry answered honestly. The hash was correct. The only anomalous event is the outbound connection from an install script, which is the one thing most environments do not watch.
Compare where each candidate defence sits:
flowchart TB
subgraph PRE["Before adoption"]
V1["Resolver existence check"]
V2["Age and download floor"]
V3["RAG-grounded suggestion"]
end
subgraph AT["At install"]
C1["Lifecycle scripts disabled"]
C2["Sandboxed install, no credentials"]
C3["Egress limited to registry"]
end
subgraph POST["After adoption"]
L1["Lockfile pinning"]
L2["Integrity hashes"]
L3["Attestations and SBOM"]
end
PRE --> AT --> POST
NOTE1["Stops the name entering"]:::emerald
NOTE2["Stops the payload landing"]:::emerald
NOTE3["Preserves the compromise faithfully"]:::rose
PRE --- NOTE1
AT --- NOTE2
POST --- NOTE3
classDef emerald fill:#047857,stroke:#34d399,stroke-width:1px,color:#fff
classDef rose fill:#be123c,stroke:#fb7185,stroke-width:1px,color:#fff
classDef slate fill:#334155,stroke:#64748b,stroke-width:1px,color:#e2e8f0
classDef blue fill:#1e40af,stroke:#3b82f6,stroke-width:1px,color:#fff
class V1,V2,V3 blue
class C1,C2,C3 slate
class L1,L2,L3 slateMost organisations have invested heavily in the bottom band and not at all in the top two. That allocation was correct for the threats of 2019.
stateDiagram-v2
[*] --> Suggested
Suggested --> Rejected: name absent from registry
Suggested --> Adopted: name resolves
Rejected --> [*]
Adopted --> Executed: install scripts run
Executed --> Pinned: written to lockfile
Pinned --> Propagated: pushed, built in CI, shipped
Propagated --> Investigated: incident or disclosure
Investigated --> [*]
note right of Adopted
The only transition an attacker
must buy is Suggested to Adopted,
and it costs one registration
end noteWatch It Run
[IMAGE: Frequency plot of hallucinated package names ranked by how many of ten re-queries they appear in, with bars at 10/10 and at 1/10 clearly dominant and a shallow middle. Annotation over the 10/10 bar reads "43 percent: the attacker's target list". Caption: "Hallucination repeatability is bimodal, and the useful mode is the persistent one."]
By the Numbers
| Quantity | Value | Source |
|---|---|---|
| Code samples generated | 576,000 across 16 models, Python and JavaScript | Spracklen et al., USENIX Security 25 |
| Package references extracted | 2.23 million | Spracklen et al. |
| References that did not exist | 440,445 (19.7 percent) | Spracklen et al. |
| Unique invented names | 205,474 | Spracklen et al. |
| Hallucination rate, open-weight models | 21.7 percent of recommendations | Spracklen et al. |
| Hallucination rate, commercial models | 5.2 percent of recommendations | Spracklen et al. |
| Hallucinations recurring in all 10 re-queries | 43 percent | Spracklen et al. |
| Hallucinations recurring more than once | 58 percent | Spracklen et al. |
| Hallucinations never recurring | 39 percent | Spracklen et al. |
| Largest hallucination category (conflation) | 38 percent | Spracklen et al. |
| Frontier-cohort rate, 2026 re-evaluation | 4.62 to 6.10 percent over 199,845 paired prompts | arXiv:2605.17062 |
| Names invented identically by all five 2026 models | 127 (109 PyPI, 18 npm) | arXiv:2605.17062 |
| Of those, still registrable after registry defences | 53 (41 PyPI, 12 npm) | arXiv:2605.17062 |
huggingface-cli proof-of-concept downloads |
over 15,000 in three months | The Register, 2024 |
| Nx s1ngularity credentials recovered from leak | 2,349 from 1,079 systems | GitGuardian, 2025 |
Sources: package hallucination measurements from Spracklen et al., 2025, USENIX Security 25, with data and code at Spracks/PackageHallucination; 2026 frontier-cohort figures from arXiv:2605.17062; the huggingface-cli download figure is as reported by The Register in March 2024 and reflects a three-month window, not a current total; the s1ngularity credential count is GitGuardian's analysis of the publicly leaked repositories and is a lower bound on what was actually taken. Counts of packages affected in the Shai-Hulud waves vary between vendors and are omitted here for that reason.
A Concrete Example
Take a team of 40 engineers on a Python service, using a commercial model through an IDE assistant. Call the invented package pyproc-utils; it is a name made up for this walkthrough, because naming a real hallucinated package in an article is itself a way to get it registered.
Step 1: one snippet. An engineer asks for code to normalise log lines. The model returns a snippet with five imports: re, json, dateutil, structlog, and pyproc-utils. Four exist.
Step 2: the per-snippet probability. With a commercial-model rate of \(p = 0.052\) per recommended package and \(k = 5\) recommendations:
Roughly one snippet in four carries a name that does not exist. That is not a rare event; it is a daily one, and it is why the failure mode is invisible in a retrospective. Everyone has seen it and nobody logged it.
Step 3: is this one persistent? The engineer regenerates. pyproc-utils returns. The team has, without meaning to, performed exactly the test an attacker performs. Per the USENIX repetition figures, a hallucination that recurs has a 43 percent chance of being in the all-ten-of-ten class, and those are the names worth registering.
Step 4: the attacker's yield. Run the harvest loop against 1,000 realistic prompts of this kind, each producing \(k = 5\) recommendations:
About 112 persistent hallucinated references per thousand prompts. Deduplicated, the unique-name count is lower, because conflation drives many prompts to the same attractor; the 2026 study's 127 model-agnostic names out of 199,845 prompts is the tight, high-confidence end of that distribution. Either way the attacker's shopping list is in the tens to low hundreds, and registration is free.
Step 5: the install. pip install pyproc-utils succeeds. setup.py runs as the engineer, with their shell environment. It reads ~/.aws/credentials, the GITHUB_TOKEN in their environment, and ~/.ssh/id_ed25519, and posts them to a collection endpoint. Elapsed time from the model's suggestion to exfiltration: under two minutes.
Step 6: the trace it leaves. The engineer finds the API does not do what they expected, removes the dependency, and moves on. The lockfile diff that added and removed one package is reviewed and merged without comment. There is no alert anywhere, because nothing in the sequence violated a policy. The next signal the organisation receives arrives weeks later, from someone else, about a token.
Step 7: which control would have fired, and when. Existence-and-age checking at step 5 would have blocked adoption on a package with no history. Disabling install scripts would have made step 5 inert, turning the compromise into an import error. Egress restriction on the dev container would have made step 5 complete and step 5's exfiltration fail. Every control in the lockfile band would have done exactly what it is designed to do, which is to make step 6 reproducible.
[IMAGE: Timeline strip of the seven steps above along a horizontal axis marked in minutes, with each defence drawn as a vertical gate positioned at the step where it would fire, and the three lockfile-band controls drawn as gates positioned after the exfiltration marker. Caption: "Three controls are upstream of the damage and three are downstream of it."]
Where It Breaks
Blocklists cannot reach the tail
205,474 names from one study is a floor. The set grows with every new library that gives conflation raw material, shifts with prompt phrasing, and changes on every model release. Registry-side reservation of disclosed names is worth doing, and the 2026 study's coordinated disclosure with PyPI Security and Socket.dev is exactly the right process, but it closed 74 of 127 names and left 53 registrable. Enumerating the attack surface is not the same as covering it.
"Just check the package exists" fails on a registered package
Existence checking is the cheapest control and it is necessary. It is also, by construction, the one the attacker has already defeated: the entire point of registering the name was to make the check pass. Existence has to be joined with signals the attacker cannot cheaply fake. Age, download history, repository linkage, maintainer history and release cadence are all weak individually and reasonable in combination, at the cost of friction on genuinely new packages, which is a real cost borne by legitimate maintainers.
Self-verification inherits the error
The USENIX authors tested mitigations and found the models can flag a meaningful share of their own hallucinated names when asked directly, and that retrieval grounding and fine-tuning both reduce the rate, with fine-tuning the strongest. That is good news and it is bounded news. A verification step running in the same model that produced the name shares its priors, and a conflation hallucination is by definition the name the model finds most plausible. Verification that matters queries the registry, not the model.
Cross-model consensus inverts
This one is worth stating twice because it runs against intuition. If three independent models agree on a package name, a reasonable engineer treats that as corroboration. The 2026 result says the names all models produce are a specific, small, enumerable set, and that set is the most valuable real estate an attacker can own. Consensus is a strong signal of a real package in general and a strong signal of an attacker-registered one in the residual case, and you cannot tell which from the consensus alone.
[IMAGE: Venn-style figure of three overlapping sets, one per model, each set labelled "names this model invents". The three-way intersection is small, shaded red and labelled "127 model-agnostic names, 53 still registrable"; the outer crescents are large and pale. Caption: "The intersection engineers read as corroboration is the set an attacker would buy first."]
The human checkpoint is being removed
The reason slopsquatting has not produced a headline incident on the scale of s1ngularity may simply be that a person has usually been in the loop, reading an unfamiliar name in an install command. Autonomous coding agents remove that. An agent that hits ModuleNotFoundError and resolves it by installing the missing package has converted the single reliable detection point into an automated adoption step, at machine speed. The same agents also make the consequence worse: the Nx s1ngularity compromise in August 2025 demonstrated malware invoking locally installed AI CLIs to enumerate a host's secrets, getting a context-aware credential search for the cost of one prompt (Wiz, 2025).
Ecosystem hardening solves a different problem
npm's response to the 2025 worms was substantial: classic token creation disabled in November 2025, remaining classic tokens revoked on 9 December 2025, and a push toward OIDC-based trusted publishing (GitHub, 2025). Semgrep's analysis of npm v12 reports dependency lifecycle scripts no longer running by default (Semgrep, 2026), which is the single most valuable change in the list for this threat. But trusted publishing and 2FA raise the cost of hijacking someone else's package. A slopsquatter does not hijack anything. They publish their own package, legitimately, under a name nobody else wanted, with every ecosystem control satisfied.
Removal does not undo it
A dependency added and removed in the same afternoon leaves a lockfile diff that looks like housekeeping. Most organisations cannot reconstruct which packages were briefly installed on which machines, so the incident-response scope after a disclosure is "every machine that ran an install in the affected window", which is every machine. Credential rotation is bounded by inventory, and inventory is the thing nobody has.
Alternative Designs
| Design | How it works | Key advantage | Key limitation | Best when |
|---|---|---|---|---|
| Existence and age gate | Refuse any dependency below a minimum age, download count or repository-linkage threshold | Blocks freshly registered names, cheap to run in CI | Friction on legitimate new packages; a patient attacker ages the name | Any team, as a baseline |
| Internal mirror with allowlist | Resolve only against a proxy serving pre-fetched, scanned versions | Closes slopsquatting and dependency confusion together | Operational cost; adds a human step to every new dependency | Regulated or large orgs |
| Lifecycle scripts off by default | Install without executing publisher scripts, with a short explicit allowlist | Turns a compromise into an import error; now npm's default in v12 | Allowlist decay; some packages genuinely need build steps | Everywhere, immediately |
| Sandboxed install with egress control | Resolve in a container with no credential store and outbound access only to the registry | Generalises across all delivery mechanisms, signature-free | Needs real infrastructure; easy to build a sandbox that leaks | CI always, laptops ideally |
| RAG-grounded suggestion | Model consults a live registry index before recommending a name | Attacks the root cause instead of the symptom | Reduces but does not eliminate; index must be trusted and current | Tool vendors, not consumers |
| Fine-tuning against hallucination | Train the model on verified package sets | Largest measured reduction of the model-side mitigations | Vendor-side only; new packages age out of the training set | Model providers |
| Lockfile, hashes, attestation | Bind name to bytes and bytes to publisher | Essential against tampering and account takeover | Structurally blind to this attack; preserves it faithfully | Always, but not for this |
The honest reading of the table is that the consumer-side controls which actually bite are the boring ones: gate adoption, disable install scripts, sandbox the install, restrict egress. Nothing in that list is novel, and nothing in it is AI-specific, which is why it has been so easy to leave undone.
How It Is Used in Practice
The mature version of this looks like a policy with three enforcement points and no reliance on anyone being careful.
At adoption, a CI check compares the dependency manifest's new entries against a threshold policy: package age, download history, whether the repository link resolves, whether a maintainer has other published work. New entries failing it require a named approver. This is the only place where the hallucination itself can be caught, and it is a mechanical check rather than a review.
At install, scripts are off. npm v12's default change makes this the path of least resistance for JavaScript; Python teams get there with build isolation and an explicit allowlist. Installs run in a container with no mounted credential store, no Docker socket, and outbound access limited to the registry and artefact store. The last constraint is the one that generalises: s1ngularity exfiltrated through GitHub precisely because every CI environment on earth permits GitHub.
At publish, the organisation's own packages move to trusted publishing so that no long-lived publish token sits on a laptop. This does nothing for slopsquatting and everything for the amplification step that turned both 2025 worms from incidents into events, and it is worth doing on that basis alone (CISA, 2025).
Registries are moving in parallel. PyPI supports Sigstore-backed attestations under PEP 740, npm has retired classic tokens, and both have run name-reservation exercises against disclosed hallucination sets. These raise the floor for everyone and none of them substitute for the three enforcement points above, because a registry cannot know that a legitimately published package was named by a model.
[IMAGE: Org-chart-style diagram of the three enforcement points, adoption gate in CI, install sandbox on developer machines and runners, trusted publishing at release, each annotated with the specific attack class it blocks and the classes it does not. Caption: "Three gates, three different threats; none of them covers another's."]
Insights Worth Remembering
-
Repeatability is the vulnerability, not the error rate. A model that invents a different wrong name every time is annoying. A model that invents the same wrong name 43 percent of the time has published a target list. Any future measurement of this risk should lead with persistence, not with a headline hallucination percentage.
-
The supply chain stack is instrumented one step too late. Lockfiles, hashes, SBOMs and attestations all operate on a dependency graph that already exists. The attack happens at the transition into that graph, which is the one event nobody logs. Fixing this is a matter of moving a control, not inventing one.
-
Attestation answers "who", never "whether". PyPI's documentation says this plainly: integrity, not trustworthiness. A slopsquatted package built reproducibly in GitHub Actions and signed through Sigstore is exactly as attested as a legitimate one, and a security programme that treats provenance as a trust decision has misread what it bought.
-
Consensus across models is anti-correlated with safety here. The set of names every model invents is small, enumerable, and the highest-value real estate an attacker can occupy. Agreement should raise your confidence that a package is real and, in the residual case, raise your suspicion that it is registered. Those are not reconcilable from the consensus alone.
-
Install time is the real execution boundary. The compromise completes before any test runs, any scanner scans, or any code imports anything. Controls positioned after install describe a machine that is already owned. One flag,
--ignore-scripts, buys more than most detection budgets. -
Better models shrink the range and keep the tail. The 2026 cohort compressed inter-model spread by an order of magnitude and still left 53 registrable model-agnostic names. Risk that falls from 20 percent to 5 percent is a five-fold improvement and an unchanged architecture; planning for the residual is the whole job.
-
Agents convert a detection point into an adoption step. The reliable control in this attack has always been a human reading an unfamiliar name in an install command. An agent that clears
ModuleNotFoundErrorby installing the missing package deletes that control silently, and nobody will file a ticket about it. -
Ecosystem hardening and consumer containment are disjoint. Trusted publishing, phishing-resistant 2FA and attestations defeat account takeover. Sandboxing and adoption gates defeat malicious-by-design packages. Each is often sold as supply chain security, and a programme with only one of them has half a control.
Open Questions
Does agent-assisted development measurably raise the incidence, or only the ceiling? The mechanism is clear and the measurement is not. Nobody has published telemetry on how often autonomous agents install packages to clear import errors, or what fraction of those installs are of names with no history. Until someone does, the claim that agents materially increase slopsquatting exposure is well-reasoned and unquantified.
How durable is a model-agnostic hallucination across model generations? The 2026 study measured 127 shared names within one cohort. Whether those names persist as the cohort turns over, or whether each generation produces its own attractors, decides whether name reservation is a one-time cleanup or a permanent operational duty. The data to answer this exists and the longitudinal study has not been run.
Can registries price the defence without taxing new maintainers? Age and download thresholds are the most effective consumer-side gate and they fall hardest on legitimate new packages, which is a real tax on the ecosystem's newcomers. Whether reputation signals, maintainer attestation or staged trust can separate the two is an open design problem rather than a solved one.
Has a slopsquatting incident with real damage already happened undetected? huggingface-cli proved the delivery path works with a benign payload. Given that install-and-remove leaves almost no trace and that most organisations cannot enumerate briefly-installed packages, the absence of a confirmed malicious case is weak evidence of absence. This is speculation, stated as such, and it is the kind that is uncomfortable to leave unresolved.
Does grounding generalise, or just shift the trust? Retrieval-grounded suggestion was the most architecturally satisfying mitigation in the USENIX study, and it relocates the question to whether the index is current and honest. An index that includes a freshly registered slopsquatted package will confidently confirm it exists.
Sources and Further Reading
- Spracklen, J., Wijewickrama, R., Sakib, A. H. M. N., Maiti, A., Viswanath, B., & Jadliwala, M. (2025). "We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs." 34th USENIX Security Symposium. Distinguished Paper Award. USENIX · arXiv:2406.10279 · code and data
- "The Range Shrinks, the Threat Remains: Re-evaluating LLM Package Hallucinations on the 2026 Frontier-Model Cohort" (2026). arXiv:2605.17062
- USENIX. "Package Hallucinations: How LLMs Can Invent Vulnerabilities." ;login: online. usenix.org
- Tschacher, N. P. (2016). "Typosquatting in Programming Language Package Managers." Bachelor thesis, University of Hamburg. incolumitas.com
- Birsan, A. (2021). "Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies." Medium
- PyTorch (2022). "Compromised PyTorch-nightly dependency chain between December 25th and December 30th, 2022." pytorch.org
- The Register (2024). "AI bots hallucinate software packages and devs download them." 28 March 2024. theregister.com
- PyPI (2024). "PyPI now supports digital attestations." blog.pypi.org
- PEP 740: Index support for digital attestations. peps.python.org
- Trail of Bits (2024). "Attestations: A new generation of signatures on PyPI." blog.trailofbits.com
- Wiz (2025). "s1ngularity: supply chain attack leaks secrets on GitHub." wiz.io
- GitGuardian (2025). "The Nx s1ngularity Attack: Inside the Credential Leak." blog.gitguardian.com
- CISA (2025). "Widespread Supply Chain Compromise Impacting npm Ecosystem." Alert, 23 September 2025. cisa.gov
- Krebs, B. (2025). "Self-Replicating Worm Hits 180+ Software Packages." krebsonsecurity.com
- Datadog Security Labs (2025). "The Shai-Hulud 2.0 npm worm: analysis, and what you need to know." securitylabs.datadoghq.com
- Unit 42, Palo Alto Networks (2025). "Shai-Hulud Worm Compromises npm Ecosystem in Supply Chain Attack." unit42.paloaltonetworks.com
- GitHub (2025). "npm security update: Classic token creation disabled and granular token changes." Changelog, 5 November 2025. github.blog
- Semgrep (2026). "RIP npm Postinstall Scripts: npm v12 Kills Auto Script Execution by Default." semgrep.dev
- Pillar Security (2025). "New Vulnerability in GitHub Copilot and Cursor: How Hackers Can Weaponize Code Agents Through Compromised Rule Files." pillar.security
Free to read, no ads, no sign-up. If it was useful you can buy me a coffee.