mcp-capdiff 0.1.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_capdiff-0.1.1/CHANGELOG.md +40 -0
- mcp_capdiff-0.1.1/LICENSE +21 -0
- mcp_capdiff-0.1.1/MANIFEST.in +8 -0
- mcp_capdiff-0.1.1/PKG-INFO +173 -0
- mcp_capdiff-0.1.1/README.md +150 -0
- mcp_capdiff-0.1.1/SECURITY.md +11 -0
- mcp_capdiff-0.1.1/action.yml +58 -0
- mcp_capdiff-0.1.1/docs/policy.md +20 -0
- mcp_capdiff-0.1.1/docs/precision-matrix.md +18 -0
- mcp_capdiff-0.1.1/docs/releasing.md +39 -0
- mcp_capdiff-0.1.1/docs/report-schema.md +32 -0
- mcp_capdiff-0.1.1/docs/rules/MCP001.md +37 -0
- mcp_capdiff-0.1.1/docs/rules/MCP002.md +36 -0
- mcp_capdiff-0.1.1/docs/rules/MCP003.md +36 -0
- mcp_capdiff-0.1.1/docs/rules/MCP004.md +35 -0
- mcp_capdiff-0.1.1/docs/rules/MCP005.md +30 -0
- mcp_capdiff-0.1.1/docs/rules/MCP007.md +29 -0
- mcp_capdiff-0.1.1/docs/rules/MCP010.md +33 -0
- mcp_capdiff-0.1.1/docs/rules/MCP016.md +29 -0
- mcp_capdiff-0.1.1/docs/rules/MCP017.md +29 -0
- mcp_capdiff-0.1.1/docs/rules/README.md +17 -0
- mcp_capdiff-0.1.1/docs/v0.1-scope.md +40 -0
- mcp_capdiff-0.1.1/docs/versioning.md +14 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/.github/workflows/mcp-audit.yml +25 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/README.md +18 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/mcp-audit.yaml +6 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/pyproject.toml +5 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/approval_removed/after.py +8 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/approval_removed/before.py +8 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/filesystem_expansion/after.py +9 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/filesystem_expansion/before.py +13 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/network_allowlist_added/after.py +14 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/network_allowlist_added/before.py +9 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/unrestricted_fetch/after.py +9 -0
- mcp_capdiff-0.1.1/examples/vulnerable-fastmcp/scenarios/unrestricted_fetch/before.py +9 -0
- mcp_capdiff-0.1.1/fixtures/destructive_with_approval/server.py +8 -0
- mcp_capdiff-0.1.1/fixtures/destructive_without_approval/server.py +8 -0
- mcp_capdiff-0.1.1/fixtures/fastmcp_async_annotations/server.py +16 -0
- mcp_capdiff-0.1.1/fixtures/filesystem_guard_after_read/server.py +15 -0
- mcp_capdiff-0.1.1/fixtures/filesystem_restricted/server.py +14 -0
- mcp_capdiff-0.1.1/fixtures/filesystem_unrestricted/server.py +10 -0
- mcp_capdiff-0.1.1/fixtures/postgresql_read_only/server.py +17 -0
- mcp_capdiff-0.1.1/fixtures/safe_server/server.py +16 -0
- mcp_capdiff-0.1.1/fixtures/sensitive_read_only/server.py +14 -0
- mcp_capdiff-0.1.1/fixtures/sensitive_to_network/server.py +20 -0
- mcp_capdiff-0.1.1/fixtures/shell_direct/server.py +10 -0
- mcp_capdiff-0.1.1/fixtures/shell_dynamic_no_shell/server.py +11 -0
- mcp_capdiff-0.1.1/fixtures/shell_safe_allowlist/server.py +12 -0
- mcp_capdiff-0.1.1/fixtures/sql_destructive/server.py +10 -0
- mcp_capdiff-0.1.1/fixtures/url_allowlisted/server.py +14 -0
- mcp_capdiff-0.1.1/fixtures/url_arbitrary/server.py +9 -0
- mcp_capdiff-0.1.1/fixtures/url_guard_after_request/server.py +15 -0
- mcp_capdiff-0.1.1/fixtures/url_hostname_unguarded/server.py +13 -0
- mcp_capdiff-0.1.1/fixtures/vulnerable_server/server.py +32 -0
- mcp_capdiff-0.1.1/mcp-audit.yaml +17 -0
- mcp_capdiff-0.1.1/pyproject.toml +48 -0
- mcp_capdiff-0.1.1/setup.cfg +4 -0
- mcp_capdiff-0.1.1/src/mcp_audit/__init__.py +6 -0
- mcp_capdiff-0.1.1/src/mcp_audit/__main__.py +6 -0
- mcp_capdiff-0.1.1/src/mcp_audit/analysis/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/analysis/diff.py +235 -0
- mcp_capdiff-0.1.1/src/mcp_audit/analysis/scan.py +29 -0
- mcp_capdiff-0.1.1/src/mcp_audit/cli/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/cli/main.py +112 -0
- mcp_capdiff-0.1.1/src/mcp_audit/models/__init__.py +12 -0
- mcp_capdiff-0.1.1/src/mcp_audit/models/capability.py +73 -0
- mcp_capdiff-0.1.1/src/mcp_audit/models/finding.py +87 -0
- mcp_capdiff-0.1.1/src/mcp_audit/models/report.py +120 -0
- mcp_capdiff-0.1.1/src/mcp_audit/policy/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/policy/evaluator.py +29 -0
- mcp_capdiff-0.1.1/src/mcp_audit/policy/loader.py +138 -0
- mcp_capdiff-0.1.1/src/mcp_audit/reporters/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/reporters/json.py +9 -0
- mcp_capdiff-0.1.1/src/mcp_audit/reporters/manifest.py +30 -0
- mcp_capdiff-0.1.1/src/mcp_audit/reporters/sarif.py +94 -0
- mcp_capdiff-0.1.1/src/mcp_audit/reporters/terminal.py +97 -0
- mcp_capdiff-0.1.1/src/mcp_audit/rules/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/rules/engine.py +177 -0
- mcp_capdiff-0.1.1/src/mcp_audit/scanners/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/scanners/source/__init__.py +1 -0
- mcp_capdiff-0.1.1/src/mcp_audit/scanners/source/python.py +441 -0
- mcp_capdiff-0.1.1/src/mcp_capdiff.egg-info/PKG-INFO +173 -0
- mcp_capdiff-0.1.1/src/mcp_capdiff.egg-info/SOURCES.txt +98 -0
- mcp_capdiff-0.1.1/src/mcp_capdiff.egg-info/dependency_links.txt +1 -0
- mcp_capdiff-0.1.1/src/mcp_capdiff.egg-info/entry_points.txt +2 -0
- mcp_capdiff-0.1.1/src/mcp_capdiff.egg-info/requires.txt +1 -0
- mcp_capdiff-0.1.1/src/mcp_capdiff.egg-info/top_level.txt +1 -0
- mcp_capdiff-0.1.1/tests/golden/diff.txt +42 -0
- mcp_capdiff-0.1.1/tests/golden/manifest.yaml +14 -0
- mcp_capdiff-0.1.1/tests/golden/scan.json +80 -0
- mcp_capdiff-0.1.1/tests/golden/scan.sarif.json +90 -0
- mcp_capdiff-0.1.1/tests/golden/terminal.txt +41 -0
- mcp_capdiff-0.1.1/tests/test_demo_scenarios.py +46 -0
- mcp_capdiff-0.1.1/tests/test_diff.py +176 -0
- mcp_capdiff-0.1.1/tests/test_discovery.py +49 -0
- mcp_capdiff-0.1.1/tests/test_golden.py +68 -0
- mcp_capdiff-0.1.1/tests/test_policy.py +92 -0
- mcp_capdiff-0.1.1/tests/test_reporters.py +44 -0
- mcp_capdiff-0.1.1/tests/test_rule_accuracy.py +111 -0
- mcp_capdiff-0.1.1/tests/test_scan.py +32 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes follow Keep a Changelog. Releases use semantic versioning for the CLI package; rules and output schemas are versioned independently.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.1] - 2026-10-01
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Dedicated PyPI Trusted Publishing workflow with tag-pinned builds and OIDC authentication.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Rename the PyPI distribution to `mcp-capdiff` while preserving the MCP Audit product name, `mcp_audit` import package, and `mcp-audit` command.
|
|
16
|
+
- Prepare version `0.1.1` so the public `v0.1.0` tag remains immutable.
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2026-10-01
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Prevent `PostgreSQL` from being misclassified as an HTTP `POST` side effect.
|
|
23
|
+
- Attach destructive semantic evidence to MCP004 and MCP010 instead of unrelated database-read evidence.
|
|
24
|
+
- Fail closed on inaccessible source trees and zero-tool scans instead of reporting a false PASS.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Discovery benchmark for async `@mcp.tool()` functions with FastMCP `ToolAnnotations`.
|
|
29
|
+
- Explicit `--allow-empty` escape hatch for intentional zero-tool scans.
|
|
30
|
+
- Python/FastMCP source discovery and capability extraction.
|
|
31
|
+
- Nine security and regression rules in ruleset `0.1`.
|
|
32
|
+
- Cross-tool sensitive-data path detection with MCP-context isolation.
|
|
33
|
+
- Terminal, JSON, SARIF, manifest, and Git security-diff output.
|
|
34
|
+
- Versioned report and manifest schema `1.0`.
|
|
35
|
+
- Policy thresholds and reasoned, expiring suppressions.
|
|
36
|
+
- Composite GitHub Action with Code Scanning upload.
|
|
37
|
+
|
|
38
|
+
### Verified
|
|
39
|
+
|
|
40
|
+
- External PostgreSQL MCP benchmark: 20 tools discovered, 6 legitimate findings, no known false positives from `PostgreSQL` token matching, and no false PASS.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MCP Audit contributors
|
|
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,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-capdiff
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Security regression scanner for Model Context Protocol servers.
|
|
5
|
+
Author: MCP Audit contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/robbyfa/MCP-Audit
|
|
8
|
+
Project-URL: Repository, https://github.com/robbyfa/MCP-Audit
|
|
9
|
+
Project-URL: Issues, https://github.com/robbyfa/MCP-Audit/issues
|
|
10
|
+
Keywords: mcp,security,static-analysis,sarif,ci
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: PyYAML<7,>=6.0
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# MCP Audit
|
|
25
|
+
|
|
26
|
+
MCP Audit is an open-source security regression scanner for Model Context Protocol servers. It extracts tool capabilities and permissions, detects risky cross-tool data flows, compares security posture across Git revisions, and emits CI-friendly evidence.
|
|
27
|
+
|
|
28
|
+
The first version focuses on Python/FastMCP projects and the core product thesis from the spec: show what an MCP server can do, what changed, and whether the change creates a dangerous capability path.
|
|
29
|
+
|
|
30
|
+
MCP005 is the capability-graph rule: it identifies when one tool returns classified data and another tool in the same MCP registration context can send it to an external destination. Findings include the source, data classification, sink, destination, path, source evidence, impact, and remediation.
|
|
31
|
+
|
|
32
|
+
## Install locally
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv sync
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run project commands through `uv run`, or activate the virtual environment with
|
|
39
|
+
`source .venv/bin/activate` before using bare commands.
|
|
40
|
+
|
|
41
|
+
The package also supports isolated CLI installation directly from a checkout:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pipx install .
|
|
45
|
+
mcp-audit --version
|
|
46
|
+
mcp-audit scan .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The product and CLI are named MCP Audit, while the PyPI distribution is named `mcp-capdiff`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pipx install mcp-capdiff
|
|
53
|
+
mcp-audit --version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Scan a server
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv run mcp-audit scan .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Scans fail closed when source cannot be read or when no MCP tools are discovered. Use `--allow-empty` only when an empty result is intentional.
|
|
63
|
+
|
|
64
|
+
Useful output formats:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv run mcp-audit scan . --format json
|
|
68
|
+
uv run mcp-audit scan . --format sarif --output mcp-audit.sarif
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Generate a capability manifest
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uv run mcp-audit manifest .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Compare against a Git baseline
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run mcp-audit diff --base origin/main
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The diff report focuses on security changes: before/after risk, added and changed tools, expanded capabilities, new findings, resolved findings, and approval removal.
|
|
84
|
+
|
|
85
|
+
## Run tests
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv run pytest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The corpus contains positive, negative, and edge cases under `fixtures/`, including host allowlisting, path-root validation, approval gating, and cross-tool sensitive-data paths.
|
|
92
|
+
|
|
93
|
+
## GitHub Action
|
|
94
|
+
|
|
95
|
+
The repository includes a composite action that runs the security diff, uploads SARIF to GitHub Code Scanning, and fails the check when policy thresholds are crossed:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
permissions:
|
|
99
|
+
actions: read
|
|
100
|
+
contents: read
|
|
101
|
+
security-events: write
|
|
102
|
+
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v4
|
|
105
|
+
with:
|
|
106
|
+
fetch-depth: 0
|
|
107
|
+
- uses: actions/setup-python@v5
|
|
108
|
+
with:
|
|
109
|
+
python-version: "3.12"
|
|
110
|
+
- uses: your-org/mcp-audit@v0.1
|
|
111
|
+
with:
|
|
112
|
+
target: .
|
|
113
|
+
base: origin/main
|
|
114
|
+
policy: mcp-audit.yaml
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
JSON reports and manifests use the versioned schema documented in `docs/report-schema.md`.
|
|
118
|
+
|
|
119
|
+
The copyable [vulnerable FastMCP demo](examples/vulnerable-fastmcp/) includes four before/after pull-request scenarios and its own Action workflow.
|
|
120
|
+
|
|
121
|
+
## V0.1 rules
|
|
122
|
+
|
|
123
|
+
- `MCP001` arbitrary shell execution.
|
|
124
|
+
- `MCP002` unrestricted filesystem access.
|
|
125
|
+
- `MCP003` arbitrary URL or SSRF surface.
|
|
126
|
+
- `MCP004` high-impact side effect without approval.
|
|
127
|
+
- `MCP005` sensitive read to external write path.
|
|
128
|
+
- `MCP007` unbounded security-sensitive input.
|
|
129
|
+
- `MCP010` destructive tool exposed.
|
|
130
|
+
- `MCP016` capability escalation in a diff.
|
|
131
|
+
- `MCP017` approval removed in a diff.
|
|
132
|
+
|
|
133
|
+
Each rule's detection behavior, examples, remediation, and limitations are documented in [docs/rules](docs/rules/README.md).
|
|
134
|
+
|
|
135
|
+
## Demo fixtures
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv run mcp-audit scan fixtures/safe_server
|
|
139
|
+
uv run mcp-audit scan fixtures/vulnerable_server
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The vulnerable fixture includes a sensitive file read tool, an unrestricted URL fetcher, shell execution, and a destructive operation. MCP Audit should flag the individual findings and the cross-tool path:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
read_customer_file -> agent_context -> fetch_url
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Policy
|
|
149
|
+
|
|
150
|
+
The default policy fails on high and critical findings. A minimal policy file can set a risk threshold and suppress reviewed findings:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
policy:
|
|
154
|
+
ci:
|
|
155
|
+
fail_on:
|
|
156
|
+
- critical
|
|
157
|
+
- high
|
|
158
|
+
max_risk_score: 60
|
|
159
|
+
|
|
160
|
+
suppress:
|
|
161
|
+
- rule: MCP003
|
|
162
|
+
tool: internal_fetch
|
|
163
|
+
reason: "Network egress is enforced by service mesh"
|
|
164
|
+
expires: 2027-01-31
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Suppression reasons are required. Expired suppressions no longer hide findings, and `mcp-audit policy check` reports them. See [policy documentation](docs/policy.md).
|
|
168
|
+
|
|
169
|
+
## Version contracts
|
|
170
|
+
|
|
171
|
+
CLI, ruleset, report schema, and manifest schema versions evolve independently. See [versioning](docs/versioning.md) and [report schema](docs/report-schema.md).
|
|
172
|
+
|
|
173
|
+
Passing MCP Audit is technical security evidence, not a legal compliance determination.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# MCP Audit
|
|
2
|
+
|
|
3
|
+
MCP Audit is an open-source security regression scanner for Model Context Protocol servers. It extracts tool capabilities and permissions, detects risky cross-tool data flows, compares security posture across Git revisions, and emits CI-friendly evidence.
|
|
4
|
+
|
|
5
|
+
The first version focuses on Python/FastMCP projects and the core product thesis from the spec: show what an MCP server can do, what changed, and whether the change creates a dangerous capability path.
|
|
6
|
+
|
|
7
|
+
MCP005 is the capability-graph rule: it identifies when one tool returns classified data and another tool in the same MCP registration context can send it to an external destination. Findings include the source, data classification, sink, destination, path, source evidence, impact, and remediation.
|
|
8
|
+
|
|
9
|
+
## Install locally
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv sync
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Run project commands through `uv run`, or activate the virtual environment with
|
|
16
|
+
`source .venv/bin/activate` before using bare commands.
|
|
17
|
+
|
|
18
|
+
The package also supports isolated CLI installation directly from a checkout:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pipx install .
|
|
22
|
+
mcp-audit --version
|
|
23
|
+
mcp-audit scan .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The product and CLI are named MCP Audit, while the PyPI distribution is named `mcp-capdiff`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pipx install mcp-capdiff
|
|
30
|
+
mcp-audit --version
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Scan a server
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv run mcp-audit scan .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Scans fail closed when source cannot be read or when no MCP tools are discovered. Use `--allow-empty` only when an empty result is intentional.
|
|
40
|
+
|
|
41
|
+
Useful output formats:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv run mcp-audit scan . --format json
|
|
45
|
+
uv run mcp-audit scan . --format sarif --output mcp-audit.sarif
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Generate a capability manifest
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv run mcp-audit manifest .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Compare against a Git baseline
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv run mcp-audit diff --base origin/main
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The diff report focuses on security changes: before/after risk, added and changed tools, expanded capabilities, new findings, resolved findings, and approval removal.
|
|
61
|
+
|
|
62
|
+
## Run tests
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv run pytest
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The corpus contains positive, negative, and edge cases under `fixtures/`, including host allowlisting, path-root validation, approval gating, and cross-tool sensitive-data paths.
|
|
69
|
+
|
|
70
|
+
## GitHub Action
|
|
71
|
+
|
|
72
|
+
The repository includes a composite action that runs the security diff, uploads SARIF to GitHub Code Scanning, and fails the check when policy thresholds are crossed:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
permissions:
|
|
76
|
+
actions: read
|
|
77
|
+
contents: read
|
|
78
|
+
security-events: write
|
|
79
|
+
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
with:
|
|
83
|
+
fetch-depth: 0
|
|
84
|
+
- uses: actions/setup-python@v5
|
|
85
|
+
with:
|
|
86
|
+
python-version: "3.12"
|
|
87
|
+
- uses: your-org/mcp-audit@v0.1
|
|
88
|
+
with:
|
|
89
|
+
target: .
|
|
90
|
+
base: origin/main
|
|
91
|
+
policy: mcp-audit.yaml
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
JSON reports and manifests use the versioned schema documented in `docs/report-schema.md`.
|
|
95
|
+
|
|
96
|
+
The copyable [vulnerable FastMCP demo](examples/vulnerable-fastmcp/) includes four before/after pull-request scenarios and its own Action workflow.
|
|
97
|
+
|
|
98
|
+
## V0.1 rules
|
|
99
|
+
|
|
100
|
+
- `MCP001` arbitrary shell execution.
|
|
101
|
+
- `MCP002` unrestricted filesystem access.
|
|
102
|
+
- `MCP003` arbitrary URL or SSRF surface.
|
|
103
|
+
- `MCP004` high-impact side effect without approval.
|
|
104
|
+
- `MCP005` sensitive read to external write path.
|
|
105
|
+
- `MCP007` unbounded security-sensitive input.
|
|
106
|
+
- `MCP010` destructive tool exposed.
|
|
107
|
+
- `MCP016` capability escalation in a diff.
|
|
108
|
+
- `MCP017` approval removed in a diff.
|
|
109
|
+
|
|
110
|
+
Each rule's detection behavior, examples, remediation, and limitations are documented in [docs/rules](docs/rules/README.md).
|
|
111
|
+
|
|
112
|
+
## Demo fixtures
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
uv run mcp-audit scan fixtures/safe_server
|
|
116
|
+
uv run mcp-audit scan fixtures/vulnerable_server
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The vulnerable fixture includes a sensitive file read tool, an unrestricted URL fetcher, shell execution, and a destructive operation. MCP Audit should flag the individual findings and the cross-tool path:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
read_customer_file -> agent_context -> fetch_url
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Policy
|
|
126
|
+
|
|
127
|
+
The default policy fails on high and critical findings. A minimal policy file can set a risk threshold and suppress reviewed findings:
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
policy:
|
|
131
|
+
ci:
|
|
132
|
+
fail_on:
|
|
133
|
+
- critical
|
|
134
|
+
- high
|
|
135
|
+
max_risk_score: 60
|
|
136
|
+
|
|
137
|
+
suppress:
|
|
138
|
+
- rule: MCP003
|
|
139
|
+
tool: internal_fetch
|
|
140
|
+
reason: "Network egress is enforced by service mesh"
|
|
141
|
+
expires: 2027-01-31
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Suppression reasons are required. Expired suppressions no longer hide findings, and `mcp-audit policy check` reports them. See [policy documentation](docs/policy.md).
|
|
145
|
+
|
|
146
|
+
## Version contracts
|
|
147
|
+
|
|
148
|
+
CLI, ruleset, report schema, and manifest schema versions evolve independently. See [versioning](docs/versioning.md) and [report schema](docs/report-schema.md).
|
|
149
|
+
|
|
150
|
+
Passing MCP Audit is technical security evidence, not a legal compliance determination.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
MCP Audit is pre-1.0. Security fixes are applied to the latest released minor version.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Please use GitHub private vulnerability reporting for the repository. Do not include secrets, production source code, or private scan output in a public issue.
|
|
10
|
+
|
|
11
|
+
Include the affected MCP Audit version, a minimal reproducer, impact, and any known workaround. Maintainers should acknowledge a report within seven days and coordinate disclosure after a fix is available.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: MCP Audit
|
|
2
|
+
description: Scan a Python/FastMCP server for security capability regressions and upload SARIF.
|
|
3
|
+
inputs:
|
|
4
|
+
target:
|
|
5
|
+
description: Path to the MCP server source.
|
|
6
|
+
required: false
|
|
7
|
+
default: "."
|
|
8
|
+
base:
|
|
9
|
+
description: Git revision used as the approved security baseline.
|
|
10
|
+
required: false
|
|
11
|
+
default: "origin/main"
|
|
12
|
+
policy:
|
|
13
|
+
description: Optional MCP Audit policy file.
|
|
14
|
+
required: false
|
|
15
|
+
default: ""
|
|
16
|
+
sarif:
|
|
17
|
+
description: SARIF output path.
|
|
18
|
+
required: false
|
|
19
|
+
default: "mcp-audit.sarif"
|
|
20
|
+
outputs:
|
|
21
|
+
exit-code:
|
|
22
|
+
description: MCP Audit exit code (0 pass, 1 policy failure).
|
|
23
|
+
value: ${{ steps.audit.outputs.exit-code }}
|
|
24
|
+
runs:
|
|
25
|
+
using: composite
|
|
26
|
+
steps:
|
|
27
|
+
- name: Install MCP Audit
|
|
28
|
+
shell: bash
|
|
29
|
+
run: python -m pip install "${{ github.action_path }}"
|
|
30
|
+
- name: Generate security diff
|
|
31
|
+
id: audit
|
|
32
|
+
shell: bash
|
|
33
|
+
env:
|
|
34
|
+
MCP_AUDIT_TARGET: ${{ inputs.target }}
|
|
35
|
+
MCP_AUDIT_BASE: ${{ inputs.base }}
|
|
36
|
+
MCP_AUDIT_POLICY: ${{ inputs.policy }}
|
|
37
|
+
MCP_AUDIT_SARIF: ${{ inputs.sarif }}
|
|
38
|
+
run: |
|
|
39
|
+
set +e
|
|
40
|
+
args=(diff "$MCP_AUDIT_TARGET" --base "$MCP_AUDIT_BASE" --format sarif --output "$MCP_AUDIT_SARIF")
|
|
41
|
+
if [[ -n "$MCP_AUDIT_POLICY" ]]; then
|
|
42
|
+
args+=(--policy "$MCP_AUDIT_POLICY")
|
|
43
|
+
fi
|
|
44
|
+
mcp-audit "${args[@]}"
|
|
45
|
+
code=$?
|
|
46
|
+
echo "exit-code=$code" >> "$GITHUB_OUTPUT"
|
|
47
|
+
if [[ $code -eq 2 ]]; then
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
exit 0
|
|
51
|
+
- name: Upload SARIF
|
|
52
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
53
|
+
with:
|
|
54
|
+
sarif_file: ${{ inputs.sarif }}
|
|
55
|
+
- name: Enforce policy result
|
|
56
|
+
if: steps.audit.outputs.exit-code == '1'
|
|
57
|
+
shell: bash
|
|
58
|
+
run: exit 1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Policy and suppressions
|
|
2
|
+
|
|
3
|
+
Policies define CI thresholds and reviewed exceptions:
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
policy:
|
|
7
|
+
ci:
|
|
8
|
+
fail_on: [critical, high]
|
|
9
|
+
max_risk_score: 60
|
|
10
|
+
|
|
11
|
+
suppress:
|
|
12
|
+
- rule: MCP003
|
|
13
|
+
tool: internal_fetch
|
|
14
|
+
reason: "Egress is restricted to api.example.com by the service mesh"
|
|
15
|
+
expires: 2027-01-31
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`reason` is required. `tool` is optional and omission suppresses the rule for all tools. `expires` is optional and must use `YYYY-MM-DD`; after that date the finding is no longer suppressed. `mcp-audit policy check` exits with status `1` when a suppression is expired and status `2` for invalid policy syntax.
|
|
19
|
+
|
|
20
|
+
Suppressions are policy decisions, not proof that a finding is safe. Prefer a narrow tool-specific suppression with a short expiry.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Rule precision matrix
|
|
2
|
+
|
|
3
|
+
The release gate requires positive, negative, guarded, and edge coverage for the differentiating rules.
|
|
4
|
+
|
|
5
|
+
| Rule | True positive | True negative | Guarded safe | Tricky edge |
|
|
6
|
+
| --- | --- | --- | --- | --- |
|
|
7
|
+
| MCP001 | `shell_direct` | `safe_server` | `shell_safe_allowlist` | `shell_dynamic_no_shell` detects a dynamic executable without `shell=True` |
|
|
8
|
+
| MCP002 | `filesystem_unrestricted` | `sensitive_read_only` | `filesystem_restricted` | `filesystem_guard_after_read` rejects a late guard |
|
|
9
|
+
| MCP003 | `url_arbitrary` | fixed URL in demo baseline | `url_allowlisted` | hostname mention and guard-after-request remain findings |
|
|
10
|
+
| MCP005 | `sensitive_to_network` | `sensitive_read_only` | constrained source-only server | root corpus proves independent MCP contexts are isolated |
|
|
11
|
+
| MCP016 | network expansion diff | unchanged diff | allowlist-added demo resolves risk | a new high-impact tool is an escalation |
|
|
12
|
+
| MCP017 | approval-removed diff | preserved approval | approval-added demo resolves risk | capability field change is retained in machine output |
|
|
13
|
+
|
|
14
|
+
MCP004, MCP007, and MCP010 also have paired approved/bounded and unapproved/unbounded fixtures. The matrix is intentionally tied to fixture and test names so reviewers can audit the claim.
|
|
15
|
+
|
|
16
|
+
`postgresql_read_only` protects the semantic classifier from matching `post` inside `PostgreSQL`; `sql_destructive` proves that actual `DELETE`/`DROP` semantics still produce MCP004 and MCP010 with relevant evidence.
|
|
17
|
+
|
|
18
|
+
Discovery is separately benchmarked by `fastmcp_async_annotations`, which must find both plain async `@mcp.tool()` and `@mcp.tool(annotations=ToolAnnotations(...))` decorators. Missing targets, unreadable trees, parse failures, and unexpected zero-tool scans fail closed.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Release checklist
|
|
2
|
+
|
|
3
|
+
## Local preflight
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync --locked
|
|
7
|
+
uv run pytest
|
|
8
|
+
uv build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Install the wheel into a clean environment and verify `mcp-audit --version`, a passing scan, a failing scan, and `policy check`.
|
|
12
|
+
|
|
13
|
+
## Contract review
|
|
14
|
+
|
|
15
|
+
- Confirm CLI/package, ruleset, report schema, and manifest schema versions.
|
|
16
|
+
- Review golden-output changes explicitly.
|
|
17
|
+
- Update `CHANGELOG.md` and every affected rule page.
|
|
18
|
+
- Confirm the composite Action references supported major action versions.
|
|
19
|
+
|
|
20
|
+
## Publish
|
|
21
|
+
|
|
22
|
+
1. Create and push the matching Git tag, such as `v0.1.1`.
|
|
23
|
+
2. Publish the matching GitHub Release. This automatically runs `release.yml`.
|
|
24
|
+
3. Verify `pipx install mcp-capdiff` in a clean environment.
|
|
25
|
+
4. Run the tagged Action from the demo repository and confirm SARIF appears in Code Scanning.
|
|
26
|
+
|
|
27
|
+
For the first release, configure a GitHub Actions Pending Trusted Publisher on PyPI before running the workflow:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
PyPI project name: mcp-capdiff
|
|
31
|
+
Owner: robbyfa
|
|
32
|
+
Repository: MCP-Audit
|
|
33
|
+
Workflow filename: release.yml
|
|
34
|
+
Environment: pypi
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The GitHub environment must be named `pypi`. Publishing uses OIDC and does not require a `PYPI_TOKEN` secret. Manual workflow dispatches require a release tag; the workflow checks out that immutable tag and verifies that the distribution version matches it.
|
|
38
|
+
|
|
39
|
+
The PyPI distribution is `mcp-capdiff`; the product remains MCP Audit and the installed command remains `mcp-audit`. Name availability is only guaranteed when the first release is registered.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Report schema 1.0
|
|
2
|
+
|
|
3
|
+
MCP Audit uses the same dataclass-backed objects for terminal, JSON, SARIF, manifest, and diff output. JSON reports identify the contract with:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"schema_version": "1.0",
|
|
8
|
+
"report_type": "scan"
|
|
9
|
+
}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Reports also include the CLI and ruleset versions. Manifests carry `schema_version` and `rules_version`; SARIF uses `semanticVersion` and `properties.rulesVersion` on the tool driver.
|
|
13
|
+
|
|
14
|
+
`report_type` is either `scan` or `diff`.
|
|
15
|
+
|
|
16
|
+
## Finding contract
|
|
17
|
+
|
|
18
|
+
Every finding includes:
|
|
19
|
+
|
|
20
|
+
- `rule_id`, `title`, and `severity` for identity and prioritization.
|
|
21
|
+
- `tool` and `location` for the affected MCP surface.
|
|
22
|
+
- `message` describing what was detected.
|
|
23
|
+
- `impact` describing the security consequence.
|
|
24
|
+
- `recommendation` describing the expected remediation.
|
|
25
|
+
- `evidence`, a list of typed source locations and snippets.
|
|
26
|
+
- `path`, `data_classification`, and `destination` for capability-graph findings.
|
|
27
|
+
|
|
28
|
+
## Diff contract
|
|
29
|
+
|
|
30
|
+
Diff reports contain before/after risk, new/removed/changed tools, field-level capability changes, new findings, resolved findings, and synthetic regression findings (`MCP016` and `MCP017`). Policies are evaluated against new and regression findings, not the entire current scan.
|
|
31
|
+
|
|
32
|
+
Additive fields may be introduced in schema `1.x`. Removing or changing the meaning of an existing field requires a new major schema version.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# MCP001: Arbitrary shell execution
|
|
2
|
+
|
|
3
|
+
**Severity:** Critical
|
|
4
|
+
|
|
5
|
+
## What it detects
|
|
6
|
+
|
|
7
|
+
FastMCP tools that invoke `os.system`, dynamic `eval`/`exec`/`compile`, subprocess APIs with `shell=True`, or a model-controlled executable/interpreter payload.
|
|
8
|
+
|
|
9
|
+
## Why it matters
|
|
10
|
+
|
|
11
|
+
Model-controlled input may execute commands with the MCP server process's privileges.
|
|
12
|
+
|
|
13
|
+
## Vulnerable example
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def run(command: str):
|
|
18
|
+
return subprocess.run(command, shell=True)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Safe example
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
@mcp.tool()
|
|
25
|
+
def status(service: str):
|
|
26
|
+
if service not in {"api", "worker"}:
|
|
27
|
+
raise ValueError("unsupported")
|
|
28
|
+
return subprocess.run(["systemctl", "status", service])
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Remediation
|
|
32
|
+
|
|
33
|
+
Expose explicit operations, allowlist values, pass argument arrays, and avoid a shell interpreter.
|
|
34
|
+
|
|
35
|
+
## Limitations
|
|
36
|
+
|
|
37
|
+
Ruleset 0.1 recognizes common standard-library execution APIs. Wrapper functions and indirect aliases may be missed.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# MCP002: Unrestricted filesystem access
|
|
2
|
+
|
|
3
|
+
**Severity:** High
|
|
4
|
+
|
|
5
|
+
## What it detects
|
|
6
|
+
|
|
7
|
+
Filesystem reads or writes reached by an MCP tool without a detected approved-root check before the operation.
|
|
8
|
+
|
|
9
|
+
## Why it matters
|
|
10
|
+
|
|
11
|
+
An attacker may read secrets or modify files available to the server process.
|
|
12
|
+
|
|
13
|
+
## Vulnerable example
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def read_file(path: str):
|
|
18
|
+
return Path(path).read_text()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Safe example
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
resolved = (REPORT_ROOT / path).resolve()
|
|
25
|
+
if REPORT_ROOT not in resolved.parents:
|
|
26
|
+
raise ValueError("invalid path")
|
|
27
|
+
return resolved.read_text()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Remediation
|
|
31
|
+
|
|
32
|
+
Resolve the requested path, reject traversal outside an explicit root, and perform the check before access.
|
|
33
|
+
|
|
34
|
+
## Limitations
|
|
35
|
+
|
|
36
|
+
Ruleset 0.1 recognizes direct `Path` and `open` patterns. Framework-specific storage wrappers may require future adapters.
|