# Hallucinated packages, slopsquatting, and dependency hygiene

> Source: https://learn-python.com/review/dependencies/
> Part of Learn Python, free to read.

Ask a model for a library that does something slightly unusual and it will sometimes give you a package name that sounds exactly right, has a plausible API, and does not exist.

This is a well-documented behaviour, it happens across every model family, and the same non-existent names recur — the failure is not uniformly random, which is the part that matters. A predictable hallucination is a namespace an attacker can pre-register and wait in.

The industry name for that attack is **slopsquatting**, by analogy with typosquatting. It is cheap to execute, it targets an install command that people run without thinking, and Python is a particularly good target because `pip install` runs arbitrary setup code from an index anyone can publish to.

:::danger The whole attack in three lines
1. An agent suggests `pip install requests-retry-adapter` — a package that does not exist.
2. Someone has already registered that name on PyPI, because a model suggested it to them too.
3. Installation executes their code, with your credentials in the environment.
:::

## Why models do this

Package names are compositional and predictable: `<thing>-<qualifier>`, `py<thing>`, `<thing>-python`, `<framework>-<feature>`. A model that has seen thousands of real names generalises the pattern and produces new ones that fit it perfectly. It is the same generative behaviour that makes it good at naming your functions.

It gets worse in three specific situations, worth knowing because they are where to pay attention:

- **Niche requirements.** "A library for parsing HL7 v2 into pydantic models" — sparse training data, high invention rate.
- **Version drift.** A package that was real, then renamed or absorbed. `sklearn` versus `scikit-learn`, `PIL` versus `pillow`, and dozens of smaller cases.
- **Wrong ecosystem.** A real npm package name suggested as a Python one, because the concept exists and the model has crossed the streams.

## The four-second check

Before any `uv add` or `pip install` that you did not think of yourself:

```bash
# does it exist, and is it what you think it is?
uv pip index versions <name>          # or: pip index versions <name>
```

Then look at the PyPI page and ask three questions:

1. **When was it first published?** A package solving an old problem that appeared last month is a red flag.
2. **How many releases, and over how long?** One release, ever, is a red flag.
3. **Does the repository link go anywhere real,** with commit history and issues from other people?

That is genuinely all it takes, and it catches essentially every instance of this attack.

:::warn The dangerous moment is the paste
The risk is not the agent suggesting a name — it is you pasting an install command from a chat window into a terminal without reading it. Put `Bash(uv add:*)` and `Bash(pip install:*)` in the **ask** list of your [permission config](/ai/agent-setup/), never the allow list. That single line converts this from a real risk to a non-issue.
:::

## Beyond hallucination: the dependency you did not need

The more common and more boring problem is that agents add dependencies too readily. Asked to parse a date, retry a request or flatten a list, a model frequently reaches for a package where the standard library or three lines would do — because the training data is full of code that does exactly that.

Every dependency you add is a supply-chain surface, a version constraint on your other dependencies, and a thing that will eventually be unmaintained. The Python standard library is large and covers more than most generated code assumes:

| Generated reaches for | Often unnecessary because |
|---|---|
| `python-dateutil` | `datetime.fromisoformat` handles ISO 8601 since 3.11 |
| `requests` | fine, but `httpx` if you need async; `urllib.request` for one call |
| `six`, `future` | Python 2 compatibility shims. Delete on sight. |
| `attrs` alongside `pydantic` | pick one; `dataclasses` may be enough |
| a retry library | `tenacity` is good, but a 6-line loop is often better |
| `toml` | `tomllib` is stdlib since 3.11 |

Add a line to your `AGENTS.md`: *"New dependencies need a sentence in the PR description justifying them. Prefer stdlib."* It works, and it makes the decision visible at review time.

## Pin, lock, and read the lockfile diff

```bash
uv add httpx            # writes pyproject.toml AND uv.lock
git diff uv.lock        # read this. it is the actual supply chain.
```

A one-line change to `pyproject.toml` can be a forty-line change to the lockfile. The lockfile diff is where you see the transitive dependencies you just accepted, and it is the only place you see them.

## Automate the rest

```bash
uv add --dev pip-audit
uv run pip-audit                  # known CVEs in your resolved tree
```

Wire it into CI rather than the agent loop — it needs network access and it is too slow for a per-edit check. Weekly, plus on every lockfile change, is the right cadence.

Also worth enabling: your host's automated dependency updates (Dependabot, Renovate). Not because the updates matter individually, but because a repo where updates arrive continuously is one where a security update can actually be merged in an afternoon.

:::verdict The whole policy, in four lines
1. Package installs go behind a confirmation, never on the allowlist.
2. Unfamiliar name? Check first published, release count, repository.
3. Read the lockfile diff.
4. `pip-audit` in CI.
:::

:::promo digitalocean
:::

## Common questions

### How often do models actually invent package names?

Often enough that it is worth a systematic check rather than vigilance. Published research on this has found meaningful hallucination rates across model families, with a substantial share of invented names repeating across runs — and that repeatability is what makes the attack economic. The rate is lower on frontier models than it was, and it is not zero.

### Is this specific to Python?

No — npm has the same problem and a larger attack surface. Python is a particularly attractive target because installation can execute arbitrary code and because `pip install` is a command people run reflexively without reading.

### Does a lockfile protect me?

From version drift and from a compromised later release, yes. From installing a malicious package in the first place, no — the lockfile records whatever you added. The check has to happen at `uv add` time, which is exactly why that command belongs behind a confirmation.

### What if the package genuinely does not exist but I need what it does?

Good outcome, actually: it means you now know to check whether the stdlib covers it, and if not, whether the real library is called something else. Ask the agent for two or three alternatives and verify each — the second suggestion is usually the real one.
