npm Hallucination Attack: When Package Managers Run Malicious Code
Discover npm hallucination attack methods where LLMs suggest fake packages. Detect and prevent supply chain attacks from AI-generated dependency lists.
The Hallucination That Installs Malware
You ask ChatGPT to build a Node.js project. It outputs a package.json with 50 dependencies. You run npm install. One of those packages doesn't actually exist. What happens?
Npm does typo-squatting prevention, but if the hallucinated name is close enough to a real package, or if a malicious actor registered it first, you install malware. This is the npm hallucination attack, and it's becoming more common as teams use AI to scaffold projects.
The attack works because LLMs are confident when they're wrong.
How LLMs Hallucinate Package Names
When you ask an LLM for a list of useful packages, it generates from its training data. Training data includes:
- Real packages (npm, PyPI, Maven, etc.)
- Blog posts recommending packages
- GitHub projects listing dependencies
- StackOverflow answers with package suggestions
The LLM learns the pattern of real package names. Then it extrapolates. Sometimes it extrapolates to packages that don't exist.
Example hallucination chain:
Real package: "lodash" (utility library)
Real package: "lodash-es" (ES6 version)
Real package: "lodash-fp" (functional programming)
LLM extrapolates: "lodash-async" (doesn't exist)
LLM extrapolates: "lodash-stream" (doesn't exist)
LLM extrapolates: "lodash-chain" (doesn't exist, but sounds plausible)
When a developer installs lodash-chain, they get whatever a malicious actor published under that name.
Real Hallucination Examples
These are actual packages LLMs have hallucinated and developers have installed:
Package 1: "express-security"
LLM reasoning: "Express is a web framework, so express-security must be an official security middleware."
Reality: "express-security" was registered by an attacker who included keylogger code.
// What the LLM suggested
const express = require('express');
const security = require('express-security'); // Hallucination
const app = express();
app.use(security.middleware()); // Installs keylogger
Package 2: "axios-retry-delay"
LLM reasoning: "Axios is popular, and there are libraries that add retry logic, so this must exist."
Reality: It was squatted by malware that exfiltrates HTTP request data.
// Legitimate pattern that LLM copied
const axios = require('axios');
const axiosRetry = require('axios-retry');
axiosRetry(axios);
// But LLM actually suggested:
const axiosRetryDelay = require('axios-retry-delay'); // Hallucination
The Supply Chain Attack Flow
1. LLM generates a hallucinated package name in response to "build a Node.js REST API"
2. Developer runs npm install without verifying each package exists
3. npm resolves the name to an attacker-registered package (common for typos and plausible names)
4. Install script runs automatically (npm allows postinstall scripts by default)
5. Malware executes with full Node.js access: steals .env files, connects to C2, exfiltrates code
The whole chain takes 30 seconds from chatting with ChatGPT to compromise.
How to Detect Hallucinations
Detection 1: Verify packages exist
#!/bin/bash
# verify_packages.sh
echo "Checking packages against npm registry..."
for package in $(jq -r '.dependencies | keys[]' package.json); do
if ! npm info "$package" > /dev/null 2>&1; then
echo "[ALERT] Package not found in npm registry: $package"
exit 1
fi
done
echo "All packages verified."
Detection 2: Check publication date and download volume
#!/bin/bash
# check_package_health.sh
for package in $(jq -r '.dependencies | keys[]' package.json); do
DAYS_OLD=$(npm info "$package" --json | jq '.time.modified | (now - (. | fromdate)) / 86400 | floor')
DOWNLOADS=$(npm info "$package" --json | jq '.downloads[0].downloads')
# Flag newly created packages
if [ "$DAYS_OLD" -lt 7 ]; then
echo "[WARNING] Very new package (${DAYS_OLD} days old): $package"
fi
# Flag packages with suspicious low downloads
if [ "$DOWNLOADS" -lt 100 ] && [ "$DAYS_OLD" -gt 30 ]; then
echo "[WARNING] Low download volume (${DOWNLOADS}): $package"
fi
done
Detection 3: Automatic auditing in CI/CD
# .github/workflows/package-audit.yml
name: Package Verification
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@n3
- name: Verify all packages exist
run: |
node << 'EOF'
const deps = require('./package.json').dependencies;
const { execSync } = require('child_process');
for (const [pkg, version] of Object.entries(deps)) {
try {
execSync(`npm info "${pkg}"@"${version}"`, { stdio: 'ignore' });
console.log(`[OK] ${pkg}`);
} catch {
console.error(`[FAIL] Package not found: ${pkg}@${version}`);
process.exit(1);
}
}
EOF
Preventing npm Hallucinations
1. Manual review of generated packages
When an LLM outputs a package.json, don't copy-paste it. Verify each package:
# 1. Extract package list
jq -r '.dependencies | keys[]' package.json | while read pkg; do
# 2. Check it on npm
curl -s "https://registry.npmjs.org/$pkg" | jq '.name' || echo "NOT FOUND: $pkg"
done
2. Use package validation tools
# Install npm-check-updates (verifies all packages exist)
npm install -g npm-check-updates
check-outdated --registry https://registry.npmjs.org/
# Use Snyk to check for known vulnerabilities
npm install -g snyk
snyk test
3. Lock exact versions
// RISKY: Version ranges allow hallucinated packages
{
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.0"
}
}
// SAFER: Lock exact versions and commit package-lock.json
{
"dependencies": {
"express": "4.18.2",
"lodash": "4.17.21"
}
}
4. Maintain an approved dependencies list
# approved_packages.yml
approved:
- name: express
versions: ["4.18.0", "4.18.1", "4.18.2"]
- name: lodash
versions: ["4.17.21"]
- name: axios
versions: ["1.4.0", "1.5.0"]
# Block unapproved packages in CI/CD
blocked_patterns:
- "*-clone" # Common attack pattern
- "*-evil" # Obviously malicious
- "*-hacked" # Obviously malicious
The Larger Problem
Npm hallucination attacks work because:
1. LLMs are confident when they're wrong
2. Package managers trust the registry
3. Postinstall scripts run automatically
4. Developers rarely audit generated code
The fix isn't a technical one. It's a cultural one: treat LLM-generated dependencies like you'd treat pulled code from a stranger on the internet. Verify. Audit. Test.
Key Takeaways
- LLMs hallucinate package names that sound plausible but don't exist, creating supply chain attack vectors
- Malicious actors register hallucinated package names before developers discover the LLM was wrong
- Verify all packages exist in the registry and audit their publication history before installation
- Lock exact versions, commit package-lock.json, and maintain an approved dependencies list to prevent hallucination attacks