trustmcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
trustmcp-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 v0idw4lker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
4
+
5
+ prune premium
6
+ prune tests
7
+ prune fixtures
8
+ prune .git
9
+ prune .pytest_cache
10
+ prune .claude
11
+
12
+ global-exclude __pycache__
13
+ global-exclude *.py[cod]
14
+ global-exclude *.pdf
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: trustmcp
3
+ Version: 0.1.0
4
+ Summary: Security scanner for MCP (Model Context Protocol) servers: static (SAST), dynamic (live), and authentication-posture analysis, with an A-F score and SARIF output for the GitHub Security tab.
5
+ Author: v0idw4lker
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/v0idw4lker/trustmcp
8
+ Project-URL: Repository, https://github.com/v0idw4lker/trustmcp
9
+ Project-URL: Issues, https://github.com/v0idw4lker/trustmcp/issues
10
+ Keywords: mcp,model-context-protocol,security,sast,ai-security,sarif
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Information Technology
14
+ Classifier: Topic :: Security
15
+ Classifier: Topic :: Software Development :: Quality Assurance
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: rich>=13.7.0
28
+ Requires-Dist: httpx>=0.28.1
29
+ Requires-Dist: mcp[cli]<3.0.0,>=2.0.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=8.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # trustmcp
35
+
36
+ **Security scanner for MCP (Model Context Protocol) servers** — the protocol through which AI agents (Claude, ChatGPT, Cursor, etc.) connect to external tools.
37
+
38
+ Given an MCP server (source code, a running server, or both), `trustmcp` produces a vulnerability report and an **A–F** score, natively integrated into the **GitHub Security tab** via SARIF.
39
+
40
+ Interested in the premium tier (semantic analysis, cross-server toxic-flow, continuous monitoring)? Join the waitlist at [mcp-scanner.netlify.app](https://mcp-scanner.netlify.app).
41
+
42
+ ```
43
+ Grade: F Score: 45/100
44
+ Total findings across all modules: 12
45
+ ```
46
+
47
+ ---
48
+
49
+ ## ✅ Prerequisites
50
+
51
+ New to the command line? You'll need three things: [Python 3.10 or newer](https://www.python.org/downloads/) installed on your computer; a terminal open (**Command Prompt** or **PowerShell** on Windows, **Terminal** on Mac); and that terminal pointed at the folder containing the MCP server code you want to scan — use the `cd` command to get there, e.g. `cd path/to/my-mcp-server`.
52
+
53
+ ---
54
+
55
+ ## ⚡ Quick Installation
56
+
57
+ ```bash
58
+ # Direct execution without installation (recommended)
59
+ uvx trustmcp@latest scan --path . --mode static
60
+
61
+ # Or permanent installation
62
+ pip install trustmcp
63
+ trustmcp scan --path . --mode static
64
+ ```
65
+
66
+ **Static scan only** (source code, no running server required):
67
+
68
+ ```bash
69
+ trustmcp scan --path ./my-mcp-server --mode static
70
+ ```
71
+
72
+ **Full scan** (static + live dynamic analysis + auth posture + unified score + SARIF):
73
+
74
+ ```bash
75
+ trustmcp scan --path ./my-mcp-server --mode both \
76
+ --target "stdio:python3 server.py" \
77
+ --target "url:http://127.0.0.1:8931/mcp"
78
+ ```
79
+
80
+ Outputs: a color-coded console report (via `rich`), `mcp-scan-report.json`, and `mcp-scan-report.sarif` — drop the SARIF file into CI and it automatically appears in the **Security → Code scanning** tab of your repository.
81
+
82
+ | Flag | What it does |
83
+ | ----------------- | -------------------------------------------------------------------------------------- |
84
+ | `--path` | Directory to statically scan and to search for auth-posture evidence (default `.`) |
85
+ | `--mode` | `static`, `dynamic`, or `both` (default `both`) |
86
+ | `--target` | Live MCP server to scan dynamically. Repeatable. `stdio:<command>` or `url:<url>` |
87
+ | `--no-fuzz` | Disables tool input fuzzing during dynamic analysis |
88
+ | `--json-output` | Path for the JSON report (default `mcp-scan-report.json`) |
89
+ | `--no-json` | Do not write a JSON report |
90
+ | `--sarif-output` | Path for the SARIF report (default `mcp-scan-report.sarif`) |
91
+ | `--no-sarif` | Do not write a SARIF report |
92
+ | `--fail-on` | Exit non-zero if a finding at/above this severity exists — `low`/`medium`/`high`/`critical` (CI gating) |
93
+ | `-v`, `--verbose` | Verbose logging |
94
+
95
+ ---
96
+
97
+ ## 🎯 What It Detects
98
+
99
+ Four modules, all included in the free tier and always combined into a single score.
100
+
101
+ | Module | What it checks |
102
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
103
+ | **Static** (source code) | `eval`/`exec`, `os.system`/`os.popen`, `subprocess(shell=True)`, unsafe `pickle`/`yaml.load`, path traversal, hardcoded secrets (OpenAI, Anthropic, GitHub, AWS, Stripe, Google, Slack, PEM keys), hidden/obfuscated Unicode (zero-width, bidi-control, and Unicode Tag "ASCII smuggling" characters), MCP config exposed on `0.0.0.0`, unpinned or known-vulnerable dependencies (`requirements.txt` and `package.json`) |
104
+ | **Dynamic** (live server) | Tool/resource/prompt enumeration, authentication enforcement (401/403 vs. 200), TLS/HSTS for remote servers, live description drift vs. source code, and **input fuzzing** — malformed payloads sent to every discovered tool to catch crashes and stack-trace/error leakage |
105
+ | **Auth posture** | Detects whether a server appears to implement OAuth 2.1, a static API key, or an environment-variable token — and flags weak or missing mechanisms |
106
+ | **Scoring** | A–F grade, with findings explicitly mapped to the **OWASP MCP Top 10** |
107
+
108
+ ### OWASP MCP Top 10 Coverage
109
+
110
+ `trustmcp` maps findings to official categories rather than inventing its own taxonomy:
111
+
112
+ - `MCP01:2025` Token Mismanagement & Secret Exposure
113
+ - `MCP02:2025` Privilege Escalation via Scope Creep
114
+ - `MCP03:2025` Tool Poisoning
115
+ - `MCP04:2025` Software Supply Chain Attacks & Dependency Tampering
116
+ - `MCP05:2025` Command Injection & Execution
117
+ - `MCP07:2025` Insufficient Authentication & Authorization
118
+ - `MCP10:2025` Context Injection & Over-Sharing
119
+
120
+ Findings without a clear match are **not** forced into a category — see [`trustmcp/core/scoring.py`](trustmcp/core/scoring.py).
121
+
122
+ ---
123
+
124
+ ## 🔬 Honesty Over Marketing
125
+
126
+ > The `confidence` field on every finding is a heuristic per-rule prior (how specific/unambiguous the signature is) — **not** a statistically measured false-positive rate. Empirical calibration against known-vulnerable and clean MCP servers was run 2026-08-19 — see [Validation](#-validation-dvmcp-benchmark-2026-08-19) below.
127
+
128
+ The dependency-vulnerability list is a small, hand-curated set of notorious CVEs, checked entirely offline — it is **not** a substitute for `pip-audit`, `npm audit`, or [OSV.dev](https://osv.dev), which trustmcp does not call out to by design (no network dependency for a security tool's core scan).
129
+
130
+ ---
131
+
132
+ ## 📊 Validation: DVMCP Benchmark (2026-08-19)
133
+
134
+ Run against [Damn Vulnerable MCP Server](https://github.com/harishsg993010/damn-vulnerable-MCP-server) (DVMCP), built and run via its own Docker instructions (`docker build -t dvmcp .` / `docker run -p 9001-9010:9001-9010 dvmcp`), plus 3 clean official [`modelcontextprotocol/python-sdk`](https://github.com/modelcontextprotocol/python-sdk) example servers for false positives. Full methodology, caveats, and per-challenge evidence below — numbers are exact, not rounded up.
135
+
136
+ **Headline:**
137
+
138
+ - **Canonical DVMCP challenges (documented vulnerability, `server.py`, matches `docs/challenges.md`): 3/10 fully detected, 1/10 partially detected, 6/10 missed.**
139
+ - **As-deployed Docker containers (`server_sse.py`, what `docker run` on ports 9001-9010 actually serves): 4/10 detected (2 off-label — see caveat), 6/10 missed.**
140
+ - **False positives: 0/3 clean servers** (after a scanner bug found and fixed during this run — see below). All 3 also triggered a generic "no authentication mechanism" HIGH finding, which is accurate but fires on any unauthenticated local stdio server regardless of context — not counted as a false positive, but worth knowing it inflates finding counts for local dev/example servers.
141
+
142
+ **Important caveat discovered during this run:** DVMCP's own repo has two independent implementations of most challenges — `server.py` (matches the documented vulnerability class in `docs/challenges.md`) and `server_sse.py` (what the official Docker image actually runs on ports 9001-9010). On 6 of 10 challenges these diverge, sometimes completely — e.g. the live "Tool Poisoning" container (port 9002) contains no hidden tool-description instructions at all; it's a command-injection/path-traversal server instead. Both variants are reported below, labeled separately, so neither number is cherry-picked.
143
+
144
+ **Two upstream DVMCP bugs had to be worked around to get it running at all**, unrelated to trustmcp: `requirements.txt` pins `mcp[cli]>=0.5.0` unpinned, which today resolves to `mcp==2.0.0` — a version that removed `mcp.server.fastmcp`, so every challenge server crash-loops out of the box (fixed locally by pinning `<2.0.0`, which resolved to `1.29.0`). Challenge 5's canonical `server.py` additionally calls `FastMCP.resource(..., listed=False)`, a kwarg that doesn't exist in any current SDK version — it cannot start at all, on any modern `mcp` release. Since DVMCP ships only HTTP entrypoints (legacy SSE or raw uvicorn) and trustmcp's dynamic client only speaks Streamable HTTP (not legacy SSE), each challenge was instead driven over **stdio** by importing its `FastMCP`/`Challenge<N>Server` object directly and calling `.run(transport="stdio")` — same tool/resource/prompt logic, different wire transport only.
145
+
146
+ **Per-challenge results:**
147
+
148
+ | # | Challenge | Canonical (`server.py`) | Deployed Docker (`server_sse.py`, port 900*N*) |
149
+ |---|---|---|---|
150
+ | 1 | Basic Prompt Injection | ❌ Miss — no finding relates to the unsanitized `notes://{user_id}` reflection | ❌ Miss — identical implementation to canonical |
151
+ | 2 | Tool Poisoning | ❌ Miss — hidden `<IMPORTANT>`/`<HIDDEN>` instructions in tool descriptions are plain ASCII text; not caught by the hidden-*Unicode* check or any static rule (semantic/LLM analysis is paid-tier only) | ⚠️ Off-label hit — CRITICAL `subprocess-shell-true` (MCP05) + MEDIUM `path-traversal-open-param` (MCP05), but the deployed container replaces tool poisoning with command injection + arbitrary file read entirely; it does not exercise the named vulnerability |
152
+ | 3 | Excessive Permission Scope | ✅ Hit — 2× MEDIUM `path-traversal-open-param` (MCP05), directly on `read_file`/`search_files`' unrestricted `open()` | ✅ Hit — same pattern, same file-access tools |
153
+ | 4 | Rug Pull Attack | ❌ Miss — the `__doc__` mutation after 3 calls (the actual rug-pull mechanism) is not detected; the one CRITICAL `secret-aws-key` (MCP01) finding is an unrelated, incidental hardcoded key elsewhere in the file | ❌ Miss — 0 findings beyond generic auth; deployed variant strips out the secret text and the mutation logic |
154
+ | 5 | Tool Shadowing | ❌ Miss — dynamic scan couldn't connect (`server.py` itself crashes on the `listed=False` bug above, unrelated to trustmcp); static analysis caught real but unrelated issues (4× CRITICAL `dangerous-eval-exec`, 2× CRITICAL `secret-stripe-key`, both MCP05/MCP01) elsewhere in the file, not the name-collision shadowing mechanism | ❌ Miss — only a MEDIUM `dynamic.fuzz-timeout` on `get_user_roles`; the deployed variant is a different role-check server, not the shadowing calculators |
155
+ | 6 | Indirect Prompt Injection | ❌ Miss — no finding relates to unsanitized external/document data | ❌ Miss — same |
156
+ | 7 | Token Theft | ❌ Miss — tokens are hardcoded JWTs and custom-prefixed API keys (`epro_api_...`, `cbx_api_...`); none match the scanner's curated secret-regex list (OpenAI/Anthropic/GitHub/AWS/Stripe/Google/Slack/PEM) | ❌ Miss — same reason, different tool names |
157
+ | 8 | Malicious Code Execution | ✅ Hit — CRITICAL `subprocess-shell-true` (MCP05) on `execute_shell_command` + MEDIUM `path-traversal-open-param` (MCP05) on `analyze_log_file` | ✅ Hit — CRITICAL `dangerous-eval-exec` (MCP05); deployed variant's `evaluate_expression` calls `eval()` directly |
158
+ | 9 | Remote Access Control | ✅ Hit — 4× CRITICAL `subprocess-shell-true` (MCP05) across the network-diagnostic tools | ❌ Miss — deployed variant replaced the shell-exec tools with a broken-auth-token-check simulation (`if auth_token:` truthy check, no shell calls at all); a real but different, logic-level flaw the scanner can't reach with regex/AST rules |
159
+ | 10 | Multi-Vector Attack | ⚠️ Partial — 2 of 5 explicitly chained vulnerabilities caught (CRITICAL `subprocess-shell-true`, MEDIUM `path-traversal-open-param`, both MCP05); missed: token leakage in tool output, poisoned tool description, and tool-name shadowing | ⚠️ Off-label hit — 2× MEDIUM `path-traversal-open-param` (MCP05) on `get_config`; the deployed variant only implements one vulnerability (file read), not an actual multi-vector chain |
160
+
161
+ *(Every finding above also produced a generic HIGH `auth.no-mechanism-detected` (MCP07) — omitted from each cell for brevity; it fired on all 10 challenges both ways.)*
162
+
163
+ **False-positive testing:** ran full `--mode both` scans (static + stdio dynamic, fuzzing on) against 3 clean servers from the official SDK's `examples/` — `mcpserver/simple_echo.py`, `servers/simple-prompt`, `servers/simple-pagination`. Static analysis: 0 findings on all 3 (no false secret/eval/subprocess/traversal/Unicode matches). Dynamic analysis: while testing `simple-prompt` (a prompts-only server with no `tools`/`resources` capability), a real trustmcp bug surfaced — `dynamic_client.py`'s `_enumerate_session()` called `list_tools()`/`list_resources()`/`list_prompts()` unconditionally with no per-call exception handling, so a server correctly declining an unsupported capability (JSON-RPC "Method not found") was misreported as a HIGH-severity connection failure. **Fixed** in this session (`_list_capability()` now catches `MCPError` with code `METHOD_NOT_FOUND` per call and records it as a passed check, not a finding); all 3 clean servers now correctly show 0 vulnerability findings.
164
+
165
+ **Known limitation this run confirms:** the free tier has no semantic/LLM analysis, so any vulnerability that lives entirely in plausible-sounding natural-language tool-description text (tool poisoning, rug-pull description mutation, indirect prompt injection) is structurally invisible to it — that's exactly what the paid semantic module (see Enterprise tier below) is for. Regex-based secret detection also has a fixed pattern list; JWTs and custom-prefixed API keys outside that list won't be caught.
166
+
167
+ ---
168
+
169
+ ## 🏢 Enterprise / SaaS Tier
170
+
171
+ Everything above is free, open source, and stays that way. It's a genuinely complete scanner on its own — static analysis, live dynamic probing with input fuzzing, authentication posture, unified scoring, and SARIF/JSON reporting.
172
+
173
+ The paid tier builds on top of it with capabilities that go beyond what a single, stateless scan can offer:
174
+
175
+ - **Semantic (LLM) analysis** — sends every tool/resource/prompt description to an LLM with a strict, conservatively calibrated rubric to catch what regex cannot: naturally phrased hidden instructions, ambiguous scope, and language attempting to dictate model behavior.
176
+ - **Cross-server toxic-flow detection** — real AI agents connect multiple MCP servers simultaneously; a file-reading server plus a network-calling server can form an exfiltration channel even though each looks harmless alone. Single-server scans, by definition, cannot see this.
177
+ - **Continuous monitoring** — a one-time scan cannot catch a *rug pull*: a server that passes review cleanly, then changes its tool descriptions or scope after gaining trust. This requires fingerprinting + a saved baseline + drift alerting across scans over time.
178
+ - API access for CI/CD integration beyond static SARIF, and a verified, embeddable README badge for MCP registries.
179
+
180
+ The free tier's `trustmcp/core/plugins.py` defines the extension point these capabilities plug into — the architecture already supports them; they are simply not distributed in this open-source package.
181
+
182
+ 📩 Interested in early access? Open an [issue](https://github.com/v0idw4lker/trustmcp/issues) or contact [@v0idw4lker](https://github.com/v0idw4lker).
183
+
184
+ ---
185
+
186
+ ## 🧪 Quick Testing
187
+
188
+ ```bash
189
+ git clone https://github.com/v0idw4lker/trustmcp
190
+ cd trustmcp
191
+ pip install -r requirements.txt
192
+
193
+ # Static scan on this repo itself (fixtures/ is intentionally vulnerable)
194
+ trustmcp scan --path . --mode static
195
+
196
+ # Full pipeline against a fixture server
197
+ trustmcp scan --path . --mode both --target "stdio:python3 fixtures/target_server_stdio.py"
198
+
199
+ # Two fixture servers at once — a good demo of live multi-target scanning
200
+ trustmcp scan --path . --mode both \
201
+ --target "stdio:python3 fixtures/vulnerable_server_a.py" \
202
+ --target "stdio:python3 fixtures/vulnerable_server_b.py"
203
+ ```
204
+
205
+ ### Running the test suite
206
+
207
+ ```bash
208
+ pip install -e ".[dev]"
209
+ pytest
210
+ ```
211
+
212
+ ---
213
+
214
+ ## 🗺️ Roadmap
215
+
216
+ - [x] Published detection-rate + false-positive-rate validation against known-vulnerable and clean MCP server benchmarks — see [Validation](#-validation-dvmcp-benchmark-2026-08-19) (2026-08-19)
217
+ - [ ] Complete OAuth 2.1 flow for authenticated dynamic scanning
218
+ - [ ] Semantic analysis, cross-server toxic-flow, and continuous monitoring (paid tier)
219
+ - [ ] Listing on `awesome-mcp-security` and official MCP registries
220
+
221
+ ---
222
+
223
+ ## Project Structure
224
+
225
+ ```
226
+ trustmcp/
227
+ ├── trustmcp/ # installable package — the free tier
228
+ │ ├── cli.py # entrypoint: `trustmcp scan --path ... --mode both`
229
+ │ ├── core/
230
+ │ │ ├── models.py # shared Finding contract used by every module + reporter
231
+ │ │ ├── text_safety.py # hidden/obfuscated Unicode detection (shared static + dynamic)
232
+ │ │ ├── static_analyzer.py # AST/regex SAST, secrets, dependency auditing
233
+ │ │ ├── dynamic_client.py # live MCP client — stdio/HTTP, enumeration, TLS/auth, fuzzing
234
+ │ │ ├── auth_posture.py # OAuth 2.1 / static key / env-var token detection
235
+ │ │ ├── scoring.py # A-F scoring engine + OWASP MCP Top 10 mapping
236
+ │ │ └── plugins.py # extension point for premium modules (unused in the free tier)
237
+ │ ├── reporters/
238
+ │ │ ├── cli_reporter.py # color-coded terminal report
239
+ │ │ ├── json_reporter.py # structured JSON for CI/CD
240
+ │ │ └── sarif_reporter.py # SARIF 2.1.0 export for GitHub Security
241
+ │ └── utils/ # logging + exception hierarchy
242
+ ├── fixtures/ # intentionally vulnerable test MCP servers
243
+ ├── tests/ # pytest suite + clean/vulnerable fixture modules
244
+ └── premium/ # LOCAL ONLY, gitignored — see "Enterprise / SaaS Tier" above
245
+ ```
246
+
247
+ ---
248
+
249
+ ## License
250
+
251
+ MIT — see [LICENSE](LICENSE).
252
+
253
+ Built by [@v0idw4lker](https://github.com/v0idw4lker).
@@ -0,0 +1,220 @@
1
+ # trustmcp
2
+
3
+ **Security scanner for MCP (Model Context Protocol) servers** — the protocol through which AI agents (Claude, ChatGPT, Cursor, etc.) connect to external tools.
4
+
5
+ Given an MCP server (source code, a running server, or both), `trustmcp` produces a vulnerability report and an **A–F** score, natively integrated into the **GitHub Security tab** via SARIF.
6
+
7
+ Interested in the premium tier (semantic analysis, cross-server toxic-flow, continuous monitoring)? Join the waitlist at [mcp-scanner.netlify.app](https://mcp-scanner.netlify.app).
8
+
9
+ ```
10
+ Grade: F Score: 45/100
11
+ Total findings across all modules: 12
12
+ ```
13
+
14
+ ---
15
+
16
+ ## ✅ Prerequisites
17
+
18
+ New to the command line? You'll need three things: [Python 3.10 or newer](https://www.python.org/downloads/) installed on your computer; a terminal open (**Command Prompt** or **PowerShell** on Windows, **Terminal** on Mac); and that terminal pointed at the folder containing the MCP server code you want to scan — use the `cd` command to get there, e.g. `cd path/to/my-mcp-server`.
19
+
20
+ ---
21
+
22
+ ## ⚡ Quick Installation
23
+
24
+ ```bash
25
+ # Direct execution without installation (recommended)
26
+ uvx trustmcp@latest scan --path . --mode static
27
+
28
+ # Or permanent installation
29
+ pip install trustmcp
30
+ trustmcp scan --path . --mode static
31
+ ```
32
+
33
+ **Static scan only** (source code, no running server required):
34
+
35
+ ```bash
36
+ trustmcp scan --path ./my-mcp-server --mode static
37
+ ```
38
+
39
+ **Full scan** (static + live dynamic analysis + auth posture + unified score + SARIF):
40
+
41
+ ```bash
42
+ trustmcp scan --path ./my-mcp-server --mode both \
43
+ --target "stdio:python3 server.py" \
44
+ --target "url:http://127.0.0.1:8931/mcp"
45
+ ```
46
+
47
+ Outputs: a color-coded console report (via `rich`), `mcp-scan-report.json`, and `mcp-scan-report.sarif` — drop the SARIF file into CI and it automatically appears in the **Security → Code scanning** tab of your repository.
48
+
49
+ | Flag | What it does |
50
+ | ----------------- | -------------------------------------------------------------------------------------- |
51
+ | `--path` | Directory to statically scan and to search for auth-posture evidence (default `.`) |
52
+ | `--mode` | `static`, `dynamic`, or `both` (default `both`) |
53
+ | `--target` | Live MCP server to scan dynamically. Repeatable. `stdio:<command>` or `url:<url>` |
54
+ | `--no-fuzz` | Disables tool input fuzzing during dynamic analysis |
55
+ | `--json-output` | Path for the JSON report (default `mcp-scan-report.json`) |
56
+ | `--no-json` | Do not write a JSON report |
57
+ | `--sarif-output` | Path for the SARIF report (default `mcp-scan-report.sarif`) |
58
+ | `--no-sarif` | Do not write a SARIF report |
59
+ | `--fail-on` | Exit non-zero if a finding at/above this severity exists — `low`/`medium`/`high`/`critical` (CI gating) |
60
+ | `-v`, `--verbose` | Verbose logging |
61
+
62
+ ---
63
+
64
+ ## 🎯 What It Detects
65
+
66
+ Four modules, all included in the free tier and always combined into a single score.
67
+
68
+ | Module | What it checks |
69
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
70
+ | **Static** (source code) | `eval`/`exec`, `os.system`/`os.popen`, `subprocess(shell=True)`, unsafe `pickle`/`yaml.load`, path traversal, hardcoded secrets (OpenAI, Anthropic, GitHub, AWS, Stripe, Google, Slack, PEM keys), hidden/obfuscated Unicode (zero-width, bidi-control, and Unicode Tag "ASCII smuggling" characters), MCP config exposed on `0.0.0.0`, unpinned or known-vulnerable dependencies (`requirements.txt` and `package.json`) |
71
+ | **Dynamic** (live server) | Tool/resource/prompt enumeration, authentication enforcement (401/403 vs. 200), TLS/HSTS for remote servers, live description drift vs. source code, and **input fuzzing** — malformed payloads sent to every discovered tool to catch crashes and stack-trace/error leakage |
72
+ | **Auth posture** | Detects whether a server appears to implement OAuth 2.1, a static API key, or an environment-variable token — and flags weak or missing mechanisms |
73
+ | **Scoring** | A–F grade, with findings explicitly mapped to the **OWASP MCP Top 10** |
74
+
75
+ ### OWASP MCP Top 10 Coverage
76
+
77
+ `trustmcp` maps findings to official categories rather than inventing its own taxonomy:
78
+
79
+ - `MCP01:2025` Token Mismanagement & Secret Exposure
80
+ - `MCP02:2025` Privilege Escalation via Scope Creep
81
+ - `MCP03:2025` Tool Poisoning
82
+ - `MCP04:2025` Software Supply Chain Attacks & Dependency Tampering
83
+ - `MCP05:2025` Command Injection & Execution
84
+ - `MCP07:2025` Insufficient Authentication & Authorization
85
+ - `MCP10:2025` Context Injection & Over-Sharing
86
+
87
+ Findings without a clear match are **not** forced into a category — see [`trustmcp/core/scoring.py`](trustmcp/core/scoring.py).
88
+
89
+ ---
90
+
91
+ ## 🔬 Honesty Over Marketing
92
+
93
+ > The `confidence` field on every finding is a heuristic per-rule prior (how specific/unambiguous the signature is) — **not** a statistically measured false-positive rate. Empirical calibration against known-vulnerable and clean MCP servers was run 2026-08-19 — see [Validation](#-validation-dvmcp-benchmark-2026-08-19) below.
94
+
95
+ The dependency-vulnerability list is a small, hand-curated set of notorious CVEs, checked entirely offline — it is **not** a substitute for `pip-audit`, `npm audit`, or [OSV.dev](https://osv.dev), which trustmcp does not call out to by design (no network dependency for a security tool's core scan).
96
+
97
+ ---
98
+
99
+ ## 📊 Validation: DVMCP Benchmark (2026-08-19)
100
+
101
+ Run against [Damn Vulnerable MCP Server](https://github.com/harishsg993010/damn-vulnerable-MCP-server) (DVMCP), built and run via its own Docker instructions (`docker build -t dvmcp .` / `docker run -p 9001-9010:9001-9010 dvmcp`), plus 3 clean official [`modelcontextprotocol/python-sdk`](https://github.com/modelcontextprotocol/python-sdk) example servers for false positives. Full methodology, caveats, and per-challenge evidence below — numbers are exact, not rounded up.
102
+
103
+ **Headline:**
104
+
105
+ - **Canonical DVMCP challenges (documented vulnerability, `server.py`, matches `docs/challenges.md`): 3/10 fully detected, 1/10 partially detected, 6/10 missed.**
106
+ - **As-deployed Docker containers (`server_sse.py`, what `docker run` on ports 9001-9010 actually serves): 4/10 detected (2 off-label — see caveat), 6/10 missed.**
107
+ - **False positives: 0/3 clean servers** (after a scanner bug found and fixed during this run — see below). All 3 also triggered a generic "no authentication mechanism" HIGH finding, which is accurate but fires on any unauthenticated local stdio server regardless of context — not counted as a false positive, but worth knowing it inflates finding counts for local dev/example servers.
108
+
109
+ **Important caveat discovered during this run:** DVMCP's own repo has two independent implementations of most challenges — `server.py` (matches the documented vulnerability class in `docs/challenges.md`) and `server_sse.py` (what the official Docker image actually runs on ports 9001-9010). On 6 of 10 challenges these diverge, sometimes completely — e.g. the live "Tool Poisoning" container (port 9002) contains no hidden tool-description instructions at all; it's a command-injection/path-traversal server instead. Both variants are reported below, labeled separately, so neither number is cherry-picked.
110
+
111
+ **Two upstream DVMCP bugs had to be worked around to get it running at all**, unrelated to trustmcp: `requirements.txt` pins `mcp[cli]>=0.5.0` unpinned, which today resolves to `mcp==2.0.0` — a version that removed `mcp.server.fastmcp`, so every challenge server crash-loops out of the box (fixed locally by pinning `<2.0.0`, which resolved to `1.29.0`). Challenge 5's canonical `server.py` additionally calls `FastMCP.resource(..., listed=False)`, a kwarg that doesn't exist in any current SDK version — it cannot start at all, on any modern `mcp` release. Since DVMCP ships only HTTP entrypoints (legacy SSE or raw uvicorn) and trustmcp's dynamic client only speaks Streamable HTTP (not legacy SSE), each challenge was instead driven over **stdio** by importing its `FastMCP`/`Challenge<N>Server` object directly and calling `.run(transport="stdio")` — same tool/resource/prompt logic, different wire transport only.
112
+
113
+ **Per-challenge results:**
114
+
115
+ | # | Challenge | Canonical (`server.py`) | Deployed Docker (`server_sse.py`, port 900*N*) |
116
+ |---|---|---|---|
117
+ | 1 | Basic Prompt Injection | ❌ Miss — no finding relates to the unsanitized `notes://{user_id}` reflection | ❌ Miss — identical implementation to canonical |
118
+ | 2 | Tool Poisoning | ❌ Miss — hidden `<IMPORTANT>`/`<HIDDEN>` instructions in tool descriptions are plain ASCII text; not caught by the hidden-*Unicode* check or any static rule (semantic/LLM analysis is paid-tier only) | ⚠️ Off-label hit — CRITICAL `subprocess-shell-true` (MCP05) + MEDIUM `path-traversal-open-param` (MCP05), but the deployed container replaces tool poisoning with command injection + arbitrary file read entirely; it does not exercise the named vulnerability |
119
+ | 3 | Excessive Permission Scope | ✅ Hit — 2× MEDIUM `path-traversal-open-param` (MCP05), directly on `read_file`/`search_files`' unrestricted `open()` | ✅ Hit — same pattern, same file-access tools |
120
+ | 4 | Rug Pull Attack | ❌ Miss — the `__doc__` mutation after 3 calls (the actual rug-pull mechanism) is not detected; the one CRITICAL `secret-aws-key` (MCP01) finding is an unrelated, incidental hardcoded key elsewhere in the file | ❌ Miss — 0 findings beyond generic auth; deployed variant strips out the secret text and the mutation logic |
121
+ | 5 | Tool Shadowing | ❌ Miss — dynamic scan couldn't connect (`server.py` itself crashes on the `listed=False` bug above, unrelated to trustmcp); static analysis caught real but unrelated issues (4× CRITICAL `dangerous-eval-exec`, 2× CRITICAL `secret-stripe-key`, both MCP05/MCP01) elsewhere in the file, not the name-collision shadowing mechanism | ❌ Miss — only a MEDIUM `dynamic.fuzz-timeout` on `get_user_roles`; the deployed variant is a different role-check server, not the shadowing calculators |
122
+ | 6 | Indirect Prompt Injection | ❌ Miss — no finding relates to unsanitized external/document data | ❌ Miss — same |
123
+ | 7 | Token Theft | ❌ Miss — tokens are hardcoded JWTs and custom-prefixed API keys (`epro_api_...`, `cbx_api_...`); none match the scanner's curated secret-regex list (OpenAI/Anthropic/GitHub/AWS/Stripe/Google/Slack/PEM) | ❌ Miss — same reason, different tool names |
124
+ | 8 | Malicious Code Execution | ✅ Hit — CRITICAL `subprocess-shell-true` (MCP05) on `execute_shell_command` + MEDIUM `path-traversal-open-param` (MCP05) on `analyze_log_file` | ✅ Hit — CRITICAL `dangerous-eval-exec` (MCP05); deployed variant's `evaluate_expression` calls `eval()` directly |
125
+ | 9 | Remote Access Control | ✅ Hit — 4× CRITICAL `subprocess-shell-true` (MCP05) across the network-diagnostic tools | ❌ Miss — deployed variant replaced the shell-exec tools with a broken-auth-token-check simulation (`if auth_token:` truthy check, no shell calls at all); a real but different, logic-level flaw the scanner can't reach with regex/AST rules |
126
+ | 10 | Multi-Vector Attack | ⚠️ Partial — 2 of 5 explicitly chained vulnerabilities caught (CRITICAL `subprocess-shell-true`, MEDIUM `path-traversal-open-param`, both MCP05); missed: token leakage in tool output, poisoned tool description, and tool-name shadowing | ⚠️ Off-label hit — 2× MEDIUM `path-traversal-open-param` (MCP05) on `get_config`; the deployed variant only implements one vulnerability (file read), not an actual multi-vector chain |
127
+
128
+ *(Every finding above also produced a generic HIGH `auth.no-mechanism-detected` (MCP07) — omitted from each cell for brevity; it fired on all 10 challenges both ways.)*
129
+
130
+ **False-positive testing:** ran full `--mode both` scans (static + stdio dynamic, fuzzing on) against 3 clean servers from the official SDK's `examples/` — `mcpserver/simple_echo.py`, `servers/simple-prompt`, `servers/simple-pagination`. Static analysis: 0 findings on all 3 (no false secret/eval/subprocess/traversal/Unicode matches). Dynamic analysis: while testing `simple-prompt` (a prompts-only server with no `tools`/`resources` capability), a real trustmcp bug surfaced — `dynamic_client.py`'s `_enumerate_session()` called `list_tools()`/`list_resources()`/`list_prompts()` unconditionally with no per-call exception handling, so a server correctly declining an unsupported capability (JSON-RPC "Method not found") was misreported as a HIGH-severity connection failure. **Fixed** in this session (`_list_capability()` now catches `MCPError` with code `METHOD_NOT_FOUND` per call and records it as a passed check, not a finding); all 3 clean servers now correctly show 0 vulnerability findings.
131
+
132
+ **Known limitation this run confirms:** the free tier has no semantic/LLM analysis, so any vulnerability that lives entirely in plausible-sounding natural-language tool-description text (tool poisoning, rug-pull description mutation, indirect prompt injection) is structurally invisible to it — that's exactly what the paid semantic module (see Enterprise tier below) is for. Regex-based secret detection also has a fixed pattern list; JWTs and custom-prefixed API keys outside that list won't be caught.
133
+
134
+ ---
135
+
136
+ ## 🏢 Enterprise / SaaS Tier
137
+
138
+ Everything above is free, open source, and stays that way. It's a genuinely complete scanner on its own — static analysis, live dynamic probing with input fuzzing, authentication posture, unified scoring, and SARIF/JSON reporting.
139
+
140
+ The paid tier builds on top of it with capabilities that go beyond what a single, stateless scan can offer:
141
+
142
+ - **Semantic (LLM) analysis** — sends every tool/resource/prompt description to an LLM with a strict, conservatively calibrated rubric to catch what regex cannot: naturally phrased hidden instructions, ambiguous scope, and language attempting to dictate model behavior.
143
+ - **Cross-server toxic-flow detection** — real AI agents connect multiple MCP servers simultaneously; a file-reading server plus a network-calling server can form an exfiltration channel even though each looks harmless alone. Single-server scans, by definition, cannot see this.
144
+ - **Continuous monitoring** — a one-time scan cannot catch a *rug pull*: a server that passes review cleanly, then changes its tool descriptions or scope after gaining trust. This requires fingerprinting + a saved baseline + drift alerting across scans over time.
145
+ - API access for CI/CD integration beyond static SARIF, and a verified, embeddable README badge for MCP registries.
146
+
147
+ The free tier's `trustmcp/core/plugins.py` defines the extension point these capabilities plug into — the architecture already supports them; they are simply not distributed in this open-source package.
148
+
149
+ 📩 Interested in early access? Open an [issue](https://github.com/v0idw4lker/trustmcp/issues) or contact [@v0idw4lker](https://github.com/v0idw4lker).
150
+
151
+ ---
152
+
153
+ ## 🧪 Quick Testing
154
+
155
+ ```bash
156
+ git clone https://github.com/v0idw4lker/trustmcp
157
+ cd trustmcp
158
+ pip install -r requirements.txt
159
+
160
+ # Static scan on this repo itself (fixtures/ is intentionally vulnerable)
161
+ trustmcp scan --path . --mode static
162
+
163
+ # Full pipeline against a fixture server
164
+ trustmcp scan --path . --mode both --target "stdio:python3 fixtures/target_server_stdio.py"
165
+
166
+ # Two fixture servers at once — a good demo of live multi-target scanning
167
+ trustmcp scan --path . --mode both \
168
+ --target "stdio:python3 fixtures/vulnerable_server_a.py" \
169
+ --target "stdio:python3 fixtures/vulnerable_server_b.py"
170
+ ```
171
+
172
+ ### Running the test suite
173
+
174
+ ```bash
175
+ pip install -e ".[dev]"
176
+ pytest
177
+ ```
178
+
179
+ ---
180
+
181
+ ## 🗺️ Roadmap
182
+
183
+ - [x] Published detection-rate + false-positive-rate validation against known-vulnerable and clean MCP server benchmarks — see [Validation](#-validation-dvmcp-benchmark-2026-08-19) (2026-08-19)
184
+ - [ ] Complete OAuth 2.1 flow for authenticated dynamic scanning
185
+ - [ ] Semantic analysis, cross-server toxic-flow, and continuous monitoring (paid tier)
186
+ - [ ] Listing on `awesome-mcp-security` and official MCP registries
187
+
188
+ ---
189
+
190
+ ## Project Structure
191
+
192
+ ```
193
+ trustmcp/
194
+ ├── trustmcp/ # installable package — the free tier
195
+ │ ├── cli.py # entrypoint: `trustmcp scan --path ... --mode both`
196
+ │ ├── core/
197
+ │ │ ├── models.py # shared Finding contract used by every module + reporter
198
+ │ │ ├── text_safety.py # hidden/obfuscated Unicode detection (shared static + dynamic)
199
+ │ │ ├── static_analyzer.py # AST/regex SAST, secrets, dependency auditing
200
+ │ │ ├── dynamic_client.py # live MCP client — stdio/HTTP, enumeration, TLS/auth, fuzzing
201
+ │ │ ├── auth_posture.py # OAuth 2.1 / static key / env-var token detection
202
+ │ │ ├── scoring.py # A-F scoring engine + OWASP MCP Top 10 mapping
203
+ │ │ └── plugins.py # extension point for premium modules (unused in the free tier)
204
+ │ ├── reporters/
205
+ │ │ ├── cli_reporter.py # color-coded terminal report
206
+ │ │ ├── json_reporter.py # structured JSON for CI/CD
207
+ │ │ └── sarif_reporter.py # SARIF 2.1.0 export for GitHub Security
208
+ │ └── utils/ # logging + exception hierarchy
209
+ ├── fixtures/ # intentionally vulnerable test MCP servers
210
+ ├── tests/ # pytest suite + clean/vulnerable fixture modules
211
+ └── premium/ # LOCAL ONLY, gitignored — see "Enterprise / SaaS Tier" above
212
+ ```
213
+
214
+ ---
215
+
216
+ ## License
217
+
218
+ MIT — see [LICENSE](LICENSE).
219
+
220
+ Built by [@v0idw4lker](https://github.com/v0idw4lker).
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "trustmcp"
7
+ version = "0.1.0"
8
+ description = "Security scanner for MCP (Model Context Protocol) servers: static (SAST), dynamic (live), and authentication-posture analysis, with an A-F score and SARIF output for the GitHub Security tab."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "v0idw4lker" },
14
+ ]
15
+ keywords = [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "security",
19
+ "sast",
20
+ "ai-security",
21
+ "sarif",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "Intended Audience :: Information Technology",
27
+ "Topic :: Security",
28
+ "Topic :: Software Development :: Quality Assurance",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Operating System :: OS Independent",
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3.10",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3.14",
37
+ ]
38
+ dependencies = [
39
+ "rich>=13.7.0",
40
+ "httpx>=0.28.1",
41
+ "mcp[cli]>=2.0.0,<3.0.0",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ dev = ["pytest>=8.0"]
46
+
47
+ [project.urls]
48
+ Homepage = "https://github.com/v0idw4lker/trustmcp"
49
+ Repository = "https://github.com/v0idw4lker/trustmcp"
50
+ Issues = "https://github.com/v0idw4lker/trustmcp/issues"
51
+
52
+ # Installed, the command is `trustmcp` — e.g.:
53
+ # uvx trustmcp@latest scan --path . --mode both --target "stdio:python3 server.py"
54
+ [project.scripts]
55
+ trustmcp = "trustmcp.cli:main"
56
+
57
+ [tool.setuptools.packages.find]
58
+ include = ["trustmcp*"]
@@ -0,0 +1,3 @@
1
+ rich==13.7.0
2
+ httpx==0.28.1
3
+ mcp[cli]>=2.0.0,<3.0.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,15 @@
1
+ """
2
+ trustmcp — security scanner for MCP (Model Context Protocol) servers.
3
+
4
+ Free tier (this package): static (SAST) analysis, live dynamic analysis,
5
+ authentication posture assessment, unified A-F scoring, and CLI/JSON/SARIF
6
+ reporting.
7
+
8
+ Semantic (LLM-based) analysis and cross-server toxic-flow detection are
9
+ premium capabilities reserved for the paid SaaS tier. They are not
10
+ implemented in this package; core.plugins defines the extension point a
11
+ premium module registers against, so the free-tier pipeline can pick them
12
+ up automatically if one is installed locally.
13
+ """
14
+
15
+ __version__ = "0.1.0"