# I built a static + runtime security scanner for MCP servers (Rust)

> Source: <https://dev.to/zaydmulani09/i-built-a-static-runtime-security-scanner-for-mcp-servers-rust-4498>
> Published: 2026-08-19 04:05:33+00:00

I built a static + runtime security scanner for MCP servers (Rust)

TL;DR: MCP (Model Context Protocol) has had 40+ CVEs disclosed just this year, and there wasn't a purpose-built scanner for it, so I built one. sentrymcp does static analysis (path traversal, injection, tool poisoning, missing auth) plus a runtime proxy mode that catches "rug pulls" — servers that silently change a tool's description after you've already approved it. Rust, MIT, Docker one-liner. Repo: [https://github.com/zaydmulani09/sentrymcp](https://github.com/zaydmulani09/sentrymcp)

I've been building MCP tooling for a while (a reverse proxy, some agent infra), and at some point I went looking for something to check my own servers for basic security mistakes before shipping them. There wasn't really anything.

That surprised me, because the CVE numbers aren't small. Endor Labs analyzed over 2,600 real MCP implementations and found 82% use file operations prone to path traversal, 67% use APIs related to code injection, and 34% use APIs susceptible to command injection. Separately, researchers have put the no-auth rate at around 38-40% of scanned servers — meaning close to 2 in 5 MCP servers in the wild have no authentication at all.

The vulnerability classes aren't exotic either. Anthropic's own reference implementation, `mcp-server-git`

, shipped a path traversal bug where a `repo_path`

argument was never validated against a configured boundary — the exact "developer forgot one check" pattern that shows up constantly in these audits.

So: static analysis for the code-level stuff, and since MCP has an attack class that literally can't exist in source code (a server that changes its tool description mid-session, after the user already approved the original), a runtime mode to catch that too.

| Category | What it detects | Example |
|---|---|---|
| Code vulnerabilities | Path traversal, command/shell injection, unsafe eval/code-injection sinks | Unvalidated file path args, string-interpolated `exec()` calls |
| Tool poisoning | Hidden instructions embedded in tool descriptions, unicode/homoglyph obfuscation | "ignore previous instructions" phrasing, Cyrillic lookalike characters hiding in description text |
| Auth & permissions | Missing auth on HTTP/SSE transports, hardcoded credentials, credentials leaked via logging |
`api_key = "sk-..."` as a literal, unauthenticated SSE endpoints |
| Runtime (proxy mode) | Tool description changes after initial approval ("rug pulls"), unexpected outbound connections | A tool's description silently changes between the first and second `tools/list` call in a session |

The tool-poisoning detection is the one I'm most interested in feedback on. MCP tool descriptions get read directly into the model's context as trusted content, so an attacker who controls a description can hide instructions the LLM will act on while the user only sees a benign-looking label. sentrymcp checks for imperative hidden-instruction phrasing, zero-width/bidi-control unicode, and homoglyph mixing (Latin text with Cyrillic or Greek lookalike characters slipped in).

Static scan:

```
mcpaudit scan ./some-mcp-server
```

Output is ranked by severity with a CWE or OWASP MCP Top 10 reference and a one-line remediation for each finding, split into three sections — code vulnerabilities, tool poisoning, and auth/permissions.

Runtime proxy mode, sitting between your MCP client and the real server:

```
sentrymcp proxy -- <your-server-command>
```

This baselines every tool definition it sees on session start and diffs every later `tools/list`

response against it. Any change gets flagged and logged as a JSON event, with a human-readable summary at session end.

Docker, no local Rust toolchain needed:

```
docker build -t sentrymcp .
docker run --rm -v $(pwd):/scan sentrymcp scan /scan
```

Rust workspace, four crates: a core scan engine, a rules crate that loads detection patterns from TOML files (so adding a new check doesn't require recompiling), a CLI, and the proxy. Rule-driven design was a deliberate choice — path traversal, injection, and tool-poisoning heuristics are all just declarative pattern definitions with a severity, a reference (CWE or OWASP MCP Top 10 id), and remediation text, which makes the ruleset easy to extend as new attack patterns show up.

`netstat`

, not a real sandbox).Repo's here: [https://github.com/zaydmulani09/sentrymcp](https://github.com/zaydmulani09/sentrymcp) — MIT licensed, contributions welcome. If you run MCP servers, I'd genuinely appreciate people trying the scanner against their own and telling me what it misses or false-positives on. The corpus of test cases is still small and real-world feedback is the fastest way to make the ruleset actually useful instead of just theoretically correct.
