slopshield
v0.3.0
Published
Block AI-hallucinated and typosquatted npm packages before they install.
Maintainers
Readme
slopshield
Block AI-hallucinated and typosquatted npm packages before you install them.

AI coding assistants routinely suggest packages that don't exist (~1 in 5 recommendations) or are lookalikes of real ones. Because the hallucinated names recur, attackers pre-register them ("slopsquatting") so the install pulls malware. Dependabot and Snyk only catch known-vulnerable, known packages — they're blind to nonexistent-but-soon-malicious names. slopshield fills that gap with fast heuristics over public npm registry data, and — unlike static scanners that read your files after the fact — it can intercept the install before the package lands.
npm install -g slopshield
slopshield express react lodash # → all safe, exit 0
slopshield expresss reqeust # → flags lookalikes of express / requestOr run it once without installing: npx slopshield <pkg>.
How it works
Each package gets a risk verdict (safe / medium / high / critical, or unknown when the registry can't be reached) with plain-language reasons, from five heuristics:
- Nonexistent — not registered in npm (
critical). - New — first published within the last 30 days.
- Low downloads — near-zero weekly downloads.
- Lookalike — a near-miss (edit distance) of a much more popular package; the reason names the suspected target.
- Known-slop — matches a curated list of documented hallucination/typosquat names.
It is warn-by-default (a single heuristic never blocks) and fail-open (a registry outage yields unknown, never a false "safe"). A lookalike of an existing package warns at medium; it only escalates to a blocking high/critical when signals combine (e.g. lookalike and brand-new and near-zero downloads) or the name simply doesn't exist. Thresholds live in one file (src/config.ts) — the false-positive dial.
Measured on a curated corpus (npm run validate): 100% recall on nonexistent + known-typosquat names at the default gate, and 0 false positives across 34 of the most-installed npm packages.
How it compares
| | slopshield | Dependabot / Snyk | Static "slop" file scanners | |---|---|---|---| | Catches nonexistent / hallucinated names | ✅ | ❌ (only known packages) | partial | | Catches typosquat / lookalike names | ✅ | ❌ | partial | | Known-CVE / SCA / SBOM | ❌ (not the goal) | ✅ | ❌ | | Blocks at install time, before code lands | ✅ | ❌ (reports after) | ❌ (scans files) | | Runtime dependencies | zero | many | varies |
slopshield is complementary to Dependabot/Snyk — keep using them for known-CVE coverage — and it acts at the moment of npm install, not as an after-the-fact file scan.
Requirements
- Node.js 18+ (uses the built-in
fetch). - Zero runtime dependencies — a supply-chain tool should have the smallest possible attack surface.
Usage
# Check one or more package names
slopshield express react lodash # → all safe, exit 0
slopshield expresss reqeust # → flags lookalikes of express / request
# Scan a project's package.json (deps, devDeps, optional, peer) or a newline list
slopshield --file package.json
# Machine-readable output for CI
slopshield --file package.json --json
# Choose how strict the exit code is (default: high)
slopshield expresss --fail-on medium # exit 1 on medium or aboveOptions
| Option | Description |
|---|---|
| --file <path> | Read names from a package.json or a newline-delimited list |
| --json | Output a JSON array of verdicts (always the full set, plain, never colored) |
| --quiet | Print only flagged packages (hide safe), plus a one-line summary |
| --fail-on <level> | Exit non-zero at this level or above: safe|medium|high|critical|none (default high) |
| --no-color | Disable ANSI color |
| --version | Print the installed version |
| --help | Show help |
Exit codes: 0 = nothing at/above the fail-on level; 1 = a risky package was found; 2 = usage error. unknown verdicts never fail the run.
Summary line: scans of more than one package (and any --quiet run) end with a one-line tally, e.g. 6 checked — 1 critical, 2 high, 1 medium, 2 safe (all safe when nothing is flagged). Pair it with --quiet to keep CI logs to just the problems plus the count. The exit code is always computed over every package, regardless of --quiet.
Colored output: verdicts are color-coded (green safe → yellow medium → red high → bold-red critical → dim unknown) when writing to a terminal. Color is auto-disabled when output is piped or redirected, and honors the NO_COLOR and FORCE_COLOR environment variables plus the --no-color flag (precedence: --no-color > FORCE_COLOR > NO_COLOR > TTY). --json is always plain.
Install guard
Catch risky packages before they install:
# Gate by exit code (no install) — ideal for CI and shell hooks
slopshield guard express lodash # silent, exit 0
slopshield guard expresss --block # exit 1, names the typo'd target
# Wrap an install: pre-check, then run "npm install …" verbatim if it passes
slopshield install express --save-dev
slopshield install some-ai-suggested-pkg # warns (or blocks) before npm runs
# Transparent adoption: shadow npm so every "npm install" is auto-checked
eval "$(slopshield init-shell zsh)" # add to ~/.zshrc (supports bash|zsh|fish)Policy (warn by default; block is opt-in) lives in package.json:
{
"slopshield": {
"mode": "block",
"failOn": "high",
"allow": ["my-internal-pkg"]
}
}- warn (default): prints reasons; in an interactive terminal it asks before installing; in CI it proceeds.
- block: refuses to install anything at or above
failOn— npm is never spawned; risky packages belowfailOnare warnings. - Non-registry specifiers (git/url/file) and unparseable args pass through unchecked (fail-open).
- Not a sandbox: the guard blocks risky names before install; it does not sandbox a safe package's lifecycle scripts. npm-only for now (
guardworks for any package manager via shell integration).
GitHub Action
Add slopshield to CI in a few lines — it fails the job when a risky package is found:
name: slopshield
on: [pull_request]
permissions:
contents: read
jobs:
slopshield:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: khoi03/[email protected]
with:
fail-on: high # exit non-zero at this level or aboveBy default it scans package.json and prints only flagged packages plus a summary.
| Input | Default | Description |
|---|---|---|
| file | package.json | Manifest or newline list to scan (ignored when packages is set) |
| packages | — | Space-separated package names to scan instead of a file |
| fail-on | high | Exit non-zero at this level or above: safe|medium|high|critical|none |
| quiet | true | Print only flagged packages plus the summary line |
| version | latest | slopshield version to run (pin for reproducible CI, e.g. 0.2.0) |
| working-directory | . | Directory to run the scan in |
The action runs the published CLI via npx (no extra install). Pin version for reproducible runs.
AI-agent guard
AI coding agents are the biggest source of slopsquatting — they confidently suggest packages that don't exist. slopshield can sit between the agent and your registry so a hallucinated install is caught the moment it's proposed. Two integrations, both zero-dependency and built on the same engine:
MCP server
Expose a check_package tool to any MCP-capable agent, so it can verify a name before suggesting or installing it:
slopshield mcp # speaks MCP over stdioRegister it with your client. For Claude Code:
claude mcp add slopshield -- slopshield mcpOr add it to your MCP client config directly:
{
"mcpServers": {
"slopshield": { "command": "slopshield", "args": ["mcp"] }
}
}The agent calls check_package with one or more names and gets back a verdict (safe/medium/high/critical/unknown) and reasons for each.
Claude Code hook
Block risky installs automatically with a PreToolUse hook. Add this to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "slopshield hook", "timeout": 10 }]
}
]
}
}Now whenever the agent runs npm install …, slopshield checks the packages first. A high/critical package (e.g. a hallucinated name that doesn't exist) is denied, and the reason is fed back to the model so it can correct itself; a medium risk asks you to confirm; safe installs proceed silently. The threshold and an allowlist are read from package.json#slopshield.
The hook is best-effort defense-in-depth, and it fails open: anything it can't confidently parse is allowed rather than blocked. It resolves quotes and backslash escapes, but by design it does not resolve runtime shell indirection — command substitution ($(…), `…`) and variable expansion ($VAR) pass through unchecked — and it currently covers npm only (npx, yarn, and pnpm are on the roadmap). Pair it with the MCP server above for stronger coverage.
Development
npm test # run the test suite (Node's built-in runner; needs Node 23.6+)
npm run test:coverage # run with ≥80% coverage thresholds
npm run typecheck # tsc --noEmit
npm run build # bundle to dist/cli.js
npm run validate # measure recall / false-positive rate over the corpus (needs network)
npm run build:data # regenerate src/data/popular-packages.json (needs network)See CONTRIBUTING.md for the full guide and SECURITY.md to report a vulnerability.
