badger#
Defense-in-depth tooling for LLM web-tool use. A pnpm workspace containing:
packages/fetch— Security tooling for LLM web fetches: MCP server, Claude hooks, OpenCode plugin. Prevention layer.packages/response/— Zero-dependency library that detects prompt-injection compromise in LLM conversations. Detection layer.packages/inspector/— Terminal UI to browse Claude Code and OpenCode session transcripts, flagging compromise inline. Inspection layer.
Quick start#
pnpm install
pnpm -r build
pnpm -r test # 205+ tests across workspace
fetch#
Security tooling for LLM web fetches. Organized by host:
fetch/src/
├── intercept-fetch/ # MCP fetch-tool pipeline (validate → fetch → sanitize → classify)
├── utils/ # shared: scan-tool-result, audit, session-budget
├── claude/ # Claude Code hooks (PreToolUse/PostToolUse)
└── opencode/ # OpenCode plugin (tool.execute.before/after)
Setup#
Install, then run the locally-installed init bin to wire your host (it
installs the one SDK that host needs and writes the matching config —
--project by default, --global for user-wide):
vlt add --ignore-scripts @usrrname/badger-fetch
./node_modules/.bin/badger-fetch-init --claude # MCP server + Pre/PostToolUse hooks
./node_modules/.bin/badger-fetch-init --opencode # OpenCode plugin
See packages/fetch for all flags.
Supply-chain posture#
badger is a security tool and is set up like one:
- No
npx <pkg>on-demand fetch. Install first, then run the bin fromnode_modules/.bin(above) — it executes the version you vetted, never one resolved-and-run from the registry on the spot.pnpm exec badger-fetch-initornpm exec --no-install -- badger-fetch-initwork too. - No install scripts. Every badger package ships zero
pre/post/installlifecycle scripts, so installing runs no code.--ignore-scriptskeeps that true for the dependency tree too. - Pinned, scripts-disabled SDK install. When
init --claudepulls the MCP SDK it runs<pm> add --ignore-scripts @modelcontextprotocol/sdk@<pinned>— exact version (read from this package'speerDependencies), no lifecycle code. If your package manager rejects--ignore-scripts,initaborts and prints the command rather than falling back to a scripts-enabled install.
MCP server (Claude Code / any MCP host)#
badger-fetch-init --claude registers this for you. To wire .mcp.json by hand:
{
"mcpServers": {
"badger-fetch": {
"command": "node",
"args": ["node_modules/@usrrname/badger-fetch/dist/index.js"],
"env": { "OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}" }
}
}
}
The MCP server needs the optional peer @modelcontextprotocol/sdk — init
installs it; by hand, run vlt add @modelcontextprotocol/sdk too. (The old
zero-install npx -y @usrrname/badger-fetch no longer works: package managers
don't fetch optional peers, so the server would crash with a missing SDK.)
Add "deny": ["WebFetch"] to force all fetching through fetch.
Claude hooks#
badger-fetch-init --claude --global wires these. To do it by hand, install
once (npm i -g @usrrname/badger-fetch) so the hooks skip the npx cold-start
tax, then point your hook config at the bins:
badger-fetch-pre # rate limiting (PreToolUse: WebFetch|WebSearch)
badger-fetch-post # injection scan (PostToolUse: WebFetch|WebSearch)
Configure via badger-hook-config.json or env vars:
| Variable | Default | Description |
|---|---|---|
BADGER_HOOK_MAX_WEB_REQUESTS |
20 |
Max web fetches per session |
OPENROUTER_API_KEY |
— | Enables LLM-based injection analysis |
OpenCode plugin#
Add to opencode.json:
{
"plugin": [
["@usrrname/badger-fetch/opencode", {
"maxWebRequestsPerSession": 20,
"auditLogPath": ".badger/findings.jsonl"
}]
]
}
The plugin hooks into tool.execute.before (rate limiting) and
tool.execute.after (injection detection + audit logging + stderr warnings).
What it blocks#
- Non-HTTPS schemes
- Private IPs (RFC1918), localhost, link-local
- Cloud metadata endpoints (169.254.169.254, metadata.google.internal)
- Raw IP addresses, IPv6 literals
- URLs with embedded credentials
- Rate limit exceeded
What it sanitizes#
- Strips
<script>,<style>tags and inline event handlers - Extracts text content from HTML
- Detects prompt injection (direct override, encoding, typoglycemia, spaced evasion)
- Truncates oversized responses without corrupting multibyte UTF-8
response#
Zero-dependency detection library for prompt-injection compromise.
pnpm -F ./packages/response build
pnpm -F ./packages/response test
| Function | What it does |
|---|---|
detectInjection(text) |
Matches known injection patterns after normalization (base64, zero-width, NFKC, homoglyph, typoglycemia) |
detectObfuscation(text) |
Flags obfuscation by presence (invisible chars, mixed script, encoded blobs) |
detectCompromise(input) |
Flags when assistant echoes an injection pattern from a tool result |
isWebContentTool(name) |
Host-agnostic tool name matching (case-insensitive, covers both hosts) |
inspector#
Terminal UI to browse Claude Code and OpenCode session transcripts.
node packages/inspector/src/index.ts # inspect current project
node packages/inspector/src/index.ts --follow # live-tail the active session
node packages/inspector/src/index.ts --list # list projects with transcripts
Reads ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl (Claude) or
~/.local/share/opencode/storage/ (OpenCode).
Testing#
pnpm -r test # all unit tests (vitest)
pnpm -F ./packages/fetch test # badger-fetch only
pnpm -F ./packages/inspector test # inspector only
Eval suites#
| Command | Suite | What it tests |
|---|---|---|
pnpm eval |
evals/injection/ |
LLM injection detection |
pnpm eval:mcp |
evals/mcp-blocking/ |
MCP server blocking + approve flow |
pnpm eval:sanitize |
evals/sanitization/ |
HTML stripping + injection flags |
pnpm eval:agent |
evals/agent/ |
Agent + MCP tool-use integration |