DEV Community

Cover image for An AI Agent Recommended a Malware Package. A Human Caught It. Next Time You Might Not Be So Lucky
Cor E
Cor E

Posted on

An AI Agent Recommended a Malware Package. A Human Caught It. Next Time You Might Not Be So Lucky

An engineer asked their AI coding assistant for a library recommendation. The agent suggested a package name. It sounded right. Plausible naming convention, matched the ecosystem's usual patterns, read like something that should exist.

It didn't exist. Or rather, it didn't exist yet when the model was trained. By the time the engineer went looking for it, someone had already registered that exact name and published a package with almost no download history. The Register covered it: manual review flagged the low download count and lack of history before anyone ran pip install or npm install. Crisis averted. No harm done. Zero HN comments, because "engineer does code review correctly" isn't exactly a thrilling headline.

But it should have gotten more attention than it did, because the failure mode it represents is going to keep happening, and most teams don't have a manual review step positioned to catch it every single time.

How This Actually Works: Slopsquatting

LLMs hallucinate package names. This isn't news to anyone who's used Copilot or Claude Code long enough. Ask a model for a solution to a reasonably specific problem and it will sometimes confidently recommend a library that sounds exactly like something that should exist in that ecosystem, follows the naming conventions perfectly, and simply isn't real.

Researchers have been documenting this for a while now. The attack that exploits it, dubbed "slopsquatting," is depressingly simple:

  1. Attacker monitors what package names LLMs commonly hallucinate (there's real research quantifying this at scale, and certain names get suggested repeatedly across different sessions and even different models)
  2. Attacker registers that exact name on PyPI or npm
  3. Attacker publishes something that looks legitimate on the surface but carries a malicious payload
  4. Developer asks an AI assistant the same or a similar question, gets the same hallucinated name, and this time it resolves to a real, attacker-controlled package
  5. pip install or npm install runs. Payload executes. Game over.

The uncomfortable part is that this doesn't require any sophistication on the attacker's end. No zero-day, no supply chain compromise of an existing maintainer's account, no clever obfuscation. Just squatting on a name that a language model is statistically likely to suggest, and waiting.

Why This Slips Past Normal Review

Traditional dependency scanning (Snyk, Dependabot, npm audit, whatever you're running) checks packages that are already in your lockfile against known vulnerability databases. That's a completely different problem. A brand new, zero-download, attacker-registered package has no CVEs against it. There's nothing to scan for yet, because the vulnerability isn't in the code, it's in the fact that the package shouldn't be trusted at all.

Code review catches this only if the reviewer happens to notice the package is unfamiliar and goes and checks its registry page manually. That's exactly what happened in this incident, and it worked. But it's a process step that depends entirely on a human remembering to do it, every time, for every AI-suggested dependency, forever. That doesn't scale, and it's exactly the kind of check that gets skipped when someone's shipping under deadline pressure at 6pm on a Friday.

The real gap: there's no automated checkpoint between "LLM suggests a package name" and "developer runs the install command" that asks the one question that actually matters here — does this package exist, and if it does, does it look real?

Where Sentinel Catches This

Sentinel's SlopScan integration exists specifically for this gap. It extracts package names out of LLM output and checks them against live registry data (PyPI, npm) before that recommendation ever reaches a point where a developer acts on it.

For the direct scrub endpoint, this means an AI assistant's output gets checked before it's ever displayed or used, full stop, since /v1/scrub is the layer that acts before the caller does anything with the content. Every package name mentioned gets pulled out and checked. If a package doesn't exist in the registry, or exists but has trust signals that look like the incident above (freshly created, essentially no downloads, no meaningful history), SlopScan surfaces that risk in the response alongside whatever else Sentinel found in the content.

Risk levels map to concrete outcomes:

  • DANGEROUS (confirmed malicious or known typosquat) → blocked
  • SUSPICIOUS (doesn't exist in the registry, or trust score near zero) → flagged
  • CAUTION (exists, but has warning signals like being brand new) → reported
  • SAFE → nothing added to the response, no noise

Note the important caveat here, because it matters for how you deploy this: on the agentic proxy routes (/v1/messages, /v1/grok, /v1/openai, /v1/gemini), local tool execution happens on the client's machine, outside the HTTP round-trip Sentinel can actually see. By the time an install command and its result reach Sentinel as conversation history, the install has already run. On those routes SlopScan is detection-and-notification after the fact, not prevention, and I'm not going to pretend otherwise. It appends a warning to the tool result telling the model the package it just used failed the registry check, so the model can flag it to the user and stop relying on its output.

The incident above, though, is exactly the scenario where the direct /v1/scrub endpoint is the right tool: the recommendation happens, gets scrubbed, and the developer sees the risk flag before they ever type an install command. That's the checkpoint that would have made this catch automatic instead of dependent on someone doing manual due diligence.

What the Response Actually Looks Like

Illustrative example, not the exact package name from the incident (which wasn't disclosed):

{
  "security": {
    "action_taken": "clean",
    "threat_score": 0.02,
    "package_scan": {
      "action": "flagged",
      "hits": [
        {
          "name": "fastapi-async-utils",
          "ecosystem": "pypi",
          "trust_score": 0,
          "risk": "SUSPICIOUS",
          "flags": ["not_in_registry"]
        }
      ]
    }
  },
  "safe_payload": "You could use the `fastapi-async-utils` package for this..."
}
Enter fullscreen mode Exit fullscreen mode

Notice action_taken is "clean" at the top level, threat score near zero. This wasn't a prompt injection or a jailbreak attempt. It's the package_scan block that carries the actual signal here, and it's intentionally a separate field, since a completely benign, non-adversarial prompt can still recommend a dangerous package. Your integration needs to check both fields, not just action_taken, or you'll ship code that reads "clean" while ignoring the part of the response that actually mattered.

If you're running Claude Code or a similar coding agent, the SlopScan check is also available as a local pre-execution hook — one of three in sentinel-hook-pack, a free bundle of Claude Code hooks that blocks the install before it happens locally, rather than just flagging after the fact. The other two redact secret-shaped tokens before they're written to disk and block reads of files like .env or private keys — worth wiring in regardless of what proxy setup you're running, since it closes the specific gap the agentic proxy can't.

One Thing to Do Today

If your team uses an AI coding assistant for dependency recommendations (and at this point, whose doesn't), don't rely on someone remembering to manually check the registry page for every new package name an LLM suggests. That worked once, in this incident, because someone was paying attention. It will not work every time, for every engineer, under every deadline.

Put an automated check between "LLM suggests a package" and "developer runs the install." Whether that's Sentinel's SlopScan, a local pre-execution hook, or something you build yourself, the point is that the check needs to happen regardless of whether the human reviewing the PR happens to notice.


Sentinel is an AI firewall that sits between your application and your LLM, scanning for prompt injection, data exfiltration, and now hallucinated packages before they reach a developer or an agent's next action. Self-hosted or SaaS. Check it out at sentinelaifirewall.com.

Sources


AI-assisted draft or imaging, human-curated, reviewed and edited.

Top comments (0)