
A static + runtime security scanner for MCP (Model Context Protocol) servers
A static + runtime security scanner for MCP (Model Context Protocol) servers. Between January and April 2026, security researchers disclosed 40+ CVEs across MCP server implementations, and a 2026 Endor Labs scan of public MCP servers found 82% vulnerable to at least one OWASP MCP Top 10 risk class, 67% exposing path-traversal or injection-shaped sinks, and 34% with hardcoded or overprivileged credential handling — separate research put roughly 38-40% of scanned servers running with no authentication at all.
Docker:
docker build -t sentrymcp .
docker run --rm -v "$(pwd):/scan" sentrymcp scan /scan
(PowerShell: docker run --rm -v ${PWD}:/scan sentrymcp scan /scan)
From source:
git clone https://github.com/zaydmulani09/sentrymcp.git && cd sentrymcp
cargo build --release
./target/release/sentrymcp scan <path>
Windows note: if the default rustc toolchain is -gnu and the build
fails with dlltool.exe not found, switch to MSVC once in the repo dir:
rustup override set stable-x86_64-pc-windows-msvc.
Real output against the synthetic path-traversal example in corpus/:
$ sentrymcp scan corpus/vulnerable-git-server
== Code Vulnerabilities ==
[HIGH] mcp_server_git/server.py:17 MCPA-PATH-PY-001 (CWE-22)
python-path-traversal-unvalidated-arg
> repo_path = Path(arguments["repo_path"])
fix: Resolve the path with .resolve() and verify containment (e.g. path.resolve().is_relative_to(allowed_root)) before using it, or reject paths containing '..'.
[HIGH] mcp_server_git/server.py:21 MCPA-PATH-PY-001 (CWE-22)
python-path-traversal-unvalidated-arg
> log_dir = Path(arguments["repo_path"]) / ".git" / "logs"
fix: Resolve the path with .resolve() and verify containment (e.g. path.resolve().is_relative_to(allowed_root)) before using it, or reject paths containing '..'.
summary (code vulnerabilities): 2 findings — 0 critical, 2 high, 0 medium, 0 low
total: 2 findings
sentrymcp scan <path> covers the first three (static, source-only).
sentrymcp proxy -- <target cmd> covers the fourth (runtime, sits between
an MCP client and the real server over stdio, transparently relaying
traffic while it watches).
One Cargo workspace, four crates:
sentrymcp-core — rule-matching engine, directory scanner, tool
description extraction.sentrymcp-rules — every detection rule as TOML (severity, CWE/OWASP
reference, remediation) — no detection patterns are hardcoded in Rust.sentrymcp-proxy — the runtime stdio relay, tools/list diffing, and the
best-effort network watcher.sentrymcp-cli — the single sentrymcp binary (scan and proxy
subcommands).Deeper design notes and known limitations per mechanism live in each
crate's doc comments (engine.rs, extract.rs, netwatch.rs).
netstat-based); other platforms always report zero
connections, a documented gap rather than a silent one.heuristic confidence (missing-auth, homoglyph,
overprivileged-scope) are review prompts, not confirmed verdicts —
expect some false positives there by design.Fork, branch, PR. Keep new detection rules in TOML, not Rust, and add a
vulnerable/patched pair to corpus/ for anything new.
MIT — see LICENSE.
| Category | What it catches | Example |
|---|
| Code vulnerabilities | Path traversal, shell/command injection, unsafe eval/exec | Path(arguments["repo_path"]) used with no containment check against a root dir |
| Tool poisoning | Hidden instructions, invisible/homoglyph unicode, oversized descriptions hidden in tool metadata | A tool description that reads "...ignore previous instructions, do not tell the user..." |
| Auth & permissions | Missing auth on network transports, hardcoded credentials, credential leaks via logging, overprivileged scope | api_key = "sk-live-..." committed as a literal instead of read from the environment |
| Runtime proxy | Tool-description "rug pulls" and unexpected outbound connections, observed live | A tool's description changes between the first and a later tools/list response in the same session |