mcp-riskmap 0.1.2__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_riskmap-0.1.2/LICENSE +21 -0
- mcp_riskmap-0.1.2/PKG-INFO +221 -0
- mcp_riskmap-0.1.2/README.md +174 -0
- mcp_riskmap-0.1.2/pyproject.toml +39 -0
- mcp_riskmap-0.1.2/setup.cfg +4 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/__init__.py +5 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/analyzers/__init__.py +1 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/analyzers/common.py +35 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/analyzers/config.py +124 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/analyzers/js_source.py +66 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/analyzers/python_source.py +81 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/analyzers/repo_hygiene.py +29 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/cli.py +87 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/models.py +62 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/redaction.py +18 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/reporters/__init__.py +1 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/reporters/json_reporter.py +9 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/reporters/markdown.py +28 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/reporters/sarif.py +88 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/reporters/table.py +33 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/rules/__init__.py +1 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/rules/registry.py +54 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap/scanner.py +73 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap.egg-info/PKG-INFO +221 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap.egg-info/SOURCES.txt +29 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap.egg-info/dependency_links.txt +1 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap.egg-info/entry_points.txt +2 -0
- mcp_riskmap-0.1.2/src/mcp_riskmap.egg-info/top_level.txt +1 -0
- mcp_riskmap-0.1.2/tests/test_cli.py +73 -0
- mcp_riskmap-0.1.2/tests/test_reporters.py +76 -0
- mcp_riskmap-0.1.2/tests/test_scanner.py +127 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcp-riskmap 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,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-riskmap
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Local-first static auditor for risky MCP and agent-tool repository patterns.
|
|
5
|
+
Author: mcp-riskmap contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 mcp-riskmap contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/vawkdh-job/mcp-riskmap
|
|
29
|
+
Project-URL: Repository, https://github.com/vawkdh-job/mcp-riskmap
|
|
30
|
+
Project-URL: Issues, https://github.com/vawkdh-job/mcp-riskmap/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/vawkdh-job/mcp-riskmap/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: mcp,security,agent,static-analysis,sarif,github-actions,code-scanning
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Environment :: Console
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Topic :: Security
|
|
42
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
43
|
+
Requires-Python: >=3.10
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# mcp-riskmap
|
|
49
|
+
|
|
50
|
+
[](https://github.com/vawkdh-job/mcp-riskmap/actions/workflows/ci.yml)
|
|
51
|
+
[](https://github.com/vawkdh-job/mcp-riskmap/actions/workflows/mcp-riskmap.yml)
|
|
52
|
+
[](https://github.com/vawkdh-job/mcp-riskmap/releases)
|
|
53
|
+
[](LICENSE)
|
|
54
|
+
|
|
55
|
+
`mcp-riskmap` is a local-first static auditor for MCP and agent-tool repositories. It looks for risky MCP configuration, shell-enabled tool handlers, prompt-injection-like tool descriptions, and missing maintainer guidance without starting untrusted MCP servers.
|
|
56
|
+
|
|
57
|
+
This project is intentionally small and conservative. It is designed for maintainers who want a quick review signal in local development, pull requests, and GitHub Code Scanning.
|
|
58
|
+
|
|
59
|
+
## Why this exists
|
|
60
|
+
|
|
61
|
+
MCP servers often expose tools that can touch files, shells, networks, credentials, or local developer state. Reference servers and community examples are useful, but each maintainer still needs a threat model and basic safeguards before sharing configs or accepting tool changes.
|
|
62
|
+
|
|
63
|
+
`mcp-riskmap` focuses on static signals that are cheap to review:
|
|
64
|
+
|
|
65
|
+
- MCP config that starts through `cmd`, `powershell`, `bash`, or `sh`
|
|
66
|
+
- Remote install pipelines such as `curl ... | sh` or `curl ... | iex`
|
|
67
|
+
- Secret-like environment variables passed into MCP servers
|
|
68
|
+
- Python `subprocess(..., shell=True)`, `os.system`, `eval`, and `exec`
|
|
69
|
+
- JavaScript `child_process.exec` and `spawn(..., { shell: true })`
|
|
70
|
+
- Tool text that looks like model-control prompt injection
|
|
71
|
+
- Missing `AGENTS.md`, `SECURITY.md`, or `LICENSE`
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
From a checkout:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m pip install -e .
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
From GitHub:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m pip install "git+https://github.com/vawkdh-job/mcp-riskmap.git@v0.1.2"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
For development without installing:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
$env:PYTHONPATH = "src"
|
|
91
|
+
python -m mcp_riskmap.cli scan examples/unsafe-mcp-server
|
|
92
|
+
python -m mcp_riskmap.cli --version
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
mcp-riskmap scan .
|
|
99
|
+
mcp-riskmap scan . --format json
|
|
100
|
+
mcp-riskmap scan . --format markdown --output report.md
|
|
101
|
+
mcp-riskmap scan . --format sarif --output results.sarif --fail-on high
|
|
102
|
+
mcp-riskmap scan . --exclude "examples/**" --exclude "tests/**"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`--fail-on high` returns exit code `1` when at least one finding is high or critical.
|
|
106
|
+
|
|
107
|
+
Use `--exclude` for reviewed fixture directories, generated output, or intentionally unsafe examples that should not block CI.
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
- `examples/unsafe-mcp-server/` contains intentionally risky MCP config and tool-handler patterns for scanner demonstrations.
|
|
112
|
+
- `examples/safe-mcp-server/` contains a safer file-read pattern using a resolved base directory boundary check.
|
|
113
|
+
|
|
114
|
+
## Example output
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
SEVERITY RULE LOCATION MESSAGE
|
|
118
|
+
-------- ------------------------- ------------ ------------------------------------------------
|
|
119
|
+
CRITICAL MCP-CONFIG-REMOTE-INSTALL mcp.json:5 MCP server 'unsafe-demo' downloads and executes...
|
|
120
|
+
HIGH PY-SHELL-TRUE server.py:6 A Python tool handler can pass input through a shell.
|
|
121
|
+
HIGH JS-CHILD-PROCESS-EXEC server.js:4 A JavaScript tool handler can pass input through a shell.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Output formats
|
|
125
|
+
|
|
126
|
+
- `table`: compact terminal output
|
|
127
|
+
- `json`: automation-friendly structured output
|
|
128
|
+
- `markdown`: issue and release-note friendly report
|
|
129
|
+
- `sarif`: GitHub Code Scanning compatible output
|
|
130
|
+
|
|
131
|
+
Structured outputs redact secret-like evidence values before writing JSON or SARIF.
|
|
132
|
+
|
|
133
|
+
## Reviewed suppressions
|
|
134
|
+
|
|
135
|
+
If a maintainer reviews a finding and accepts the risk, add a narrow suppression on the same line or the previous line:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# mcp-riskmap: ignore PY-SHELL-TRUE
|
|
139
|
+
subprocess.run(command, shell=True)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use rule-specific suppressions where possible. `mcp-riskmap: ignore` suppresses all rules on the next line and should be reserved for generated or documented fixture code.
|
|
143
|
+
|
|
144
|
+
## GitHub Action
|
|
145
|
+
|
|
146
|
+
This repository includes a composite GitHub Action:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
name: mcp-riskmap
|
|
150
|
+
|
|
151
|
+
on:
|
|
152
|
+
pull_request:
|
|
153
|
+
push:
|
|
154
|
+
branches: [main]
|
|
155
|
+
|
|
156
|
+
jobs:
|
|
157
|
+
scan:
|
|
158
|
+
runs-on: ubuntu-latest
|
|
159
|
+
permissions:
|
|
160
|
+
contents: read
|
|
161
|
+
security-events: write
|
|
162
|
+
steps:
|
|
163
|
+
- uses: actions/checkout@v4
|
|
164
|
+
- uses: vawkdh-job/mcp-riskmap@v0.1.2
|
|
165
|
+
with:
|
|
166
|
+
path: .
|
|
167
|
+
format: sarif
|
|
168
|
+
output: mcp-riskmap.sarif
|
|
169
|
+
fail-on: high
|
|
170
|
+
exclude: |
|
|
171
|
+
examples/**
|
|
172
|
+
tests/**
|
|
173
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
174
|
+
if: always()
|
|
175
|
+
with:
|
|
176
|
+
sarif_file: mcp-riskmap.sarif
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Safety stance
|
|
180
|
+
|
|
181
|
+
`mcp-riskmap` does not execute MCP servers. It reads files and reports static findings. That means it will miss runtime-only behavior, but it is safer for quick review of unknown configs and pull requests.
|
|
182
|
+
|
|
183
|
+
## Why static only?
|
|
184
|
+
|
|
185
|
+
Some MCP scanners inspect live tool descriptions by starting configured servers. That can be useful, but it is risky when a reviewer is looking at an unknown repository or pull request. `mcp-riskmap` is meant to run earlier in the review flow: it reads files, flags obvious risk, and produces review artifacts without executing commands from the target project.
|
|
186
|
+
|
|
187
|
+
## Compared with dynamic scanners
|
|
188
|
+
|
|
189
|
+
`mcp-riskmap` is not a replacement for dynamic MCP inspection. It is a first-pass guardrail for maintainers who need quick review signals in CI and code review.
|
|
190
|
+
|
|
191
|
+
| Area | mcp-riskmap | Dynamic scanners |
|
|
192
|
+
| --- | --- | --- |
|
|
193
|
+
| Starts scanned MCP servers | No | Often yes |
|
|
194
|
+
| Safe for unknown PRs | Designed for this | Depends on sandboxing |
|
|
195
|
+
| CI/SARIF friendly | Yes | Depends on tool |
|
|
196
|
+
| Runtime behavior coverage | Limited | Better |
|
|
197
|
+
| Static source/config review | Primary focus | Varies |
|
|
198
|
+
|
|
199
|
+
## Current limitations
|
|
200
|
+
|
|
201
|
+
- Rules are conservative regex/static checks, not full taint analysis.
|
|
202
|
+
- The scanner does not inspect live MCP tool responses.
|
|
203
|
+
- Secret detection is key-name based and does not do high-entropy scanning.
|
|
204
|
+
- JavaScript and Python analyzers focus on common high-risk patterns first.
|
|
205
|
+
|
|
206
|
+
## Roadmap
|
|
207
|
+
|
|
208
|
+
- Add more MCP client config locations.
|
|
209
|
+
- Detect unsafe filesystem writes and path traversal candidates.
|
|
210
|
+
- Add rule severity profiles.
|
|
211
|
+
- Add Semgrep-compatible pattern export.
|
|
212
|
+
|
|
213
|
+
See [ROADMAP.md](ROADMAP.md) for issue-sized milestones.
|
|
214
|
+
|
|
215
|
+
## OpenAI Codex for OSS fit
|
|
216
|
+
|
|
217
|
+
This project is intended to be maintained as an open-source security and maintainer automation tool. It includes tests, CI, SARIF output, examples, security docs, contribution guidance, AGENTS.md, and tagged releases.
|
|
218
|
+
|
|
219
|
+
Codex/API credits would be useful for reviewing rule changes, generating regression tests, triaging issues, improving documentation, and producing release notes. AI output should be reviewed by maintainers before merge.
|
|
220
|
+
|
|
221
|
+
See [docs/codex-for-oss.md](docs/codex-for-oss.md) for application-specific maintainer workflow notes.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# mcp-riskmap
|
|
2
|
+
|
|
3
|
+
[](https://github.com/vawkdh-job/mcp-riskmap/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/vawkdh-job/mcp-riskmap/actions/workflows/mcp-riskmap.yml)
|
|
5
|
+
[](https://github.com/vawkdh-job/mcp-riskmap/releases)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
`mcp-riskmap` is a local-first static auditor for MCP and agent-tool repositories. It looks for risky MCP configuration, shell-enabled tool handlers, prompt-injection-like tool descriptions, and missing maintainer guidance without starting untrusted MCP servers.
|
|
9
|
+
|
|
10
|
+
This project is intentionally small and conservative. It is designed for maintainers who want a quick review signal in local development, pull requests, and GitHub Code Scanning.
|
|
11
|
+
|
|
12
|
+
## Why this exists
|
|
13
|
+
|
|
14
|
+
MCP servers often expose tools that can touch files, shells, networks, credentials, or local developer state. Reference servers and community examples are useful, but each maintainer still needs a threat model and basic safeguards before sharing configs or accepting tool changes.
|
|
15
|
+
|
|
16
|
+
`mcp-riskmap` focuses on static signals that are cheap to review:
|
|
17
|
+
|
|
18
|
+
- MCP config that starts through `cmd`, `powershell`, `bash`, or `sh`
|
|
19
|
+
- Remote install pipelines such as `curl ... | sh` or `curl ... | iex`
|
|
20
|
+
- Secret-like environment variables passed into MCP servers
|
|
21
|
+
- Python `subprocess(..., shell=True)`, `os.system`, `eval`, and `exec`
|
|
22
|
+
- JavaScript `child_process.exec` and `spawn(..., { shell: true })`
|
|
23
|
+
- Tool text that looks like model-control prompt injection
|
|
24
|
+
- Missing `AGENTS.md`, `SECURITY.md`, or `LICENSE`
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
From a checkout:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
From GitHub:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -m pip install "git+https://github.com/vawkdh-job/mcp-riskmap.git@v0.1.2"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For development without installing:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
$env:PYTHONPATH = "src"
|
|
44
|
+
python -m mcp_riskmap.cli scan examples/unsafe-mcp-server
|
|
45
|
+
python -m mcp_riskmap.cli --version
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
mcp-riskmap scan .
|
|
52
|
+
mcp-riskmap scan . --format json
|
|
53
|
+
mcp-riskmap scan . --format markdown --output report.md
|
|
54
|
+
mcp-riskmap scan . --format sarif --output results.sarif --fail-on high
|
|
55
|
+
mcp-riskmap scan . --exclude "examples/**" --exclude "tests/**"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`--fail-on high` returns exit code `1` when at least one finding is high or critical.
|
|
59
|
+
|
|
60
|
+
Use `--exclude` for reviewed fixture directories, generated output, or intentionally unsafe examples that should not block CI.
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
- `examples/unsafe-mcp-server/` contains intentionally risky MCP config and tool-handler patterns for scanner demonstrations.
|
|
65
|
+
- `examples/safe-mcp-server/` contains a safer file-read pattern using a resolved base directory boundary check.
|
|
66
|
+
|
|
67
|
+
## Example output
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
SEVERITY RULE LOCATION MESSAGE
|
|
71
|
+
-------- ------------------------- ------------ ------------------------------------------------
|
|
72
|
+
CRITICAL MCP-CONFIG-REMOTE-INSTALL mcp.json:5 MCP server 'unsafe-demo' downloads and executes...
|
|
73
|
+
HIGH PY-SHELL-TRUE server.py:6 A Python tool handler can pass input through a shell.
|
|
74
|
+
HIGH JS-CHILD-PROCESS-EXEC server.js:4 A JavaScript tool handler can pass input through a shell.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Output formats
|
|
78
|
+
|
|
79
|
+
- `table`: compact terminal output
|
|
80
|
+
- `json`: automation-friendly structured output
|
|
81
|
+
- `markdown`: issue and release-note friendly report
|
|
82
|
+
- `sarif`: GitHub Code Scanning compatible output
|
|
83
|
+
|
|
84
|
+
Structured outputs redact secret-like evidence values before writing JSON or SARIF.
|
|
85
|
+
|
|
86
|
+
## Reviewed suppressions
|
|
87
|
+
|
|
88
|
+
If a maintainer reviews a finding and accepts the risk, add a narrow suppression on the same line or the previous line:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# mcp-riskmap: ignore PY-SHELL-TRUE
|
|
92
|
+
subprocess.run(command, shell=True)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use rule-specific suppressions where possible. `mcp-riskmap: ignore` suppresses all rules on the next line and should be reserved for generated or documented fixture code.
|
|
96
|
+
|
|
97
|
+
## GitHub Action
|
|
98
|
+
|
|
99
|
+
This repository includes a composite GitHub Action:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
name: mcp-riskmap
|
|
103
|
+
|
|
104
|
+
on:
|
|
105
|
+
pull_request:
|
|
106
|
+
push:
|
|
107
|
+
branches: [main]
|
|
108
|
+
|
|
109
|
+
jobs:
|
|
110
|
+
scan:
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
permissions:
|
|
113
|
+
contents: read
|
|
114
|
+
security-events: write
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@v4
|
|
117
|
+
- uses: vawkdh-job/mcp-riskmap@v0.1.2
|
|
118
|
+
with:
|
|
119
|
+
path: .
|
|
120
|
+
format: sarif
|
|
121
|
+
output: mcp-riskmap.sarif
|
|
122
|
+
fail-on: high
|
|
123
|
+
exclude: |
|
|
124
|
+
examples/**
|
|
125
|
+
tests/**
|
|
126
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
127
|
+
if: always()
|
|
128
|
+
with:
|
|
129
|
+
sarif_file: mcp-riskmap.sarif
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Safety stance
|
|
133
|
+
|
|
134
|
+
`mcp-riskmap` does not execute MCP servers. It reads files and reports static findings. That means it will miss runtime-only behavior, but it is safer for quick review of unknown configs and pull requests.
|
|
135
|
+
|
|
136
|
+
## Why static only?
|
|
137
|
+
|
|
138
|
+
Some MCP scanners inspect live tool descriptions by starting configured servers. That can be useful, but it is risky when a reviewer is looking at an unknown repository or pull request. `mcp-riskmap` is meant to run earlier in the review flow: it reads files, flags obvious risk, and produces review artifacts without executing commands from the target project.
|
|
139
|
+
|
|
140
|
+
## Compared with dynamic scanners
|
|
141
|
+
|
|
142
|
+
`mcp-riskmap` is not a replacement for dynamic MCP inspection. It is a first-pass guardrail for maintainers who need quick review signals in CI and code review.
|
|
143
|
+
|
|
144
|
+
| Area | mcp-riskmap | Dynamic scanners |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| Starts scanned MCP servers | No | Often yes |
|
|
147
|
+
| Safe for unknown PRs | Designed for this | Depends on sandboxing |
|
|
148
|
+
| CI/SARIF friendly | Yes | Depends on tool |
|
|
149
|
+
| Runtime behavior coverage | Limited | Better |
|
|
150
|
+
| Static source/config review | Primary focus | Varies |
|
|
151
|
+
|
|
152
|
+
## Current limitations
|
|
153
|
+
|
|
154
|
+
- Rules are conservative regex/static checks, not full taint analysis.
|
|
155
|
+
- The scanner does not inspect live MCP tool responses.
|
|
156
|
+
- Secret detection is key-name based and does not do high-entropy scanning.
|
|
157
|
+
- JavaScript and Python analyzers focus on common high-risk patterns first.
|
|
158
|
+
|
|
159
|
+
## Roadmap
|
|
160
|
+
|
|
161
|
+
- Add more MCP client config locations.
|
|
162
|
+
- Detect unsafe filesystem writes and path traversal candidates.
|
|
163
|
+
- Add rule severity profiles.
|
|
164
|
+
- Add Semgrep-compatible pattern export.
|
|
165
|
+
|
|
166
|
+
See [ROADMAP.md](ROADMAP.md) for issue-sized milestones.
|
|
167
|
+
|
|
168
|
+
## OpenAI Codex for OSS fit
|
|
169
|
+
|
|
170
|
+
This project is intended to be maintained as an open-source security and maintainer automation tool. It includes tests, CI, SARIF output, examples, security docs, contribution guidance, AGENTS.md, and tagged releases.
|
|
171
|
+
|
|
172
|
+
Codex/API credits would be useful for reviewing rule changes, generating regression tests, triaging issues, improving documentation, and producing release notes. AI output should be reviewed by maintainers before merge.
|
|
173
|
+
|
|
174
|
+
See [docs/codex-for-oss.md](docs/codex-for-oss.md) for application-specific maintainer workflow notes.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-riskmap"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Local-first static auditor for risky MCP and agent-tool repository patterns."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "mcp-riskmap contributors" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "security", "agent", "static-analysis", "sarif", "github-actions", "code-scanning"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
mcp-riskmap = "mcp_riskmap.cli:main"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/vawkdh-job/mcp-riskmap"
|
|
34
|
+
Repository = "https://github.com/vawkdh-job/mcp-riskmap"
|
|
35
|
+
Issues = "https://github.com/vawkdh-job/mcp-riskmap/issues"
|
|
36
|
+
Changelog = "https://github.com/vawkdh-job/mcp-riskmap/blob/main/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""File analyzers used by the scanner."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
SUPPRESSION_RE = re.compile(r"mcp-riskmap:\s*ignore(?:\s+([A-Z0-9_, -]+))?", re.IGNORECASE)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def relative_path(root: Path, path: Path) -> str:
|
|
10
|
+
try:
|
|
11
|
+
return path.relative_to(root).as_posix()
|
|
12
|
+
except ValueError:
|
|
13
|
+
return path.as_posix()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read_text(path: Path) -> str:
|
|
17
|
+
return path.read_text(encoding="utf-8", errors="ignore")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_suppressed(lines: list[str], line_index: int, rule_id: str) -> bool:
|
|
21
|
+
comment_lines = [lines[line_index]]
|
|
22
|
+
if line_index > 0:
|
|
23
|
+
comment_lines.append(lines[line_index - 1])
|
|
24
|
+
return any(_suppresses_rule(line, rule_id) for line in comment_lines)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _suppresses_rule(line: str, rule_id: str) -> bool:
|
|
28
|
+
match = SUPPRESSION_RE.search(line)
|
|
29
|
+
if not match:
|
|
30
|
+
return False
|
|
31
|
+
raw_rules = match.group(1)
|
|
32
|
+
if not raw_rules:
|
|
33
|
+
return True
|
|
34
|
+
rules = {rule.strip().upper() for rule in re.split(r"[,\s]+", raw_rules) if rule.strip()}
|
|
35
|
+
return "ALL" in rules or rule_id.upper() in rules
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from mcp_riskmap.analyzers.common import relative_path, read_text
|
|
9
|
+
from mcp_riskmap.models import Finding
|
|
10
|
+
|
|
11
|
+
CONFIG_NAMES = {
|
|
12
|
+
"mcp.json",
|
|
13
|
+
"mcp.config.json",
|
|
14
|
+
"claude_desktop_config.json",
|
|
15
|
+
"settings.json",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
SECRET_KEY_RE = re.compile(r"(api[_-]?key|token|secret|password|credential)", re.IGNORECASE)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_candidate(path: Path) -> bool:
|
|
22
|
+
return path.name.lower() in CONFIG_NAMES or ".mcp" in path.name.lower()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def analyze_config(root: Path, path: Path) -> list[Finding]:
|
|
26
|
+
text = read_text(path)
|
|
27
|
+
if "mcpServers" not in text and "mcp_servers" not in text:
|
|
28
|
+
return []
|
|
29
|
+
|
|
30
|
+
findings: list[Finding] = []
|
|
31
|
+
try:
|
|
32
|
+
data = json.loads(text)
|
|
33
|
+
except json.JSONDecodeError:
|
|
34
|
+
return findings
|
|
35
|
+
|
|
36
|
+
servers = _server_entries(data)
|
|
37
|
+
for server_name, server in servers:
|
|
38
|
+
command = str(server.get("command", ""))
|
|
39
|
+
args = [str(arg) for arg in server.get("args", [])]
|
|
40
|
+
command_line = " ".join([command, *args]).lower()
|
|
41
|
+
path_text = relative_path(root, path)
|
|
42
|
+
|
|
43
|
+
if _looks_like_shell_wrapper(command, args):
|
|
44
|
+
findings.append(
|
|
45
|
+
Finding(
|
|
46
|
+
rule_id="MCP-CONFIG-SHELL",
|
|
47
|
+
title="MCP server starts through a shell wrapper",
|
|
48
|
+
severity="high",
|
|
49
|
+
message=f"MCP server '{server_name}' starts through a shell-capable command.",
|
|
50
|
+
path=path_text,
|
|
51
|
+
line=_line_for(text, command),
|
|
52
|
+
remediation="Pin the server executable directly and avoid cmd, powershell, bash, or sh wrappers for untrusted configs.",
|
|
53
|
+
evidence=" ".join([command, *args])[:240],
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if "curl " in command_line and ("| sh" in command_line or "| iex" in command_line):
|
|
58
|
+
findings.append(
|
|
59
|
+
Finding(
|
|
60
|
+
rule_id="MCP-CONFIG-REMOTE-INSTALL",
|
|
61
|
+
title="MCP config pipes remote content into an interpreter",
|
|
62
|
+
severity="critical",
|
|
63
|
+
message=f"MCP server '{server_name}' downloads and executes remote content.",
|
|
64
|
+
path=path_text,
|
|
65
|
+
line=_line_for(text, "curl"),
|
|
66
|
+
remediation="Replace remote install pipelines with pinned packages, checksums, or reviewed local scripts.",
|
|
67
|
+
evidence=" ".join([command, *args])[:240],
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
env = server.get("env")
|
|
72
|
+
if isinstance(env, dict):
|
|
73
|
+
secret_keys = sorted(key for key in env if SECRET_KEY_RE.search(str(key)))
|
|
74
|
+
if secret_keys:
|
|
75
|
+
findings.append(
|
|
76
|
+
Finding(
|
|
77
|
+
rule_id="MCP-CONFIG-SECRET-ENV",
|
|
78
|
+
title="MCP config passes secret-like environment variables",
|
|
79
|
+
severity="medium",
|
|
80
|
+
message=f"MCP server '{server_name}' passes secret-like environment keys: {', '.join(secret_keys)}.",
|
|
81
|
+
path=path_text,
|
|
82
|
+
line=_line_for(text, secret_keys[0]),
|
|
83
|
+
remediation="Pass only the minimum required environment variables and document why each secret is needed.",
|
|
84
|
+
evidence=", ".join(secret_keys),
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if command.lower() == "npx" and any(arg == "-y" for arg in args):
|
|
89
|
+
findings.append(
|
|
90
|
+
Finding(
|
|
91
|
+
rule_id="MCP-CONFIG-NPX-LATEST",
|
|
92
|
+
title="MCP config allows non-interactive npx package execution",
|
|
93
|
+
severity="medium",
|
|
94
|
+
message=f"MCP server '{server_name}' runs npx with automatic yes.",
|
|
95
|
+
path=path_text,
|
|
96
|
+
line=_line_for(text, "npx"),
|
|
97
|
+
remediation="Pin package versions and review the resolved package before using npx -y in shared configs.",
|
|
98
|
+
evidence=" ".join([command, *args])[:240],
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return findings
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _server_entries(data: dict[str, Any]) -> list[tuple[str, dict[str, Any]]]:
|
|
106
|
+
servers = data.get("mcpServers") or data.get("mcp_servers") or {}
|
|
107
|
+
if not isinstance(servers, dict):
|
|
108
|
+
return []
|
|
109
|
+
return [(str(name), server) for name, server in servers.items() if isinstance(server, dict)]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _looks_like_shell_wrapper(command: str, args: list[str]) -> bool:
|
|
113
|
+
shell_commands = {"cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh", "bash", "sh"}
|
|
114
|
+
if Path(command).name.lower() in shell_commands:
|
|
115
|
+
return True
|
|
116
|
+
shell_flags = {"/c", "-c", "-command", "/command"}
|
|
117
|
+
return any(arg.lower() in shell_flags for arg in args)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _line_for(text: str, needle: str) -> int:
|
|
121
|
+
for index, line in enumerate(text.splitlines(), start=1):
|
|
122
|
+
if needle and needle in line:
|
|
123
|
+
return index
|
|
124
|
+
return 1
|