securityslopsquattingclaude-code

LLMs Hallucinated NPM Packages. Here's a Scavio Verifier

One in five LLM code suggestions reference packages that do not exist. Block them pre-install with a three-signal Scavio verifier.

6 min read

Lasso Security's research in 2025 pinned the LLM package hallucination rate at roughly 19.7%. Nearly one in five code suggestions references a package that does not exist in the registry. That was an academic result until 2026, when attackers started pre-registering the most common hallucinations as malicious npm packages and waiting for a developer to take the bait.

Several production startups have been compromised this year through a single npm install of a Claude- or Copilot-suggested package. CVE-grade defenses like Socket and Snyk only catch known-bad packages. Pure hallucinations slip past them because they are brand-new names.

Why CVE Scanners Miss This

CVE scanning compares installed packages against a database of known vulnerabilities. A hallucinated-then-squatted package might be two days old with no CVE filed yet. Socket's behavior scoring catches suspicious code after install; by then a postinstall script has already run.

The primitive we need is pre-install verification of whether a package name has a real community footprint. Hallucinated names fail this check even when they briefly exist as squatters.

The Three-Signal Verifier

Three signals, two Scavio API calls, two seconds per verification. If a package fails any signal, block the install and flag for review.

  1. Registry existence: does the name resolve on npm?
  2. SERP footprint: does Google index documentation (npmjs.com, github.com) for this name?
  3. Community mention: does Reddit or Stack Overflow reference this name in any recent discussion?

Hallucinated names almost always fail signals 2 and 3. Squatted malicious packages that briefly exist fail signals 2 or 3. Legitimate small packages pass all three.

The Verifier

import { Scavio } from 'scavio';
const scavio = new Scavio({ apiKey: process.env.SCAVIO_API_KEY });

export async function verifyNpmPackage(name: string) {
  const exists = await fetch(`https://registry.npmjs.org/${name}`).then(r => r.ok);
  if (!exists) return { safe: false, reason: 'not_in_registry' };

  const [serp, reddit] = await Promise.all([
    scavio.search({ query: `npm "${name}"` }),
    scavio.search({ platform: 'reddit', query: name })
  ]);

  const hasDocs = serp.organic_results?.some((r: { link: string }) =>
    r.link.includes('npmjs.com') || r.link.includes('github.com')
  );
  const hasMentions = (reddit.organic_results?.length || 0) > 0;

  if (!hasDocs && !hasMentions) {
    return { safe: false, reason: 'no_community_footprint' };
  }
  return { safe: true, hasDocs, hasMentions };
}

Wiring It Into Claude Code

Claude Code supports pre-bash hooks in its settings file. Drop the verifier into a pre-bash hook that intercepts npm installcommands:

JSON
{
  "hooks": {
    "pre-bash": "node ~/.claude/hooks/verify-npm.js"
  }
}

The hook parses the npm install X argument, runs the verifier, and exits non-zero if the check fails. Claude Code then refuses to run the command.

False Positives Matter

The verifier is tuned to minimize false negatives (missed malicious packages) at the cost of some false positives on genuinely obscure packages. If a real niche package has zero Reddit footprint and minimal SERP signal, the verifier flags it for manual review instead of outright blocking. That tradeoff is the right one for supply-chain safety.

Cost and Latency

Each verification: two Scavio calls at ~30 credits each, plus a local npm registry fetch. Total ~60 credits (about $0.003) and ~1.8 seconds. A team running hundreds of installs a day stays deep inside the $30/mo Scavio tier.

The full verifier source and Claude Code hook template are in the verify-llm-suggested-packages solution. Install it once per machine and your AI coding tools can never install a hallucinated package without a human in the loop.