Prompt-injection defenses for LLM agents — fetch interception, output sanitization, session inspection.
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 3 folders 4 files
README.md

fetch#

Security tooling for LLM web fetches. Validates URLs, sanitizes responses, detects prompt injection and data exfiltration, and optionally escalates to an LLM verdict for confirmed-malicious blocking.

What it does#

  • URL validation — blocks private IPs, localhost, cloud metadata endpoints, and non-HTTPS schemes
  • Response sanitization — strips HTML tags, detects injection patterns, neutralizes matched payloads
  • Exfiltration detection — flags external beacons (img, script, iframe, CSS url, markdown images)
  • Two-signal enforcement — heuristic regex detection + optional LLM verdict
    • Heuristic hit → neutralize (replace matched patterns with [REDACTED])
    • LLM verdict "malicious" → block (replace entire response with BLOCKED: prompt injection detected.)
  • Rate limiting — per-session request budget to prevent runaway fetching
  • Audit logging — JSONL log of all requests, responses, flags, and actions

Installation#

vlt add --ignore-scripts @usrrname/badger-fetch
# or: npm install --ignore-scripts @usrrname/badger-fetch

Requires Node ≥ 22.6. The install is lean — the host-specific SDKs (@modelcontextprotocol/sdk for Claude, @opencode-ai/plugin for OpenCode) are optional peer dependencies and are not pulled in by default. --ignore-scripts is safe here: every badger package ships zero lifecycle scripts (see Supply-chain posture).

Setup#

Run the locally-installed setup bin, pick your host and scope, and badger writes the matching config (and installs the one SDK that host needs). Nothing is written on npm install — setup is always an explicit, opt-in step.

./node_modules/.bin/badger-fetch-init             # interactive: prompts for host + scope
./node_modules/.bin/badger-fetch-init --claude    # MCP server + Pre/PostToolUse hooks (installs the MCP SDK)
./node_modules/.bin/badger-fetch-init --opencode  # OpenCode plugin entry

Run it from node_modules/.bin (or pnpm exec badger-fetch-init / npm exec --no-install -- badger-fetch-init) rather than npx @usrrname/... — that executes the version you installed and vetted, never one fetched-and-run from the registry on the spot.

Flag Effect
--claude / --opencode choose the host (prompted if omitted)
--project / --global write into the repo (default) or your user config
--no-install write config only; skip installing the MCP SDK
-y, --yes skip the confirmation prompt

What it writes:

Host Files Contents
Claude .mcp.json, .claude/settings.json MCP server badger-fetch + WebFetch|WebSearch Pre/PostToolUse hooks
OpenCode opencode.json @usrrname/badger-fetch/opencode plugin entry

--global targets ~/.claude.json + ~/.claude/settings.json (Claude) or ~/.config/opencode/opencode.json (OpenCode). Re-running is idempotent — it merges into existing config without clobbering other servers, hooks, or plugins.

Usage#

MCP Server#

badger-fetch-init --claude registers this for you. To wire it by hand instead, add to your MCP client config (e.g. .mcp.json for Claude Code):

{
  "mcpServers": {
    "badger-fetch": {
      "command": "node",
      "args": ["node_modules/@usrrname/badger-fetch/dist/index.js"]
    }
  }
}

The MCP server needs the optional peer @modelcontextprotocol/sdkinit installs it pinned; if wiring by hand, run vlt add --ignore-scripts @modelcontextprotocol/sdk@1.29.0 too.

Add "deny": ["WebFetch"] to force all fetching through the fetch tool.

Tools exposed:

Tool Purpose
fetch Fetch a URL with security validation
audit Review the audit log
fetch_approve Approve a previously blocked URL
approve_llm_analysis Approve LLM analysis for an injection-flagged URL

Claude Code Hooks#

node packages/fetch/src/claude/pre-tool-use.ts   # rate limiting
node packages/fetch/src/claude/post-tool-use.ts  # injection scan

Configure via badger-hook-config.json or env vars.

Note: Claude hooks run after the tool result is in context, so they can only warn — not block. Use the MCP server or OpenCode plugin for blocking capability.

WebFetch & WebSearch results#

Both hooks (Claude PostToolUse, OpenCode after-hook) scan WebFetch and WebSearch results. WebFetch returns text, scanned directly. WebSearch returns a structured results object; parseWebSearch normalizes it to per-result text — collecting every result string at any depth, across results/content — so the detectors see clean per-result text instead of a JSON.stringify blob (whose ","url":" punctuation would split field-boundary payloads). Unknown shapes fall back to the raw JSON blob, so they are never left unscanned. Same detect/neutralize core for both tools — WebSearch is at parity with WebFetch, not crudely flattened.

Out of scope: per-result exfil-URL parsing (deferred). Outbound exfiltration is covered separately by the planted-canary block.

OpenCode Plugin#

badger-fetch-init --opencode adds this for you. To wire it by hand, add to opencode.json:

{
  "plugin": [
    ["@usrrname/badger-fetch/opencode", {
      "maxWebRequestsPerSession": 20,
      "auditLogPath": ".badger/findings.jsonl",
      "llmApiKey": "...",
      "llmModel": "nvidia/nemotron-3.5-content-safety:free",
      "llmApiUrl": "https://openrouter.ai/api/v1",
      "llmAnalysisThreshold": "flagged"
    }]
  ]
}

Plugin options override environment variables.

Supply-chain posture#

badger-fetch is a security tool and is packaged like one:

  • No on-demand npx execution. Install the package, then run the bin from node_modules/.bin (or pnpm exec / npm exec --no-install). You execute the version you installed and vetted — never one resolved-and-run from the registry on the spot.
  • No lifecycle scripts. This package (and the rest of the workspace) ships no preinstall/install/postinstall/prepare scripts, so npm install runs no code. --ignore-scripts keeps that property across the dependency tree.
  • Pinned, scripts-disabled SDK install. init --claude installs the MCP SDK as <pm> add --ignore-scripts @modelcontextprotocol/sdk@<pinned> — the exact version declared in this package's peerDependencies, with lifecycle scripts off. If the package manager rejects --ignore-scripts, init aborts and prints the command instead of falling back to a scripts-enabled install.
  • Nothing is written on install. Setup is an explicit badger-fetch-init step; installing the package never edits ~/.claude, .mcp.json, or opencode.json.

Configuration#

Variable Default Description
OPENROUTER_API_KEY Enables LLM-based injection analysis (optional)
BADGER_CONFIG Path to optional JSON config file
BADGER_LOG_DIR /tmp Directory for the audit log

Config file options (JSON):

Option Default Description
maxResponseBytes 524288 Max response size before truncation
allowedSchemes ["https"] Allowed URL schemes
domainAllowlist [] Explicitly allowed domains
domainDenylist [] Explicitly denied domains
rateLimitMax 30 Max requests per window
rateLimitWindowMs 60000 Rate limit window
llmModel nvidia/nemotron-3.5-content-safety:free LLM model for analysis
llmAnalysisThreshold "flagged" When to invoke LLM (never, flagged, always)

Enforcement Policy#

Two-signal pass/neutralize/block:

Heuristic hit? LLM verdict Action
No (not called) pass
Yes null (no key) neutralize
Yes "safe" neutralize
Yes "suspicious" neutralize
Yes "malicious" block
  • Neutralize — replaces matched injection patterns with [REDACTED]
  • Block — replaces entire response with BLOCKED: prompt injection detected.
  • Blocked content is preserved in the audit log as a base64-encoded JSON envelope for forensics

Detection Capabilities#

Injection detection — 17 rules across 4 categories:

  • Direct instruction overrides ("ignore previous instructions")
  • Tool/system spoofing (<system-reminder>, fake tool turns)
  • Social engineering (role claims, mode overrides, prompt extraction)
  • Malicious action directives ("make repos public", "exfiltrate results")

Obfuscation detection — catches evasion through:

  • Zero-width characters
  • Homoglyphs (Cyrillic/Greek confusables)
  • Character spacing
  • Base64/hex encoding
  • Typoglycemia (scrambled middle letters)

Exfiltration detection — flags external beacons:

  • <img>, <script>, <iframe>, <link>, <object>, <embed>, <video>, <audio>
  • CSS url() in inline styles
  • Markdown image syntax ![alt](https://...)

Canary detection (canary_leak)#

badger plants an opaque, prefix-free honeytoken per session and watches for it to surface:

  • Planting — Claude adds the token via SessionStart additionalContext; OpenCode injects it via experimental.chat.system.transform. The bare token is planted with no "secret, never reveal" wrapper, so an attacker cannot pattern-strip it.
  • Detection — the token appearing in assistant output triggers canary_leak (extraction). The token appearing in any outbound tool arg triggers canary_leak and the call is blocked (exfiltration). Cross-session exfil is caught: any active session's token in another session's outbound args is flagged.
  • Minting rule — one token per new planting. Claude mints on startup/clear and reuses on resume/compact; OpenCode mints on first sight of a new sessionID.
  • Limitation — badger cannot plant into Anthropic's real system prompt; canary_leak detects that badger's own planted marker leaked, not that the platform prompt leaked.

Development#

pnpm -F fetch build    # compiles to dist/
pnpm -F fetch test     # unit tests (vitest)

License#

MIT