mcp-guard-py 0.3.1__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.
- mcp_guard_py-0.3.1/LICENSE +21 -0
- mcp_guard_py-0.3.1/PKG-INFO +391 -0
- mcp_guard_py-0.3.1/README.md +349 -0
- mcp_guard_py-0.3.1/mcp_guard/__init__.py +8 -0
- mcp_guard_py-0.3.1/mcp_guard/cli.py +398 -0
- mcp_guard_py-0.3.1/mcp_guard/formatters.py +206 -0
- mcp_guard_py-0.3.1/mcp_guard/injection.py +517 -0
- mcp_guard_py-0.3.1/mcp_guard/models.py +110 -0
- mcp_guard_py-0.3.1/mcp_guard/npm_scanner.py +207 -0
- mcp_guard_py-0.3.1/mcp_guard/parser.py +718 -0
- mcp_guard_py-0.3.1/mcp_guard/policy.py +163 -0
- mcp_guard_py-0.3.1/mcp_guard/py.typed +0 -0
- mcp_guard_py-0.3.1/mcp_guard/rules.py +420 -0
- mcp_guard_py-0.3.1/mcp_guard/scanner.py +52 -0
- mcp_guard_py-0.3.1/mcp_guard/supply_chain.py +414 -0
- mcp_guard_py-0.3.1/mcp_guard_py.egg-info/PKG-INFO +391 -0
- mcp_guard_py-0.3.1/mcp_guard_py.egg-info/SOURCES.txt +34 -0
- mcp_guard_py-0.3.1/mcp_guard_py.egg-info/dependency_links.txt +1 -0
- mcp_guard_py-0.3.1/mcp_guard_py.egg-info/entry_points.txt +2 -0
- mcp_guard_py-0.3.1/mcp_guard_py.egg-info/requires.txt +17 -0
- mcp_guard_py-0.3.1/mcp_guard_py.egg-info/top_level.txt +1 -0
- mcp_guard_py-0.3.1/pyproject.toml +97 -0
- mcp_guard_py-0.3.1/setup.cfg +4 -0
- mcp_guard_py-0.3.1/tests/test_auth_detection.py +433 -0
- mcp_guard_py-0.3.1/tests/test_cli.py +536 -0
- mcp_guard_py-0.3.1/tests/test_deny_rules.py +445 -0
- mcp_guard_py-0.3.1/tests/test_issue_101_nominal_destructive_keywords.py +89 -0
- mcp_guard_py-0.3.1/tests/test_issue_37_38_39.py +147 -0
- mcp_guard_py-0.3.1/tests/test_issue_fixes.py +315 -0
- mcp_guard_py-0.3.1/tests/test_markup_escaping.py +129 -0
- mcp_guard_py-0.3.1/tests/test_mcp_guard.py +899 -0
- mcp_guard_py-0.3.1/tests/test_npm_scanner.py +295 -0
- mcp_guard_py-0.3.1/tests/test_parser_errors.py +90 -0
- mcp_guard_py-0.3.1/tests/test_parser_yaml_js.py +188 -0
- mcp_guard_py-0.3.1/tests/test_prompt_injection.py +694 -0
- mcp_guard_py-0.3.1/tests/test_supply_chain.py +583 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yunare Maia
|
|
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,391 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-guard-py
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Security scanner for MCP servers - audit capabilities, detect risks
|
|
5
|
+
Author-email: Yunare Maia <yunare@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yunaremaia/mcp-guard
|
|
8
|
+
Project-URL: Repository, https://github.com/yunaremaia/mcp-guard
|
|
9
|
+
Project-URL: Issues, https://github.com/yunaremaia/mcp-guard/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/yunaremaia/mcp-guard/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: mcp,security,scanner,ai-agent,audit
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: click>=8.1
|
|
27
|
+
Requires-Dist: rich>=13.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0
|
|
29
|
+
Requires-Dist: rapidfuzz>=3.0
|
|
30
|
+
Requires-Dist: pyyaml>=6.0
|
|
31
|
+
Provides-Extra: canary
|
|
32
|
+
Requires-Dist: little-canary<0.5,>=0.4.0; extra == "canary"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pyright>=1.1.400; extra == "dev"
|
|
39
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
40
|
+
Requires-Dist: little-canary<0.5,>=0.4.0; extra == "dev"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# MCP Guard
|
|
44
|
+
|
|
45
|
+
**Security scanner for MCP servers** — audit capabilities, detect risks, generate security reports.
|
|
46
|
+
|
|
47
|
+
    
|
|
48
|
+
|
|
49
|
+
## Why
|
|
50
|
+
|
|
51
|
+
The MCP ecosystem exploded (67,000+ servers in 18 months), but security hasn't kept up:
|
|
52
|
+
- 87% of MCP servers fail high-trust thresholds
|
|
53
|
+
- 72% expose sensitive capabilities unnecessarily
|
|
54
|
+
- 53% rely on static API keys
|
|
55
|
+
|
|
56
|
+
**MCP Guard** helps you audit MCP servers before connecting agents to them.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
`mcp-guard` is not published on PyPI. Install it directly from the repository:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install git+https://github.com/yunaremaia/mcp-guard.git
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.10 or newer.
|
|
67
|
+
|
|
68
|
+
Alternatively, run it without installing via Docker:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
docker run --rm -v "$PWD:/work" ghcr.io/yunaremaia/mcp-guard:latest scan /work/my-mcp-server
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
_Get started in 5 minutes_:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Scan an MCP server directory
|
|
80
|
+
mcp-guard scan ./my-mcp-server
|
|
81
|
+
|
|
82
|
+
# Scan a specific config file
|
|
83
|
+
mcp-guard scan ./mcp.json
|
|
84
|
+
|
|
85
|
+
# Output as JSON
|
|
86
|
+
mcp-guard scan ./my-mcp-server --format json
|
|
87
|
+
|
|
88
|
+
# Output as SARIF (for GitHub Code Scanning)
|
|
89
|
+
mcp-guard scan ./my-mcp-server --format sarif --output results.sarif
|
|
90
|
+
|
|
91
|
+
# Fail CI if HIGH or CRITICAL findings
|
|
92
|
+
mcp-guard scan ./my-mcp-server --fail-on high
|
|
93
|
+
|
|
94
|
+
# Audit every MCP server installed as an npm dependency under node_modules
|
|
95
|
+
mcp-guard scan ./my-project --npm --format json
|
|
96
|
+
|
|
97
|
+
# Enforce security deny rules from a YAML config
|
|
98
|
+
mcp-guard scan ./my-mcp-server --config policy.yaml --deny
|
|
99
|
+
|
|
100
|
+
# Deny specific servers or tools on the fly
|
|
101
|
+
mcp-guard scan ./my-mcp-server --deny-server "untrusted-*" --deny
|
|
102
|
+
|
|
103
|
+
# Add Little Canary's structural filter to prompt injection checks
|
|
104
|
+
# (requires: pip install "mcp-guard[canary] @ git+https://github.com/yunaremaia/mcp-guard.git")
|
|
105
|
+
mcp-guard scan ./my-mcp-server --strict-injection
|
|
106
|
+
|
|
107
|
+
# Verify npm supply chain (sigstore attestations / provenance)
|
|
108
|
+
mcp-guard verify @modelcontextprotocol/server-memory
|
|
109
|
+
|
|
110
|
+
# Enforce a strict policy: fail if the package is unsigned (no provenance)
|
|
111
|
+
mcp-guard verify @scope/mcp-server@1.2.0 --policy strict --format json
|
|
112
|
+
|
|
113
|
+
# Show server info without scanning
|
|
114
|
+
mcp-guard info ./my-mcp-server
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## npm package scanning
|
|
118
|
+
|
|
119
|
+
MCP servers are commonly published to npm (`@modelcontextprotocol/server-*`,
|
|
120
|
+
`@mcp/*`, `mcp-*`) and ship an `mcp.json` (or another known config name) next to
|
|
121
|
+
their code. Point `scan` at a project root with `--npm` to audit the MCP surface a
|
|
122
|
+
project pulls in:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Report the MCP-related dependencies in package.json and scan every MCP
|
|
126
|
+
# config found under node_modules/ in JSON
|
|
127
|
+
mcp-guard scan ./my-project --npm --format json
|
|
128
|
+
|
|
129
|
+
# Emit a multi-run SARIF document for GitHub Code Scanning
|
|
130
|
+
mcp-guard scan ./my-project --npm --format sarif --output results.sarif
|
|
131
|
+
|
|
132
|
+
# Fail CI if any installed server trips a deny rule
|
|
133
|
+
mcp-guard scan ./my-project --npm --deny-server "untrusted-*" --deny
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
With `--npm`, the path is treated as a **project root** rather than a single
|
|
137
|
+
manifest:
|
|
138
|
+
|
|
139
|
+
- MCP-related dependencies are listed from `package.json`
|
|
140
|
+
(`dependencies`, `devDependencies`, `optionalDependencies`).
|
|
141
|
+
- Every `mcp.json` / `mcp.config.json` / `.mcp.json` / `server.json` under
|
|
142
|
+
`node_modules/` is scanned; the owning npm package is attributed to each result.
|
|
143
|
+
- A manifest that cannot be parsed is **skipped and reported, not fatal**.
|
|
144
|
+
- `--deny`, `--fail-on`, `--config` and the JSON / SARIF / CLI output formats all
|
|
145
|
+
apply to the aggregated result set.
|
|
146
|
+
|
|
147
|
+
Discovery is filesystem-only: it never runs `npm` or `node`, never executes a
|
|
148
|
+
discovered manifest, and makes no network call (the same offline posture as the
|
|
149
|
+
supply-chain verifier).
|
|
150
|
+
|
|
151
|
+
## Supported config formats
|
|
152
|
+
|
|
153
|
+
`mcp-guard scan <file>` (and `MCPParser.from_file`) auto-detects the format from the file extension:
|
|
154
|
+
|
|
155
|
+
- **`.json`** — standard JSON manifest; the default.
|
|
156
|
+
- **`.yaml` / `.yml`** — YAML manifest (parsed with a safe loader, no code execution).
|
|
157
|
+
- **`.js`** — a JavaScript object-literal config (`module.exports = { ... }` or `export default { ... }`). The body is read as a JSON-compatible object literal and is **never executed**, so executable JS bodies are not supported. Keys and string values must use double quotes, as in JSON.
|
|
158
|
+
|
|
159
|
+
Directories (the `scan ./my-mcp-server` form) still look for a known JSON config file such as `mcp.json`; pass a YAML or `.js` file directly to use those formats.
|
|
160
|
+
|
|
161
|
+
## Deny Rules & Policy Enforcement
|
|
162
|
+
|
|
163
|
+
You can configure deny rules in a YAML file (e.g. `policy.yaml` or default `mcp-guard.yaml`). Deny rules support exact matches, wildcards (`*`), and tool-level scoping:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
# Security Policy Configuration
|
|
167
|
+
deny:
|
|
168
|
+
# Block unverified or untrusted servers
|
|
169
|
+
servers:
|
|
170
|
+
- "malicious-server"
|
|
171
|
+
- "github-*"
|
|
172
|
+
# Block high-risk tools (exact name, wildcard, or scoped to server)
|
|
173
|
+
tools:
|
|
174
|
+
- "github/delete_repo"
|
|
175
|
+
- "filesystem/write"
|
|
176
|
+
- "sys_*"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Use the `--deny` flag to fail with exit code 1 whenever any denied server or tool is detected:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
mcp-guard scan ./my-mcp-server --config policy.yaml --deny
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Supply Chain Verification
|
|
186
|
+
|
|
187
|
+
`mcp-guard verify` checks whether an npm-distributed MCP server was published
|
|
188
|
+
with **sigstore attestations** (provenance / SLSA). It queries the npm registry
|
|
189
|
+
for the attestations attached to a package version and reports whether the
|
|
190
|
+
artifact you are about to run was built with provenance:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Unscoped, scoped, pinned or floating version references all work
|
|
194
|
+
mcp-guard verify some-mcp-server
|
|
195
|
+
mcp-guard verify @scope/some-mcp-server@1.2.0
|
|
196
|
+
|
|
197
|
+
# Machine-readable output for CI pipelines
|
|
198
|
+
mcp-guard verify @scope/some-mcp-server --format json
|
|
199
|
+
|
|
200
|
+
# Strict policy: exit code 1 when the version is unsigned
|
|
201
|
+
mcp-guard verify @scope/some-mcp-server --policy strict
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Statuses:
|
|
205
|
+
|
|
206
|
+
| status | meaning |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `signed` | sigstore attestations found (and whether one is a SLSA provenance statement) |
|
|
209
|
+
| `unsigned` | the version exists but no attestations were published with it |
|
|
210
|
+
| `not_found` | the package does not exist on the registry |
|
|
211
|
+
| `registry_error` | the registry could not be reached or answered unexpectedly |
|
|
212
|
+
|
|
213
|
+
Exit codes: `0` when verified signed (or in report mode); `1` when the package
|
|
214
|
+
is unsigned under `--policy strict`, when it is not found, or on registry
|
|
215
|
+
errors — ready to gate CI jobs.
|
|
216
|
+
|
|
217
|
+
Notes:
|
|
218
|
+
|
|
219
|
+
- No new runtime dependencies: the verifier uses only the standard library
|
|
220
|
+
(`urllib`), and the network layer is injectable, so the test suite stays
|
|
221
|
+
fully offline.
|
|
222
|
+
- Verification currently covers npm packages. PyPI (PEP 740 attestations),
|
|
223
|
+
Docker (cosign / SBOM) and reproducible-build checks are tracked as
|
|
224
|
+
follow-ups in [#74](https://github.com/yunaremaia/mcp-guard/issues/74).
|
|
225
|
+
|
|
226
|
+
## What It Detects
|
|
227
|
+
|
|
228
|
+
| Rule | Level | Description |
|
|
229
|
+
|------|-------|-------------|
|
|
230
|
+
| MCP001 | HIGH | Write operation without authentication |
|
|
231
|
+
| MCP002 | CRITICAL | Destructive operation without authentication |
|
|
232
|
+
| MCP003 | MEDIUM | Capability with excessive permissions |
|
|
233
|
+
| MCP004 | LOW | Capability without description |
|
|
234
|
+
| MCP005 | MEDIUM | Write capability without corresponding read |
|
|
235
|
+
| MCP006 | HIGH | Destructive operation without confirmation |
|
|
236
|
+
| MCP007 | MEDIUM / HIGH / CRITICAL | Explicitly disabled authentication ('auth': false) |
|
|
237
|
+
| MCP008 | LOW / MEDIUM / HIGH | Possible prompt injection in server or capability metadata |
|
|
238
|
+
| MCP009 | HIGH | Command execution without authentication |
|
|
239
|
+
| DENY001 | CRITICAL | Server matches security policy deny rule |
|
|
240
|
+
| DENY002 | CRITICAL | Tool capability matches security policy deny rule |
|
|
241
|
+
|
|
242
|
+
### Authentication Detection Semantics
|
|
243
|
+
|
|
244
|
+
`mcp-guard` validates that capability authentication fields contain truthy configuration rather than mere key presence:
|
|
245
|
+
|
|
246
|
+
- **Auth Required**: Detected when `auth`, `authorization`, or `security` is present with a truthy value (`true`, configuration object/dict, non-empty string, or non-empty list).
|
|
247
|
+
- **Auth Explicitly Disabled**: Flagged when `auth` or `authorization` is set to `false` or `"disabled"`. Capabilities explicitly disabling authentication trigger rule `MCP007` and cannot bypass `MCP001` (write) or `MCP002` (destructive) checks.
|
|
248
|
+
- **No Auth Field / Falsy**: Falsy values like `null`, `""`, `0`, or `{}` are treated as unauthenticated.
|
|
249
|
+
- **SARIF Integration**: SARIF 2.1.0 output records `properties.auth_status` as `"required"`, `"disabled"`, or `"unknown"` for each capability finding.
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
### Prompt Injection Detection (MCP008)
|
|
253
|
+
|
|
254
|
+
MCP clients pass server and tool metadata to the model, so instructions hidden there act like
|
|
255
|
+
part of the prompt. `MCP008` checks the server name and description, every capability name and
|
|
256
|
+
description, and every key and string value in `inputSchema` (parameter names, descriptions,
|
|
257
|
+
titles, `enum`/`default` values, nested objects, array `items`, `anyOf`/`$defs`). `$ref` is not
|
|
258
|
+
resolved; local definitions are scanned where they appear.
|
|
259
|
+
|
|
260
|
+
- **HIGH**: direct injection phrasing, e.g. "ignore previous instructions", "override safety
|
|
261
|
+
checks", "you are now ...", "DAN mode", chat-template tokens such as `<|im_start|>`,
|
|
262
|
+
"do not tell the user".
|
|
263
|
+
- **MEDIUM**: suspicious phrasing with legitimate uses, e.g. a bare "jailbreak", `<IMPORTANT>`
|
|
264
|
+
tags, a line starting with `system:`, directions to read files such as `~/.ssh/id_rsa`, and
|
|
265
|
+
invisible Unicode (zero-width, bidi, tag characters). A HIGH pattern inside quotes (for
|
|
266
|
+
example a security tool describing an attack) is reported as MEDIUM with `quoted: "true"`,
|
|
267
|
+
never suppressed.
|
|
268
|
+
- **LOW**: a field longer than 65,536 characters or a schema nested deeper than 64 levels;
|
|
269
|
+
the remainder is not scanned.
|
|
270
|
+
|
|
271
|
+
Text is normalized before matching: invisible characters are removed, Unicode tag characters
|
|
272
|
+
are decoded to the ASCII they hide, and full-width lookalikes are folded (NFKC).
|
|
273
|
+
|
|
274
|
+
`--strict-injection` also runs [Little Canary](https://github.com/hermes-labs-ai/little-canary)'s
|
|
275
|
+
`StructuralFilter`. It adds a broader signature set and decodes base64/hex/ROT13 payloads
|
|
276
|
+
before re-checking them. It is deterministic pattern matching. It makes no model, API or
|
|
277
|
+
network calls and needs the optional `canary` extra. Strict mode finds more and has more false
|
|
278
|
+
positives (for example `sudo` or "developer mode" in a shell tool's description).
|
|
279
|
+
|
|
280
|
+
To keep scan time linear, the filter sees each field in overlapping 2,000-character windows
|
|
281
|
+
(200-character overlap). A Little Canary match longer than 200 characters that crosses a window
|
|
282
|
+
boundary can be missed. Base64 and hex tokens longer than 200 characters are decoded whole by
|
|
283
|
+
mcp-guard instead, and the decoded text is checked by both detectors. A long token nested
|
|
284
|
+
inside a decoded token is not decoded again. Cue words that enable ROT13 and reversed-text
|
|
285
|
+
decoding ("rot13", "reverse", ...) are passed to every window of the field. ROT13 or reversed
|
|
286
|
+
text longer than 200 characters can still be split across windows.
|
|
287
|
+
|
|
288
|
+
These are pattern matches, not proof of intent, and they do not catch rephrased or novel
|
|
289
|
+
injections. On the hand-written benign fixture in `tests/fixtures/benign_tools.json` (121
|
|
290
|
+
tools), the default mode flags 5 tools (4.13%, all MEDIUM) and strict mode flags 6 (4.96%).
|
|
291
|
+
Those rates describe that fixture only.
|
|
292
|
+
|
|
293
|
+
In JSON and SARIF output each MCP008 finding has `injection_type` (e.g. `instruction-override`,
|
|
294
|
+
`safety-bypass`, `role-hijack`, `jailbreak`, `fake-authority`, `concealment`,
|
|
295
|
+
`prompt-extraction`, `exfiltration`, `hidden-characters`, `encoded-payload`),
|
|
296
|
+
`metadata_field` (e.g. `inputSchema.properties.path.description`), `detector`
|
|
297
|
+
(`mcp-guard` or `little-canary`), `evidence` and `quoted` properties.
|
|
298
|
+
|
|
299
|
+
### Command Execution Detection (MCP009)
|
|
300
|
+
|
|
301
|
+
"Runs arbitrary code" is a different trust boundary from "mutates data" (`is_write`) and
|
|
302
|
+
"loses data" (`is_destructive`), so command execution gets its own classification: a shell
|
|
303
|
+
tool neither writes nor destroys, and MCP001's "performs write operations" text would be
|
|
304
|
+
false in its output. `MCP009` fires at HIGH when a capability with no authentication matches
|
|
305
|
+
the execution family — `exec`, `execute`, `eval`, `spawn`, `shell`, `bash`, `cmd`, `command`,
|
|
306
|
+
`subprocess`, `popen`, `terminal` — as whole identifier segments or inflected description
|
|
307
|
+
words, exactly like MCP001/MCP002. Read-verb suppression applies, so `get_exec_summary` and
|
|
308
|
+
`get_command_history` stay unflagged, while `get_and_exec` would still match (`and` marks a
|
|
309
|
+
second operation). `run` and `evaluate` are deliberately excluded: measured against this
|
|
310
|
+
repo's own 121-tool corpus, they add read-shaped false positives (`run_query`,
|
|
311
|
+
`evaluate_model`) without catching any execution name the shell/process nouns miss.
|
|
312
|
+
|
|
313
|
+
Known residuals, accepted on purpose: `execute_workflow` and `execute_sql` flag (executing a
|
|
314
|
+
caller-supplied workflow or SQL string is running arbitrary logic), and a description like
|
|
315
|
+
"List the available commands" on a neutral name can trip the phrase match — the same
|
|
316
|
+
description-phrase artifact MCP001/MCP002 have. The rule is not coupled into MCP005 (a shell
|
|
317
|
+
has no meaningful "corresponding read") or MCP006 (a confirmation field does not make an
|
|
318
|
+
unauthenticated shell safe; authentication does).
|
|
319
|
+
|
|
320
|
+
## Example Output
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
MCP Server
|
|
324
|
+
test-server v1.0.0
|
|
325
|
+
A test MCP server
|
|
326
|
+
|
|
327
|
+
Risk Score: CRITICAL
|
|
328
|
+
|
|
329
|
+
Summary
|
|
330
|
+
Total Capabilities 3
|
|
331
|
+
Critical 1
|
|
332
|
+
High 1
|
|
333
|
+
Medium 0
|
|
334
|
+
Low 0
|
|
335
|
+
|
|
336
|
+
Findings
|
|
337
|
+
Rule Level Capability Message Suggestion
|
|
338
|
+
MCP002 CRITICAL delete_database Destructive operation without auth Add authentication
|
|
339
|
+
MCP001 HIGH create_user Write operation without auth Add authentication
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## CI Integration
|
|
343
|
+
|
|
344
|
+
### GitHub Action
|
|
345
|
+
|
|
346
|
+
```yaml
|
|
347
|
+
name: MCP Security Scan
|
|
348
|
+
on: [push, pull_request]
|
|
349
|
+
jobs:
|
|
350
|
+
scan:
|
|
351
|
+
runs-on: ubuntu-latest
|
|
352
|
+
steps:
|
|
353
|
+
- uses: actions/checkout@v4
|
|
354
|
+
- uses: actions/setup-python@v5
|
|
355
|
+
with:
|
|
356
|
+
python-version: "3.11"
|
|
357
|
+
- run: pip install git+https://github.com/yunaremaia/mcp-guard.git
|
|
358
|
+
- run: mcp-guard scan ./mcp-server --format sarif --output results.sarif
|
|
359
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
360
|
+
with:
|
|
361
|
+
sarif_file: results.sarif
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
If this tool is useful to you, a star helps other people find it.
|
|
365
|
+
|
|
366
|
+
## Related tools
|
|
367
|
+
|
|
368
|
+
- **[driftcheck](https://github.com/yunaremaia/driftcheck)** — detect version drift between docs and toolchain files
|
|
369
|
+
- **[context-bridge](https://github.com/yunaremaia/context-bridge)** — persistent session memory for AI agents
|
|
370
|
+
- **[mcp-reconcile](https://github.com/yunaremaia/mcp-reconcile)** — reconcile conflicting MCP server definitions
|
|
371
|
+
- **[tool-call-retry](https://github.com/yunaremaia/tool-call-retry)** — retry failed tool calls with backoff
|
|
372
|
+
|
|
373
|
+
Part of a family of focused, single-purpose developer tools — each one does one thing
|
|
374
|
+
and does it well.
|
|
375
|
+
|
|
376
|
+
## License
|
|
377
|
+
|
|
378
|
+
MIT
|
|
379
|
+
|
|
380
|
+
## Sponsoring / Treasury
|
|
381
|
+
|
|
382
|
+
This project is MIT licensed and free to use. If you want to support its
|
|
383
|
+
maintenance, you can sponsor on GitHub or contribute to the development
|
|
384
|
+
treasury wallet on Solana:
|
|
385
|
+
|
|
386
|
+
- GitHub Sponsors: https://github.com/sponsors/yunaremaia
|
|
387
|
+
- Solana: [`Eeztv1nCYUt1fwGWpzKC948gaWfjejYCAuLtUMgzDWbW`](https://solana.com/solana-wallet?base=SOL&address=Eeztv1nCYUt1fwGWpzKC948gaWfjejYCAuLtUMgzDWbW)
|
|
388
|
+
|
|
389
|
+
## Trust boundary
|
|
390
|
+
|
|
391
|
+
Treat MCP tool responses as untrusted input until validated by your agent policy.
|