Topic 03

Invented Packages and the Supply Chain

Security

The agent names packages the way it names functions, by what sounds right, and some of the names it produces do not exist. Because the same invented names come back when the same kind of question is asked again, an attacker can register them and wait. Installing a dependency runs a stranger's code with your permissions, so a new dependency in a diff gets more scrutiny than any line you wrote yourself.

Chapter 7 met the same mechanism aimed at functions: an invented API call, which fails the moment the type checker or a test runs. Aimed at the package registry, the same invention has a different ending, because a registry will accept a new name from anyone. The first person to want that name gets it.

Why It Invents Names

A package name is assembled from patterns like any other text the model writes: a domain word, a function word, a common suffix. Most of the time the result is a real package, because real packages are named that way. Some of the time it is not. One large measurement, a study by Spracklen and colleagues presented at USENIX Security 2025, generated 576,000 code samples with 16 models and checked every package they referenced. 19.7% of the referenced packages did not exist. The rate was at least 5.2% for the commercial models and 21.7% for the open-source ones, and across the run the models produced 205,474 unique invented names.

Those numbers describe the models in the study, and the rate for the model you use today will differ. It will not be zero, and a codebase with a few hundred dependencies changes them dozens of times a year, more and more often through an agent.

Predictable, Therefore Registrable

The same study asked whether the inventions repeat. It reran 500 prompts that had produced an invented name, ten times each. In its own words, "43% of hallucinated packages were repeated in all 10 queries", and "58% of the time, a hallucinated package is repeated more than once in 10 iterations." A name that comes back reliably is a name worth registering: publish a package under it, put something unpleasant in its install step, and wait for agents to suggest it to people.

The practice has a name. "Slopsquatting" was in circulation by April 2025, a blend of AI "slop" and typosquatting. The difference from typosquatting is the target. Typosquatting bets on a human mistyping a real name. Slopsquatting bets on a model inventing the same unreal one, and a model is far more consistent than a tired human.

The Cragmoor Case

Asked to fix the time-zone handling of the waitlist hold, the agent imports a time-zone helper package with a plausible two-word name. The session has installs auto-approved, so the install runs at once and succeeds, because someone registered that name 19 days earlier. The tests go green. The diff is small: one import, a few calls, a new line in the dependency manifest. Everything looks like a tidy fix.

Two registry pages: the package the agent chose, and the kind it should have
The new packagechosen by the agent
First published 19 days ago. One maintainer, whose other packages all appeared that same week. No linked source repository. 40 downloads, most of them since last week. Runs a script at install time.
An established packagefor comparison
Releases over many years. Several named maintainers and an organisation. A linked source repository whose tags match the published versions. No install script. Used by thousands of other packages.

The install script already ran, on your laptop, with your permissions, before the diff existed. Whatever it did, it did then.

Install Runs Code

In all three ecosystems this book uses, adding a dependency can execute its code before you have read a line of it. The JavaScript package manager that ships with the current long-term-support runtime runs a package's own install scripts: its documentation lists the scripts that run on install, among them preinstall, install and postinstall, "run from the scripts of" the package being installed. Its newest major version, as of 2026, blocks dependencies' install scripts until the project approves them, which helps only after the upgrade and only until someone approves one. The Python package installer hands a source distribution to the project's "build backend" to build it, which means running the package's own build code. And on the Java side, build plugins execute during the build, and any library runs as soon as a test loads it. The agent runs the tests on every step.

So the review that happens after the install is too late for the install itself. The package manager's add and install commands belong on the always-ask list from Chapter 3, and the working rule for the agent is that it proposes a dependency and waits; nothing installs it until a person has looked.

Checking a New Dependency

Open the registry page and the source repository yourself, not through the agent. It takes two minutes and the checks are concrete. The package exists and has a release history longer than a few weeks. Its maintainers are identifiable people or organisations. The published package matches the linked source, for example its version tags. It does not run scripts at install, or you know why it does. Then ask the question the agent rarely asks, Chapter 7's question about over-building: does the standard library or an existing dependency already do this?

At Cragmoor it does. The time-zone fix needs the language's own date and time support and the time-zone helper in schedule, both already there. The new package was never needed, and every dependency avoided is a name nobody can squat. The licence of any package that does pass these checks is a separate question, taken up in Chapter 13.

The Lock File Is Part of the Diff

A new top-level dependency brings its own dependencies, and they arrive without anyone asking for them by name. The manifest diff shows one new line; the lock file diff shows how many packages actually arrived. At Cragmoor's squatted helper it is one requested package and 11 transitive ones, several published by the same single maintainer within the same week. Read the lock file change as code: its size, and every new name in it.

What each file in the diff says about the same change
Dependency manifestwhat you asked for
1 line added: the time-zone helper at its latest version. Looks like the smallest possible change.
Lock filewhat you got
12 packages added: the helper and 11 it depends on, 4 of them published in the same week by the helper's own maintainer.

Registries also differ in how hard they make squatting. The Java ecosystem's main registry requires a publisher to prove ownership of a namespace, such as a domain, before publishing under it; its documentation says it must "validate that you own the web domain reflected by your namespace." That raises the bar for registering a convincing name. It does not remove it, because an attacker can own a domain too.

Invented Package vs Invented API

An invented API fails loudly: the function does not exist, and the type checker or the first test run says so. It costs a minute.

An invented package fails loudly only while the name is unregistered. Once someone registers it, the install succeeds, the code runs, and the failure is that a stranger's code is now in your build. The same mechanism has a much larger blast radius.

Misconceptions
  • "If the install succeeds, the package is real." It succeeds precisely for the names someone registered to catch it. Success proves registration, not legitimacy.
  • "I will catch it in review." Install-time code runs at install, which in an auto-approved session happens before the diff exists.
  • "A package with downloads is safe." Download counts are easy to inflate, and they include the installs that agents themselves trigger for every user who got the same suggestion.
  • "The agent checked the documentation for it." It can produce documentation-shaped text about a package that never existed, as Chapter 6 warned of every explanation.
  • "Only careless ecosystems have this problem." Every public registry accepts new names from strangers. The ones that verify namespace ownership raise the cost; they do not close the door.
Why It Matters
  • Keep the package manager's add and install commands on the always-ask list. The install is the moment a stranger's code runs.
  • Open the registry page and the source repository of every new dependency before approving it. Age, maintainers and a matching source take two minutes to check.
  • Ask whether the standard library or an existing dependency already covers the need. Every dependency avoided is a name nobody can squat.
  • Read the lock file diff as code and count what arrived. One requested package often brings ten more.

Knowledge Check

Why are invented package names predictable enough for an attacker to register in advance?

  • Because models are trained on registry lists, and they reuse names from the oldest packages first
  • Because the same prompts tend to produce the same invented names again, many on every rerun
  • Because invented names always follow one fixed pattern that an attacker can generate in bulk
  • Because registries publish lists of names that failed to install, which attackers then claim

In a session with installs auto-approved, when does a malicious install script run relative to your review of the diff?

  • After the review, because the agent waits for approval of the diff before running any command at all
  • During the review, because the package manager pauses each install until the next test run begins
  • Never, because install scripts only run in CI and never on a developer's own machine or laptop
  • Before the review, because the install runs its script the moment the agent installs the package

Which registry signal is the weakest evidence that a package is legitimate?

  • A download count in the tens of thousands
  • A release history going back several years
  • Several maintainers who can be identified
  • A source repository whose version tags match

An agent in an auto-approved session imports a plausible package name that someone registered 19 days ago. The tests pass. What has most likely happened?

  • Nothing harmful, since a package that installs and passes the tests has proven that it works
  • A stranger's code ran with your permissions at install time, and now sits in your build as well
  • The registry blocked the package, and the agent fell back to the standard library without saying
  • The type checker rejected the import, so the change never reached the test run in the first place

Why is an invented package more dangerous than an invented function?

  • An invented package is harder for the agent to explain, so the developer ends up trusting the change less
  • Package names are longer than function names, so a mistyped one is harder to spot in review
  • An invented function fails at once; a registered invented package installs, runs and stays in the build
  • Invented functions are caught by the linter, while invented packages pass every linter's checks

You got correct