# import-guardian

**Category:** 💻 Developer Tools  
**Repository:** https://github.com/Baneado98/import-guardian  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/import-guardian

## Description
Catch AI-hallucinated (slopsquatted) npm imports in generated code before npm install.

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "import-guardian": {
    "command": "npx",
    "args": ["-y","import-guardian"]
  }
}
```

## Documentation & README

# import-guardian 🪝

**Catch AI-hallucinated (slopsquatted) npm imports in generated code — _before_ you run `npm install`.**

An LLM just wrote some code. It might `import` a package that **doesn't exist** — a name the model confidently hallucinated. Attackers register exactly those names and ship malware. This is **slopsquatting**, and in 2026 it's one of the fastest-growing supply-chain attacks: code generators have been observed referencing **205,000+ unique non-existent package names**, and real hallucinated packages (`react-codeshift`, a fake `huggingface-cli` with 30k+ downloads) have already shown up in the wild.

`import-guardian` reads the generated code, extracts every npm package it imports, and tells you which ones are real — so an agent never installs a name its own model invented.

It's part of the guardian set: [**npm-guardian**](https://github.com/Baneado98/npm-guardian) audits a package you've _already chosen_ for CVEs and malicious behaviour, [**license-guardian**](https://github.com/Baneado98/license-guardian) audits the _licenses_ of your dependencies (GPL/AGPL/BUSL traps) before you ship, and [**lockfile-guardian**](https://github.com/Baneado98/lockfile-guardian) audits the resolved `package-lock.json` for integrity tampering and risky install scripts. `import-guardian` works one step earlier than all of them, at the moment the code — and its dependency names — are generated.

## What it catches

| | |
|---|---|
| 👻 **Hallucinated** | imports of packages that **do not exist on npm** — the model invented the name. Returns `BLOCK` with a "did you mean" to the closest real package. |
| 🪤 **Fresh squats** | packages that _do_ exist but were published days ago, have no source repo, near-zero downloads, or are a 1–2 edit typo of a popular library. Returns `REVIEW`. |
| 🧩 **Import-aware** | resolves `import x from "@scope/pkg/sub"` → `@scope/pkg`, `lodash/fp` → `lodash`; skips Node built-ins (`fs`, `node:path`) and local paths. Plain regex, no AST, **zero runtime deps beyond the MCP SDK**. |

Verdicts: 🟢 `CLEAN` · 🟠 `REVIEW` · 🔴 `BLOCK`.

## Use it as an MCP server (free)

```json
{
  "mcpServers": {
    "import-guardian": { "command": "npx", "args": ["-y", "import-guardian-mcp"] }
  }
}
```

Tools:

- **`scan_code_imports`** — give it a block of generated JS/TS; it extracts and checks every npm import. _Run this on code you just generated, before its install command._
- **`check_packages`** — verify an explicit list of package names.
- **`verify_package`** — deep-check a single package name (existence, age, versions, repo, weekly downloads, edit distance to popular packages).
- **`audit_lockfile`** 🔒 _(premium)_ — the deep one. Give it a lockfile from **npm** (`package-lock.json`), **yarn** (`yarn.lock`), **pnpm** (`pnpm-lock.yaml`), **PyPI** (`requirements.txt` / `poetry.lock` / `Pipfile.lock`) or **Go** (`go.mod` / `go.sum`) and it resolves the **whole transitive tree** and audits every package for **dependency confusion**, transitive **typosquats / homoglyph clones**, **maintainer-takeover** fingerprints, nonexistent pins, and **install-script** risk. See below.

## Deep lockfile audit (premium) 🔒

`scan_code_imports` looks at the handful of imports you can *see*. `audit_lockfile` looks at the **hundreds you can't** — the fully resolved dependency graph in your lockfile — and finds the supply-chain attacks an LLM cannot reason about from source alone:

| | |
|---|---|
| 🎯 **Dependency confusion** | a package the lockfile resolves from a **private / internal registry** whose name is **unclaimed on the public index** — an attacker can publish that exact name publicly and shadow it. The single highest-impact supply-chain bug, and impossible to detect without the resolved lockfile + a live public-registry cross-check. On **PyPI** it's sharper still: `pip install --extra-index-url` merges your private index with public PyPI and installs the **highest version from either** (the 2021 attack that hit Microsoft / Apple / Tesla). Returns **critical**. |
| 🪞 **Transitive typosquats** | homoglyph/Unicode-confusable clones (`rеact` with a Cyrillic *e*), transpositions (`axois`→`axios`), and registry-specific squats (PyPI `python3-dateutil` vs `python-dateutil`, normalized per PEP 503) across the **entire tree** — matched against a large per-ecosystem corpus of high-traffic packages. |
| 🕳️ **Maintainer takeover** | the fingerprint of a compromised maintainer: a **dormant package suddenly republished**, a sole-maintainer project, or a rapid republish burst — the pattern behind **event-stream**, **ua-parser-js**, **node-ipc** and PyPI's **ctx** hijack. |
| 💉 **Install-script version-diffing** | the sharpest injection signal: a package whose **newest release ADDED** a `postinstall`/`preinstall`/`install`/`prepare` hook that the **previous release did not have**. A previously-clean dependency that suddenly gains code-execution-on-install is exactly how event-stream and ua-parser-js shipped — caught by diffing the two releases' metadata on the live registry. Returns **critical**, and tells you the last clean version to pin. |
| 🔬 **Install-script PAYLOAD analysis** | goes past *whether* a hook exists to *what it does*: downloads the package **tarball**, gunzip+untars it **in-memory** (no deps), resolves the lifecycle command to its entry file, and statically analyzes the **actual code** for malware behavior — network egress, credential/env exfil, child-process exec, download-and-run, obfuscation. An **obfuscation-resistant AST/token analyzer** decodes `\x`/`\u`/octal escapes and folds split-string concatenation, so a sink hidden as `require("child_pro"+"cess")` or `global["ev"+"al"](…)` is still caught. The payload lives inside a binary artifact the buyer's agent never fetches — that's the access+compute moat. |
| 🐍 **PyPI sdist `setup.py` payload analysis** | the PyPI analogue of the npm tarball pass, and the sharpest PyPI surface: a **source distribution** runs `setup.py` — arbitrary Python — at **`pip install` time, before any of your code**. We download the **sdist** (anti-DoS capped), untar it in-memory, and statically analyze `setup.py` / `setup.cfg` / `pyproject.toml` for the stealer/loader pattern — `os.environ` exfil, `os.system`/`subprocess` exec, `.pypirc`/AWS/SSH credential theft, `urllib`/`requests` egress, `exec`/`eval`/`__import__` obfuscation, download-and-run, and a **non-standard build backend**. A **Python AST de-obfuscator** decodes `\x`/`\u`/octal escapes and folds split + implicit-adjacent strings, so `getattr(__import__("o"+"s"),"sys"+"tem")(…)` is still resolved. The `ctx` / `jeIlyfish` / `colourama` attack class — caught from an artifact the buyer's agent never fetches. **BLOCK is gated on the malware *combination*** (exfil/download-exec/packed-loader), so a legitimate C/Cython build that merely shells out to a compiler is reported as an informational note, **not** a false-positive block. |
| 🦠 **Known-malware / known-vuln (live OSV cross-reference)** | every resolved package is cross-referenced — at its **exact pinned version** — against the live **[OSV.dev](https://osv.dev)** advisory database (which ingests the OpenSSF **Malicious Packages** feed as `MAL-*` plus GitHub/PyPI/Go/crates/Maven/Packagist advisories). If the version your lockfile pins is documented **malware** (e.g. `event-stream@3.3.6`, `ctx@0.1.2`), that's a **critical / BLOCK** — the strongest possible signal. The mapping is **version-specific**: `event-stream@3.3.6` is malware, `3.3.4` is clean, and only a live database query can tell them apart. This is a different *kind* of moat from the static passes above — the buyer's model doesn't hold the advisory corpus and can't resolve version↔advisory. Queries are **batched, cached (TTL), concurrency-capped**, and degrade clean if OSV is unreachable (it never turns a clean package into a false positive). |
| 🏗️ **Local build-backend follow-hop** (PyPI) | when `pyproject.toml` declares a **non-standard build backend that ships *inside* the sdist** (`backend-path = ["."]`), the package runs its **own code** as the PEP 517 backend at every build/install. We **resolve that module to its file in the sdist and analyze its real source** (and follow `setup.py`'s local imports one hop), instead of merely flagging the backend name — closing the gap where a payload hides one indirection away from `setup.py`. |
| 🔀 **Go `replace` hijack** | a `replace … => host/path` in `go.mod` pointing at an **external host**, **live-verified** against the Go module proxy. If the replacement doesn't resolve (a private / attacker host), the code your build actually compiles isn't the module you audited or anything `go.sum` can vouch for. |
| 💣 **Install-script & provenance risk** | packages in the resolved tree that run install scripts, brand-new pins, missing repos, and **near-zero real downloads** (verified against npm / pypistats / crates.io). |

**Seven ecosystems.** The same resolved-graph engine runs over **npm / yarn / pnpm**, **PyPI** (`requirements.txt`, `poetry.lock`, `Pipfile.lock`), **Go modules** (`go.mod`, `go.sum`), **Rust / Cargo** (`Cargo.lock`, `Cargo.toml`), **Java** (Maven `pom.xml`, Gradle `build.gradle` / `.kts` / `gradle.lockfile`) and **PHP / Composer** (`composer.json`, `composer.lock`) — each cross-checked against its own public index (npm registry, PyPI, the Go module proxy, crates.io, **Maven Central**, **Packagist**). On crates.io the matcher knows the `-`/`_` equivalence (`rustdecimal` vs `rust_decimal`); on Maven an internal `groupId:artifactId` unclaimed on Central is the dependency-confusion target; on Composer an internal `vendor/package` unregistered on Packagist while a private repo is merged with the public index is the confusion target, and a Packagist-**abandoned** package is flagged as a takeover risk. Large lockfiles are bounded: direct + private-source packages are always live-checked first, then transitive up to a cap, so one call never fans out to tens of thousands of requests.

```
POST /pro/audit   { "lockfile": "<contents of package-lock.json>", "filename": "package-lock.json" }
```

Pay per call via **x402** (USDC on Base) or a prepaid Stripe key. The free `POST /audit` returns the verdict and issue counts by severity, but **withholds which packages are affected and why** — that's the paid report. This deep engine runs **server-side only** and is never shipped in the npm package.

### Example

```
scan_code_imports({ code: 'import shift from "react-codeshift";\nimport React from "react";' })

🟠 REVIEW — 1 referenced package(s) look risky.
🟠 react-codeshift  (risk 57/100)
    • No source repository linked.
    • Only 1 published version.
    • Only 3 downloads in the last week despite being 158 days old.
🟢 react  (ok) — established package.
```

## Free HTTP API

```
POST /scan        { "code": "import x from 'reqeusts'\nimport y from 'lodash'" }
GET  /verify?name=express
```

Hosted at **https://import-guardian.vercel.app** · try [`/verify?name=express`](https://import-guardian.vercel.app/verify?name=express) vs [`/verify?name=reqeusts`](https://import-guardian.vercel.app/verify?name=reqeusts).

## Pay-per-call (x402)

The `/pro/*` routes are gated by [x402](https://x402.org). Your agent pays **$0.02 USDC** per call automatically — no sign-up, no API key. Payment settles on-chain (USDC on Base). The server holds **no private key**; it only declares a public receiving address.

```
POST /pro/scan    { "code": "…" }   # 402 → pay → result, no rate limit
```

## How it works (and its limits)

- Existence + freshness come straight from the live npm registry (`registry.npmjs.org`) and the download-stats API — these are **facts**, not guesses, which is the moat: an agent can't reliably know on its own whether a name it generated is real and trusted.
- "Did you mean" uses Levenshtein distance against a curated list of high-impact packages attackers impersonate.
- It does **not** execute or install anything (read-only), and it intentionally errs toward `REVIEW` rather than silently passing a brand-new lookalike. It is a guardrail, not a guarantee — pair it with [npm-guardian](https://github.com/Baneado98/npm-guardian) for behavioural/CVE auditing of packages you decide to keep.

## License

MIT.

