
harsha31bs07-blip/dep-bouncer
Developer toolsnpm install, reimagined for AI agents: an MCP server that vets npm releases against the package's own history before an agent installs them
Overview
Add this live URL as a remote MCP server in your client: https://dep-bouncer-6ad22414-giberelin-heapify-global-community.app.nitrocloud.ai/mcp Then ask: *"Use the safe_install prompt, then add axios to this project."* Or replay the axios incident directly (Bash / zsh): (tested on the live server): - : verdict card rendered (screenshots below). - : claude mcp add --transport http dep-bouncer ; a real session called bouncer_check_package and bouncer_replay_incident, reported BLOCK with the evidence, and recommended a safe axios version. - : blocked the agent's own npm install of a non-existent package (guard; Cursor hook config included). - Any Streamable-HTTP MCP client via the URL (e.g. Cursor, NitroStudio, ChatGPT developer mode). Dep Bouncer is an MCP server an AI coding agent calls before it installs or upgrades an npm package.
README
Dep Bouncer
npm install, reimagined for AI agents.
▶ Watch the demo film (2:32): https://youtu.be/w9Ax5KPUGD8
Try it in 30 seconds
Add this live URL as a remote MCP server in your client:
https://dep-bouncer-6ad22414-giberelin-heapify-global-community.app.nitrocloud.ai/mcp
Then ask: “Use the safe_install prompt, then add axios to this project.”
Or replay the axios incident directly (Bash / zsh):
curl -X POST 'https://dep-bouncer-6ad22414-giberelin-heapify-global-community.app.nitrocloud.ai/mcp' \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2025-06-18' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"bouncer_replay_incident","arguments":{"incident":"axios"}}}'
Works with (tested on the live server):
- NitroChat: verdict card rendered (screenshots below).
- Claude Code as an MCP client:
claude mcp add --transport http dep-bouncer; a real session calledbouncer_check_packageandbouncer_replay_incident, reported BLOCK with the evidence, and recommended a safe axios version. - Claude Code guard hook: blocked the agent’s own
npm installof a non-existent package (guard; Cursor hook config included). - Any Streamable-HTTP MCP client via the URL (e.g. Cursor, NitroStudio, ChatGPT developer mode).
Dep Bouncer is an MCP server an AI coding agent calls before it installs or upgrades an npm package. It compares the release with the package’s own history (who published it, whether CI signed it, what runs at install time, which dependencies appeared, what code changed) and returns ALLOW / REVIEW / BLOCK with the evidence, plus the version to install instead.
- Live MCP endpoint:
https://dep-bouncer-6ad22414-giberelin-heapify-global-community.app.nitrocloud.ai/mcp - Demo video (2:32): https://youtu.be/w9Ax5KPUGD8
- Track: Developer Tools · Product reimagined: npm (
npm install/npm audit) - Built with NitroStack, deployed on NitroCloud.
The problem: agents install in seconds, advisories arrive in hours
AI coding agents now run npm install for us, mid-task, from the terminal. npm installs
whatever was published a minute ago, and npm audit only knows about published advisories.
For real hijacks, the free advisory database (OSV) needed hours to catch up:
| Hijack | Weekly downloads (Oct 2026) | First OSV entry after publish | What npm audit said meanwhile |
|---|---|---|---|
| axios 1.14.1 (Mar 2026) | 119 M | 2.9 h (MAL-2026-2307) | found 0 vulnerabilities |
| keyv 6.0.0 (Aug 2026) | 167 M | 1.7 h (MAL-2026-11524) | found 0 vulnerabilities |
| chalk 5.6.1 (Sep 2025) | 496 M | 4.0 h (MAL-2025-46969) | found 0 vulnerabilities |
| @ctrl/tinycolor 4.1.1 (Sep 2025) | 4.7 M | 3.8 h (MAL-2025-47141) | found 0 vulnerabilities |
| nx 21.5.0 (Aug 2025) | 10.8 M | 18.2 h (MAL-2025-41443) | found 0 vulnerabilities |
Lags measured from the npm registry’s publish times and OSV’s published field. Two of these
attacks went straight for AI agents: nx’s malware ran the victim’s claude, gemini and q
CLIs with permission checks disabled, and the keyv worm planted Claude Code SessionStart hooks.
Before / after
| Before: agent + npm | After: agent + Dep Bouncer | |
|---|---|---|
[email protected], 30 min after the hijack |
npm audit: found 0 vulnerabilities; postinstall of plain-crypto-js drops a RAT |
BLOCK: published by a personal account instead of the project’s CI; provenance gone; new dependency plain-crypto-js is 22 minutes old, copies the name crypto-js, runs a postinstall and is never imported by axios. Install [email protected] instead. |
[email protected] (valid provenance!) |
installs; preinstall runs an obfuscated loader |
BLOCK: new preinstall running a new file; obfuscated loader that spawns processes and downloads a runtime; 700 KB encrypted blob. Install 5.6.0. |
[email protected] today |
installs | ALLOW: consistent with its own history |
In a real agent (NitroChat, connected to the live server): “Replay the axios incident with Dep Bouncer and tell me what to install instead.”
Why this needs MCP
A verdict is not one lookup. For each request the agent’s tool call makes Dep Bouncer gather
the registry history (publishers, provenance, publish times), download and diff two tarballs,
look up every newly added dependency, check the name against popular packages, and query OSV.
Then the agent acts on the answer: it installs the safe version, asks the human (REVIEW),
or refuses (BLOCK). The safe_install prompt teaches any agent that loop.
Tools, resources, prompt, widget
| MCP surface | What it does |
|---|---|
bouncer_check_package |
Vet a release (or latest) before npm install. Verdict, evidence, what npm audit / OSV / a cooldown say, safe version. |
bouncer_check_upgrade |
Vet a version bump and show what changed between the two versions. |
bouncer_safe_version |
Newest version in a semver range that passes every check, and why newer ones were skipped. |
bouncer_check_manifest |
Vet every dependency of a package.json at the version npm would install today. Runs as an MCP Task with live progress. |
bouncer_review_install_scripts |
npm 12 asks you to approve install scripts but only shows the command. This explains each one: APPROVE / REVIEW / DENY. |
bouncer_replay_incident |
Replay a real hijack (axios, keyv, chalk, nx, tinycolor) as an agent met it, 30 minutes after publication. |
bouncer://policy |
Signals, weights and thresholds. |
bouncer://stats |
Live per-tool calls, errors, p50/p95 latency and verdict totals. |
incidents://list |
The replayable incidents and their OSV lag. |
safe_install (prompt) |
Rules that make an agent call Dep Bouncer before every dependency change and act on the verdict. |
verdict-card (widget) |
Visual verdict with evidence, the “what other checks say” panel and a one-click “use the safe version”. |
Agent guard: no install without a check
Agents forget to call tools. guard/ is a hook for Claude Code
(PreToolUse) and Cursor (beforeShellExecution) that intercepts the agent’s own terminal
commands (npm i, pnpm add, yarn add, bun add, npx, pnpm dlx, …), asks Dep Bouncer
about each package, and denies BLOCK, asks the user on REVIEW, and emits no
permission decision for ALLOW so the host keeps its own permission prompts.
Tested with a real headless Claude Code session asked to
npm install react-codeshift-utils-pro chalk: the hook denied the command (“does not exist on
npm”), the agent verified, did not work around the block, and installed only chalk.
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [
{ "type": "command", "command": "node /path/to/dep-bouncer/guard/dep-bouncer-guard.mjs" } ] } ] } }
How it decides
| Signal | Looks for | Caught |
|---|---|---|
publisher-change |
A personal account publishing where CI (npm trusted publishing) always did; a never-seen publisher | axios |
provenance-drop |
Previous releases were built and signed by CI; this one was not | axios, nx |
install-script |
A new or changed preinstall / install / postinstall |
keyv, nx, tinycolor |
new-deps |
New dependencies that are days old, run install scripts, copy a popular name, or are never imported (phantom) | axios |
code-diff |
Code new to this release: obfuscation, process execution, credential paths, exfiltration hosts, wallet hooks, AI-agent abuse (--dangerously-skip-permissions, SessionStart hooks), persistence, packed payloads |
chalk, keyv, nx, tinycolor |
release-timing |
Published in the last 24 h; bursts of versions | all (weak alone) |
name-check |
Look-alikes of ~2,400 popular names with few downloads or a recent creation date | typosquats, hallucinated names |
known-advisory |
OSV MAL- entries for the exact version |
anything already reported |
Weights: warn 15, high 35, critical 70. Any critical finding blocks; otherwise the sum
decides: < 30 ALLOW, 30–69 REVIEW, ≥ 70 BLOCK. Versions with incomplete checks require
at least REVIEW and are excluded from safe-version suggestions. Packages that don’t exist (hallucinated names),
versions npm removed, and npm’s -security placeholders are blocked outright.
Benchmark
npm run bench replays the five hijacks and checks the latest release of the 200 most
downloaded npm packages (presumed healthy; list in bench/packages.txt)
to measure false positives. Latest run (bench/RESULTS.md):
- Replayed hijacks: 5 / 5 BLOCK, none of which OSV knew about at the replay time.
- 200 popular releases: 199 ALLOW, 1 REVIEW, 0 BLOCK. The REVIEW is TypeScript 7, a full rewrite published by a new account: exactly the kind of release a human should glance at. REVIEW means “ask the human”, not “block”. (An earlier 60-package sample also caught fresh webpack/next/vitest releases as REVIEW; results move as npm does.)
Live endpoint smoke test: node scripts/smoke.mjs calls every tool, resource and prompt on
the deployed server, plus 17 hostile inputs (path traversal, __proto__, broken JSON, oversized
bodies, unknown versions). GitHub Actions runs it hourly and after every push; latest live
run passes all must-checks (p50 0.4 s, p95 2.3 s).
How we got here: the first run flagged webpack and vitest as BLOCK (bundlers rename chunk files every release). Two independent security reviews found 16 issues in the first review and 10 in the second (Oct 17); all findings were fixed. The fixes include line-based code diffs, guard decisions limited to deny/ask while preserving host permission prompts, parsing wrapped commands and subshells, checking every package, API-key support, REVIEW for incomplete checks (excluded from safe-version suggestions), resolving npm aliases, flagging git/URL specs, and always checking packages even when publisher names match. Numbers will move as npm does; CI re-runs the replays on every push.
How it compares (honestly)
| Tool | When it acts | Gap Dep Bouncer fills |
|---|---|---|
npm audit, Dependabot, OSV |
After an advisory is published | Advisories arrived 1.7–18 h after these hijacks |
| Socket Firewall Free, Aikido Safe Chain | At install, against the vendor’s malware feed | Blocklists: they block once the vendor has flagged a package. Dep Bouncer judges the release from its own history, explains why, and names the version to use. Complementary. |
Cooldowns (min-release-age, pnpm 11 default) |
Hold every release for N hours | Would have held all five hijacks, and every urgent security fix too. Dep Bouncer lets clean fresh releases through and shows cooldown status. |
| npm 12 script blocking | Skips install scripts until approved | Approval without explanation; does nothing for code that runs on import (chalk). |
| Provenance | Proves where a release was built | keyv 6.0.0 had valid provenance. |
| Socket GitHub app | On pull requests | Agents install from the terminal, not through PRs. |
Socket MCP depscore, slopsquat checkers |
Scores, fake names | Scores without evidence or a safe version; fake names only. |
Replayed incidents are labelled reconstructions
npm removed the malicious versions. Each replay uses the real last-good release (manifests,
files, publish times, fetched from the registry by scripts/build-fixtures.mjs) plus the
documented changes of the bad release, with benign stand-in code that only mimics the
structure of the attack. Every stand-in file starts with
/* Dep Bouncer reconstruction: benign stand-in, not real malware. */, and every replay result
says what is real and what was reconstructed, with sources. No malware was downloaded or stored.
Architecture
flowchart LR
Agent[AI coding agent] -- MCP --> Tools[bouncer tools / resources / prompt / widget]
Tools --> Bouncer[BouncerService]
Bouncer --> Registry[NpmRegistryService\npackuments, downloads]
Bouncer --> Tarball[TarballService\nin-memory tar.gz, size caps]
Bouncer --> OSV[OsvService]
Bouncer --> Analysis[AnalysisService]
Analysis --> Signals[8 signals] --> Verdict[verdict engine]
Bouncer --> Incidents[IncidentsService\nreplay fixtures]
src/
modules/registry/ npm registry, downloads, OSV clients; tar.gz parser; semver; TTL cache
modules/analysis/ signal contract, signals/*, code patterns, name similarity, verdict
modules/incidents/ reconstructed incident fixtures + replay service
modules/bouncer/ MCP tools, resources, prompt; orchestration service
health/ system, npm registry and OSV health checks
widgets/app/verdict-card/ the verdict widget
scripts/ build-fixtures, build-popular, benchmark, copy-assets
test/ node:test suites (signals, parser, replays, semver, ...)
NitroStack features used: DI modules and @Injectable, @Tool / @Resource / @Prompt,
@Widget, @RateLimit, @Cache, an interceptor (MetricsInterceptor), guards
(ApiKeyModule + @UseGuards), MCP Tasks (taskSupport, progress, cancellation),
@HealthCheck (system, npm registry, OSV), and the bouncer://stats resource for live
per-tool latency, errors and verdict totals.
Safety: Dep Bouncer never executes package code; tarballs come only from
registry.npmjs.org and are parsed in memory (25 MB compressed, 60 MB extracted, 5,000 files,
path-traversal checks), with byte-budgeted caches, a 4-way download semaphore and redirect
checks. No user data stored; metrics hold no request contents or IPs.
Authentication: the public demo is open on purpose (read-only, public data, rate-limited,
so judges and agents can try it). Set DEP_BOUNCER_API_KEYS=key1,key2 to require an API key
(NitroStack ApiKeyModule; clients send it in MCP _meta.apiKey). See .env.example.
Use it
Any MCP client (Claude, ChatGPT, Cursor, NitroChat): add a remote MCP server with the URL
above. Cursor ~/.cursor/mcp.json:
{ "mcpServers": { "dep-bouncer": { "url": "https://dep-bouncer-6ad22414-giberelin-heapify-global-community.app.nitrocloud.ai/mcp" } } }
Then ask: “Use the safe_install prompt, then add axios to this project.”
Run locally:
npm install
npm run build
npm test # 220 tests, offline (CI on Node 22 and 24)
npm run bench # replays + false-positive scan (needs network)
npm run dev # stdio, for NitroStudio
No environment variables are required. Optional (see .env.example): MCP_TRANSPORT_TYPE
(stdio / http / dual), PORT, NITRO_LOG_LEVEL.
Limitations and roadmap
- Heuristics: a determined attacker can write code that avoids the patterns; the publisher,
provenance, script and dependency signals still apply. Tarballs over 25 MB (e.g.
next) skip the code diff and require REVIEW for incomplete coverage. - npm only. Next: PyPI, lockfile/CI mode, org policies (e.g. “always REVIEW new publishers”).
Built with AI assistance
Harsha B S designed and directed the project. Claude Code and OpenAI Codex helped write and review code; every change was reviewed and tested. See the architecture walkthrough.
Team
Harsha B S (team Giberelin, Heapify Global Community) · MIT License
Install
This server does not publish a one-line install command.
Open the repository installation guideConfiguration
{ "mcpServers": { "dep-bouncer": { "url": "https://dep-bouncer-6ad22414-giberelin-heapify-global-community.app.nitrocloud.ai/mcp" } } }