# mcp-guard πŸ›‘οΈ [![CI](https://github.com/sainitish1609/mcp-guard/actions/workflows/ci.yml/badge.svg)](https://github.com/sainitish1609/mcp-guard/actions/workflows/ci.yml) [![Go Version](https://img.shields.io/github/go-mod/go-version/sainitish1609/mcp-guard)](https://golang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](go.mod) [![Transport](https://img.shields.io/badge/transport-stdio%20%2B%20HTTP%2FSSE-blue.svg)](#-http--sse-remote-servers) > **The local privacy firewall, prompt-injection shield, secret sanitizer, and token compressor for AI coding agents.** A zero-dependency security proxy for Model Context Protocol (MCP) servers. ![mcp-guard demo](assets/demo.gif) `mcp-guard` is an ultra-fast, zero-dependency Go binary that sits transparently between your editor (Claude Code, Cursor, VS Code, Copilot) and any [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server β€” over **stdio** or **HTTP/SSE**. It ensures your API keys, database credentials, and protected paths (`~/.ssh`, `.env`, `.git`) are **never leaked to cloud LLMs**, that malicious content **cannot hijack your agent**, and that autonomous tool execution **cannot touch what it shouldn't** β€” all while trimming token spend. --- ## πŸ’‘ Why mcp-guard? When AI agents run tools like `@modelcontextprotocol/server-filesystem` or `postgres-mcp`, they read raw files and query results straight off your machine. Three things go wrong: 1. **Secrets leak.** A file with AWS keys, JWTs, or DB passwords gets shipped verbatim to a cloud LLM. 2. **Agents get hijacked.** A file or web page can carry *hidden instructions* β€” invisible Unicode or "ignore all previous instructions" β€” that the model obeys (prompt injection / tool poisoning). 3. **Agents overreach.** An autonomous agent overwrites `~/.ssh/authorized_keys`, pipes `curl … | bash`, or reads 100 files in a burst. `mcp-guard` runs **locally** and fixes all three without breaking agent execution: - πŸ”’ **Zero-Trust I/O Inspection** β€” intercepts both requests and responses on every transport. - ⚑ **Zero-Dependency Go Binary** β€” pure standard library, sub-millisecond overhead. - πŸ”„ **Recoverable Guardrail Errors** β€” returns structured `isError: true` results so agents self-correct instead of crashing. - πŸ“Š **Visible Value** β€” a session summary shows exactly what it protected and how many tokens/dollars it saved. --- ## 🎯 Threat Model β€” what this does and doesn't replace `mcp-guard` is **defense-in-depth for the agent boundary**, not a replacement for good security hygiene. Being precise about that matters more than sounding impressive. **What it addresses** | Risk | How `mcp-guard` handles it | | --- | --- | | Sensitive data leaving your machine when an agent reads a file or query result | Redacted before it reaches the model | | Instructions injected into content your agent consumes (files, web pages, **tool descriptions**) | Hidden Unicode stripped, directives neutralized | | An agent writing to paths it was never meant to touch | Blocked, including via `../` traversal and symlinks | | A runaway or compromised agent enumerating your filesystem | Rate-limited, read-bursts flagged | | No record of any of the above | Every action audited to stderr (text or JSON Lines) | **What it explicitly does _not_ replace** - **Short-lived credentials.** If you can use STS / OIDC federation / SSO, do that first β€” it is the stronger control. Rotation shrinks the blast radius; `mcp-guard` reduces the chance of disclosure in the first place. They solve different halves, and the credential fix is the more important one. - **A secrets manager or least-privilege IAM.** A key that was never on disk cannot be read off disk. - **Reviewing what your agent actually does.** Guardrails constrain the blast radius; they do not make an unreviewed agent trustworthy. **Known limitations β€” read these before relying on it** - **Detection is heuristic.** Named-pattern secret matching is high-precision and masks by default; the entropy catch-all is lower-precision (it fires on integrity hashes and base64 fixtures) so it is **audit-only by default** and only masks when you opt in. Injection detection is signature-based. A novel credential format or a carefully-worded injection *will* get through β€” treat it as a layer, not a guarantee. - **It only sees traffic that flows through it.** An MCP server that makes its own outbound network calls (a `fetch`-style server, telemetry, a phone-home) is invisible to `mcp-guard`. It secures the client↔server channel, not the server's own egress. - **Request-side secret scanning warns, it does not block.** Some tools legitimately need credentials in their arguments, so blocking by default would break them. - **Compression can alter text.** It is off by default and skips read-for-edit tools, because rewriting a file the agent is about to patch corrupts the diff. - **It does not authenticate the MCP server.** A malicious server can still return wrong (if sanitized) answers. Injection defense reduces that risk; it does not eliminate it. > If you find a case where a real secret or injection payload slips through, that is a bug worth [opening an issue](https://github.com/sainitish1609/mcp-guard/issues) for β€” false negatives and false positives are both regressions. --- ## πŸ—οΈ Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ request β”‚ mcp-guard β”‚ request β”‚ β”‚ β”‚ Editor / MCP Client β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ MCP Server β”‚ β”‚ β”‚ β”‚ β”‚ β†’ guardrails Β· shell block β”‚ β”‚ β”‚ (filesystem, postgres, β”‚ β”‚ (Claude Code / Cursor β”‚ β”‚ β”‚ rate-limit Β· req-secrets β”‚ β”‚ β”‚ github, http, …) β”‚ β”‚ / VS Code / Copilot) │◄────────── β”‚ ← redact Β· entropy Β· inject β”‚ │◄────────── β”‚ β”‚ β”‚ response β”‚ β”‚ defense Β· compression β”‚ β”‚ response β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ stdio Β· HTTP / SSE Β· audit β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## ✨ Features ### 1. πŸ”‘ Secret & Credential Redaction (Server βž” Client) Scans **every string** in a tool result β€” including `structuredContent` mirrors and tool descriptions β€” for 12+ credential formats before they reach the LLM: * **Cloud & AI keys:** AWS (`AKIA…`), Anthropic, OpenAI, Stripe, Google, Slack, GitHub PATs. * **Database URIs:** masks the password in `postgres://user:pass@host`, `mongodb+srv://`, `redis://:pass@host` β€” even passwords containing `@`. * **Private keys & JWTs:** full-block masking for RSA/PEM keys and Bearer tokens. * **πŸ†• High-entropy catch-all (audit-only by default):** a Shannon-entropy pass flags *unknown-format* generated secrets that match no named pattern, using a character-class discriminator to avoid git SHAs, UUIDs, and file paths. Because the heuristic also fires on integrity hashes, base64 fixtures, and signed URLs, it **reports** by default (logging the detector and byte offsets) and only masks when you opt in with `--entropy-mask` or `--profile strict`. This keeps it from silently corrupting otherwise-valid structured data. ```env # Before mcp-guard: AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE DATABASE_URL=postgres://admin:P@ssw0rd123!@db.internal:5432/prod SESSION=nQ7wLp4sZa1cFd8gHj0tYuXk9mR2vB3E # After mcp-guard: AWS_ACCESS_KEY_ID=[REDACTED:aws-access-key] DATABASE_URL=postgres://admin:[REDACTED:uri-credentials]@db.internal:5432/prod SESSION=[REDACTED:high-entropy] # entropy match β€” masked under --entropy-mask / --profile strict; # audit-only (logged, not masked) by default ``` ### 2. 🧬 Prompt-Injection & Tool-Poisoning Defense πŸ†• Content coming back from a server (file bodies, web pages, even a **malicious server's own tool descriptions**) can carry instructions aimed at your agent. mcp-guard neutralizes both vectors: * **Hidden Unicode** β€” strips invisible "tag" characters (`U+E0000` block used to smuggle invisible ASCII), bidirectional-override controls, and zero-width spaces. Legitimate script/emoji joiners are preserved. * **Injection directives** β€” high-signal phrases like *"ignore all previous instructions"*, *"do not tell the user"*, or *"reveal your system prompt"* are replaced with a visible `[mcp-guard: neutralized-injection]` marker (or detect-only, your choice). ### 3. πŸ›‘οΈ Directory Guardrails & Write Protection (Client βž” Server) Blocks agents from modifying protected paths β€” through relative traversal (`../.ssh`) **and symlink escapes** πŸ†• (a `project/data β†’ ~/.ssh` link is resolved and caught): * **Protected directories (anywhere in the path):** `~/.ssh`, `.aws`, `.gnupg`, `.kube`, `.git`, `.env*` * **Sensitive files:** `id_rsa`, `id_ed25519`, `authorized_keys`, `.npmrc`, `.netrc`, `.pypirc`, `.dockercfg` * **Shell-script blocking:** refuses `*.sh`/`*.ps1`, `bash -c`, and `curl … | sh` patterns by default. * **πŸ†• Optional sensitive-read blocking:** hard-block *reads* of protected paths (default: allow the read and redact its contents instead). ### 4. 🚦 Exfiltration & Anomaly Guardrails πŸ†• A behavioral layer on top of per-call checks: * **Rate limiting** β€” throttle runaway or compromised agents past a calls-per-minute cap. * **Read-burst detection** β€” warns on a sudden spike of distinct file reads (a classic bulk-exfiltration signature). * **Outbound secret scanning** β€” warns when a *tool call's arguments* carry secret-shaped data, so a key the agent just read can't silently be forwarded to a phone-home server unnoticed. ### 5. ⚑ Context Token Compression (Opt-In) Strips redundant comments and whitespace to save context-window capacity, with a code-aware token estimator for accurate accounting. > *Compression automatically skips read-for-edit tools (`read_file`, `get_file_contents`) to preserve exact diff boundaries for safe file editing.* ### 6. πŸ“Š Session Summary & Structured Audit πŸ†• * On exit (and on `SIGUSR1`) mcp-guard prints a **summary**: secrets redacted by type, writes/reads/shell blocked, injections neutralized, tokens saved, and an **estimated `$` saved**. * All activity streams to **stderr** as human text or **JSON Lines** (`--log-format json`) for SIEM ingestion. stdout carries only the MCP protocol. ``` mcp-guard session summary secrets redacted 4 aws-access-key 1 uri-credentials 1 high-entropy 2 writes blocked 1 injections neutralized 3 tokens saved 1840 est. cost saved $0.0055 ``` ### 7. πŸŽ›οΈ Policy Profiles & Hot Reload πŸ†• * **Profiles** apply per-server strictness in one flag: `--profile strict|standard|permissive` (e.g. lock down a shell server, relax a read-only docs server). * **Hot reload** β€” send `SIGHUP` to re-read the config and swap policy live, without dropping the agent connection. --- ## πŸš€ Installation **Prebuilt binary** (no Go toolchain required) β€” grab it from the [latest release](https://github.com/sainitish1609/mcp-guard/releases/latest): ```bash # macOS (Apple Silicon) β€” swap darwin_arm64 for your platform curl -sSL https://github.com/sainitish1609/mcp-guard/releases/latest/download/mcp-guard_darwin_arm64.tar.gz | tar xz sudo mv mcp-guard /usr/local/bin/ ``` **With Go:** ```bash go install github.com/sainitish1609/mcp-guard/cmd/mcp-guard@latest ``` **From source:** ```bash git clone https://github.com/sainitish1609/mcp-guard.git cd mcp-guard go build -o mcp-guard ./cmd/mcp-guard ``` Builds for macOS, Linux, and Windows (amd64 + arm64). Every protection works on all platforms; the `SIGHUP`/`SIGUSR1` signal hooks are Unix-only, and the end-of-session summary still prints on exit everywhere. --- ## βš™οΈ Configuration & Integration ### Claude Code Wrap any standard MCP server command using `mcp-guard --`: ```bash claude mcp add postgres -- mcp-guard --profile strict --max-tokens 4000 -- npx -y @modelcontextprotocol/server-postgres ``` ### Cursor / VS Code (`.vscode/mcp.json`) ```json { "mcpServers": { "filesystem": { "command": "mcp-guard", "args": [ "--redact-secrets", "--scan-injection", "--block-shell", "--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/projects" ] } } } ``` ### 🌐 HTTP / SSE (remote servers) Protect a **remote** MCP server that speaks Streamable-HTTP/SSE β€” same pipeline, no child process: ```bash mcp-guard --profile strict --http-listen :8080 --http-upstream https://my-mcp-host.example/mcp ``` Point your client at `http://localhost:8080` and every JSON and SSE message is inspected in flight. --- ## πŸ“‹ Flag Reference | Flag | Default | Description | | --- | --- | --- | | `--profile` | *(none)* | Policy preset: `strict` \| `standard` \| `permissive` | | `--redact-secrets` | `true` | Mask API keys, tokens, and database passwords in results | | `--entropy-scan` | `true` | Flag high-entropy tokens matching no known pattern (audit-only unless `--entropy-mask`) | | `--entropy-mask` | `false` | Mask entropy findings instead of just auditing them (noisier: hashes, base64, signed URLs) | | `--scan-injection` | `true` | Strip hidden Unicode & neutralize prompt-injection directives | | `--neutralize-injection` | `true` | Rewrite detected directives (off = detect + log only) | | `--scan-requests` | `true` | Warn when outbound tool-call arguments contain secrets | | `--block-shell` | `true` | Block execution of shell scripts in exec-like tools | | `--block-sensitive-reads` | `false` | Block reads of protected paths (default: allow + redact) | | `--rate-limit N` | `0` | Max tool calls/min before throttling (0 = disabled) | | `--annotate-tools` | `true` | Append policy notices to `tools/list` so agents know boundaries | | `--compress` | `false` | Strip comments and blank lines from context (safe tools only) | | `--max-tokens N` | `0` | Approximate token budget cap per result block (0 = unlimited) | | `--protect-paths` | *(defaults)* | Comma-separated protected paths (overrides defaults) | | `--protect-names` | *(defaults)* | Comma-separated protected path segments (overrides defaults) | | `--allow-shell` | *(none)* | Comma-separated allow-list substrings for shell commands | | `--stats` | `true` | Print a session summary on exit and on `SIGUSR1` | | `--price-per-1k` | `0.003` | USD per 1K tokens for the cost-saved estimate | | `--log-level` | `info` | Verbosity: `silent` \| `error` \| `info` \| `debug` (**stderr**) | | `--log-format` | `text` | Log format: `text` \| `json` (JSON Lines) | | `--dry-run` | `false` | Log events without mutating the stream | | `--http-listen` | *(none)* | Run as an HTTP/SSE proxy on this address (e.g. `:8080`) | | `--http-upstream` | *(none)* | Upstream MCP server URL for HTTP/SSE mode | | `--config` | *(none)* | Path to a JSON config file (optional) | Signals: **`SIGHUP`** reloads config live Β· **`SIGUSR1`** prints an interim session summary. --- ## πŸ§ͺ Testing & Verification ```bash # Run unit and integration tests go test ./... -v # Run static analysis go vet ./... ``` Try it end-to-end against a real server: ```bash printf '%s\n%s\n' \ '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}' \ '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"read_text_file","arguments":{"path":"/path/to/a/.env"}}}' \ | mcp-guard --profile strict -- npx -y @modelcontextprotocol/server-filesystem /path/to ``` --- ## πŸ“„ License Distributed under the MIT License. See [`LICENSE`](LICENSE) for details.