Back to the catalog

io.github.Baneado98/import-guardian

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

Open source Open in the app JSON README (API)

About

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

Details

Kind
MCP servers
Topic
Developer tools
Publisher
baneado98
Origin
official
Category
ferramentas
Transport
local
Version
0.3.0
Last push
2026-06-26T07:54:12Z
Repository state
ativo
Language
Dockerfile
License
MIT
Added
2026-08-29 03:01:42
Updated
2026-08-29 03:01:42
Origin id
io.github.Baneado98/import-guardian

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.

More