cobaltosec-corvus 0.7.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.
- cobaltosec_corvus-0.7.0/PKG-INFO +339 -0
- cobaltosec_corvus-0.7.0/README.md +314 -0
- cobaltosec_corvus-0.7.0/cobaltosec_corvus.egg-info/PKG-INFO +339 -0
- cobaltosec_corvus-0.7.0/cobaltosec_corvus.egg-info/SOURCES.txt +60 -0
- cobaltosec_corvus-0.7.0/cobaltosec_corvus.egg-info/dependency_links.txt +1 -0
- cobaltosec_corvus-0.7.0/cobaltosec_corvus.egg-info/entry_points.txt +2 -0
- cobaltosec_corvus-0.7.0/cobaltosec_corvus.egg-info/requires.txt +12 -0
- cobaltosec_corvus-0.7.0/cobaltosec_corvus.egg-info/top_level.txt +2 -0
- cobaltosec_corvus-0.7.0/corvus/__init__.py +3 -0
- cobaltosec_corvus-0.7.0/corvus/batch.py +161 -0
- cobaltosec_corvus-0.7.0/corvus/cli.py +399 -0
- cobaltosec_corvus-0.7.0/corvus/config.py +40 -0
- cobaltosec_corvus-0.7.0/corvus/core/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/core/models.py +97 -0
- cobaltosec_corvus-0.7.0/corvus/core/session.py +41 -0
- cobaltosec_corvus-0.7.0/corvus/discovery/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/discovery/enumerator.py +84 -0
- cobaltosec_corvus-0.7.0/corvus/modules/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/modules/base.py +23 -0
- cobaltosec_corvus-0.7.0/corvus/modules/dynamic/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/modules/dynamic/info_disclosure.py +139 -0
- cobaltosec_corvus-0.7.0/corvus/modules/dynamic/param_injection.py +199 -0
- cobaltosec_corvus-0.7.0/corvus/modules/dynamic/response_flood.py +117 -0
- cobaltosec_corvus-0.7.0/corvus/modules/dynamic/rug_pull.py +128 -0
- cobaltosec_corvus-0.7.0/corvus/modules/dynamic/schema_bypass.py +103 -0
- cobaltosec_corvus-0.7.0/corvus/modules/static/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/modules/static/auth_audit.py +148 -0
- cobaltosec_corvus-0.7.0/corvus/modules/static/log_audit.py +165 -0
- cobaltosec_corvus-0.7.0/corvus/modules/static/schema_audit.py +77 -0
- cobaltosec_corvus-0.7.0/corvus/modules/static/shadow_tool.py +116 -0
- cobaltosec_corvus-0.7.0/corvus/modules/static/tool_poisoning.py +110 -0
- cobaltosec_corvus-0.7.0/corvus/payloads/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/payloads/data/injection.yaml +59 -0
- cobaltosec_corvus-0.7.0/corvus/payloads/data/poisoning_patterns.yaml +37 -0
- cobaltosec_corvus-0.7.0/corvus/payloads/data/schema_bypass.yaml +36 -0
- cobaltosec_corvus-0.7.0/corvus/payloads/data/traversal.yaml +26 -0
- cobaltosec_corvus-0.7.0/corvus/payloads/engine.py +85 -0
- cobaltosec_corvus-0.7.0/corvus/plugins.py +112 -0
- cobaltosec_corvus-0.7.0/corvus/reporting/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/reporting/report.py +111 -0
- cobaltosec_corvus-0.7.0/corvus/reporting/templates/report.md.j2 +76 -0
- cobaltosec_corvus-0.7.0/corvus/transport/__init__.py +0 -0
- cobaltosec_corvus-0.7.0/corvus/transport/base.py +53 -0
- cobaltosec_corvus-0.7.0/corvus/transport/http.py +118 -0
- cobaltosec_corvus-0.7.0/corvus/transport/sse.py +3 -0
- cobaltosec_corvus-0.7.0/corvus/transport/stdio.py +145 -0
- cobaltosec_corvus-0.7.0/pyproject.toml +54 -0
- cobaltosec_corvus-0.7.0/setup.cfg +4 -0
- cobaltosec_corvus-0.7.0/tests/test_batch.py +86 -0
- cobaltosec_corvus-0.7.0/tests/test_config.py +81 -0
- cobaltosec_corvus-0.7.0/tests/test_discovery.py +26 -0
- cobaltosec_corvus-0.7.0/tests/test_enumerator_listchanged.py +40 -0
- cobaltosec_corvus-0.7.0/tests/test_modules.py +176 -0
- cobaltosec_corvus-0.7.0/tests/test_modules_v2.py +112 -0
- cobaltosec_corvus-0.7.0/tests/test_modules_v3.py +105 -0
- cobaltosec_corvus-0.7.0/tests/test_modules_v4.py +58 -0
- cobaltosec_corvus-0.7.0/tests/test_modules_v5.py +275 -0
- cobaltosec_corvus-0.7.0/tests/test_plugins.py +137 -0
- cobaltosec_corvus-0.7.0/tests/test_request_logging.py +65 -0
- cobaltosec_corvus-0.7.0/tests/test_sarif.py +120 -0
- cobaltosec_corvus-0.7.0/tests/test_transport.py +64 -0
- cobaltosec_corvus-0.7.0/tests/test_transport_http.py +87 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cobaltosec-corvus
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: MCP server security testing framework
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: mcp,security,testing,llm,ai
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: typer>=0.12
|
|
15
|
+
Requires-Dist: httpx>=0.27
|
|
16
|
+
Requires-Dist: pydantic>=2.5
|
|
17
|
+
Requires-Dist: rich>=13
|
|
18
|
+
Requires-Dist: PyYAML>=6
|
|
19
|
+
Requires-Dist: jinja2>=3
|
|
20
|
+
Requires-Dist: anyio>=4
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# Corvus
|
|
27
|
+
|
|
28
|
+
MCP server security testing framework. Tests MCP servers against the [OWASP MCP Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/) — both static analysis and live dynamic probing.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Corvus v0.7.0 MCP Security Scanner
|
|
32
|
+
Target : python my_mcp_server.py
|
|
33
|
+
Transport : stdio
|
|
34
|
+
Modules : tool-poisoning, schema-audit, shadow-tool, auth-audit, log-audit,
|
|
35
|
+
param-injection, info-disclosure, schema-bypass, response-flood, rug-pull
|
|
36
|
+
|
|
37
|
+
Enumerating surface...
|
|
38
|
+
Tools : 12
|
|
39
|
+
Resources : 3
|
|
40
|
+
Prompts : 2
|
|
41
|
+
Server : my-server 1.0.0
|
|
42
|
+
|
|
43
|
+
[MCP01] Tool Poisoning (static)
|
|
44
|
+
[HIGH] Potential prompt injection in description of 'execute_code'
|
|
45
|
+
|
|
46
|
+
[MCP02] Parameter Injection (dynamic)
|
|
47
|
+
[HIGH] Command injection confirmed in tool 'run_shell', param 'command'
|
|
48
|
+
[MEDIUM] Path traversal accepted in tool 'read_file', param 'path'
|
|
49
|
+
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
─── Summary ──────────────────────────────────────────────────────────────────
|
|
53
|
+
CRITICAL 0 HIGH 2 MEDIUM 1 LOW 3 INFO 4
|
|
54
|
+
Session : corvus-sessions/20260608-143022/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install cobaltosec-corvus
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or from source:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/CobaltoSec/corvus
|
|
67
|
+
cd corvus
|
|
68
|
+
pip install -e ".[dev]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Scan a stdio MCP server
|
|
75
|
+
corvus scan --transport stdio --cmd "python my_server.py"
|
|
76
|
+
|
|
77
|
+
# Scan an HTTP MCP server
|
|
78
|
+
corvus scan --transport http --url http://localhost:8080
|
|
79
|
+
|
|
80
|
+
# With authentication header
|
|
81
|
+
corvus scan --transport http --url http://localhost:8080 --header "Authorization: Bearer token"
|
|
82
|
+
|
|
83
|
+
# Static analysis only (no live tool calls)
|
|
84
|
+
corvus scan --transport stdio --cmd "python my_server.py" --module static
|
|
85
|
+
|
|
86
|
+
# Specific module
|
|
87
|
+
corvus scan --transport stdio --cmd "python my_server.py" --module param-injection
|
|
88
|
+
|
|
89
|
+
# SARIF output (for CI/CD integration)
|
|
90
|
+
corvus scan --transport stdio --cmd "python my_server.py" --sarif
|
|
91
|
+
|
|
92
|
+
# Fail CI on findings above threshold
|
|
93
|
+
corvus scan --transport stdio --cmd "python my_server.py" --fail-on high
|
|
94
|
+
|
|
95
|
+
# Load config from file
|
|
96
|
+
corvus scan --config corvus.toml
|
|
97
|
+
|
|
98
|
+
# Filter low-confidence findings (0-100)
|
|
99
|
+
corvus scan --transport stdio --cmd "python my_server.py" --min-confidence 70
|
|
100
|
+
|
|
101
|
+
# Capture raw JSON-RPC exchanges
|
|
102
|
+
corvus scan --transport stdio --cmd "python my_server.py" --log-requests
|
|
103
|
+
|
|
104
|
+
# List available modules
|
|
105
|
+
corvus list-modules
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Batch Scan
|
|
109
|
+
|
|
110
|
+
Scan multiple MCP servers in one invocation:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
# targets.yaml
|
|
114
|
+
targets:
|
|
115
|
+
- name: filesystem
|
|
116
|
+
transport: stdio
|
|
117
|
+
cmd: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
118
|
+
|
|
119
|
+
- name: my-http-server
|
|
120
|
+
transport: http
|
|
121
|
+
url: http://localhost:8080
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
corvus batch targets.yaml --output-dir results/ --sarif --min-confidence 70
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Produces a per-target `report.json` and a top-level `summary.md` table.
|
|
129
|
+
|
|
130
|
+
## Modules
|
|
131
|
+
|
|
132
|
+
Full coverage of OWASP MCP Top 10:
|
|
133
|
+
|
|
134
|
+
| Name | OWASP | Type | What it tests |
|
|
135
|
+
|------|-------|------|---------------|
|
|
136
|
+
| `tool-poisoning` | MCP01 | static | Hidden instructions, obfuscation, and prompt injection patterns in tool descriptions |
|
|
137
|
+
| `param-injection` | MCP02 | dynamic | Command, path, prompt, and SQL injection payloads per parameter — schema-aware |
|
|
138
|
+
| `shadow-tool` | MCP03 | static | Tool names that shadow built-ins or signal dangerous operations (namespace squatting, trust hijacking) |
|
|
139
|
+
| `info-disclosure` | MCP04 | dynamic | Credentials, filesystem paths, stack traces, and tokens leaked in tool responses |
|
|
140
|
+
| `schema-bypass` | MCP05 | dynamic | Whether tools properly reject inputs that violate their declared schema |
|
|
141
|
+
| `rug-pull` | MCP06 | dynamic | Re-enumerates the server after dynamic testing; diffs against initial snapshot to detect added, removed, or mutated tools |
|
|
142
|
+
| `response-flood` | MCP07 | dynamic | Excessively large or highly repetitive responses that could overflow an LLM context window or inject looping instructions |
|
|
143
|
+
| `auth-audit` | MCP08 | static | Tool names and descriptions suggesting missing, optional, or bypassable authentication |
|
|
144
|
+
| `schema-audit` | MCP09 | static | Weak schema definitions (missing required fields, unconstrained types) that expand the attack surface |
|
|
145
|
+
| `log-audit` | MCP10 | static | Tools that expose or tamper with audit logs — enables anti-forensic techniques or leaks operational data |
|
|
146
|
+
|
|
147
|
+
### Module groups
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# All modules (default)
|
|
151
|
+
--module all
|
|
152
|
+
|
|
153
|
+
# Static only (no live calls to the server)
|
|
154
|
+
--module static
|
|
155
|
+
|
|
156
|
+
# Dynamic only
|
|
157
|
+
--module dynamic
|
|
158
|
+
|
|
159
|
+
# Individual module
|
|
160
|
+
--module param-injection
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Transports
|
|
164
|
+
|
|
165
|
+
### stdio
|
|
166
|
+
|
|
167
|
+
Spawns the server process and communicates via stdin/stdout. Supports any command:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
corvus scan --transport stdio --cmd "python server.py"
|
|
171
|
+
corvus scan --transport stdio --cmd "npx @modelcontextprotocol/server-filesystem /tmp"
|
|
172
|
+
corvus scan --transport stdio --cmd "uvx my-mcp-server --arg value"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### HTTP
|
|
176
|
+
|
|
177
|
+
Connects to a running HTTP/SSE MCP server:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
corvus scan --transport http --url http://localhost:8080
|
|
181
|
+
|
|
182
|
+
# With auth
|
|
183
|
+
corvus scan --transport http --url http://localhost:8080 --header "Authorization: Bearer $TOKEN"
|
|
184
|
+
corvus scan --transport http --url http://localhost:8080 --header "X-API-Key: secret"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Config File
|
|
188
|
+
|
|
189
|
+
Create `corvus.toml` to avoid repeating CLI flags:
|
|
190
|
+
|
|
191
|
+
```toml
|
|
192
|
+
[scan]
|
|
193
|
+
transport = "stdio"
|
|
194
|
+
cmd = "python my_server.py"
|
|
195
|
+
modules = "all"
|
|
196
|
+
timeout = 30
|
|
197
|
+
sarif = false
|
|
198
|
+
fail_on = "high"
|
|
199
|
+
|
|
200
|
+
[scan.headers]
|
|
201
|
+
"Authorization" = "Bearer my-token"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Then run:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
corvus scan --config corvus.toml
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
CLI flags override config file values. The `--config` flag also accepts absolute paths.
|
|
211
|
+
|
|
212
|
+
## CLI Reference
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
Usage: corvus scan [OPTIONS]
|
|
216
|
+
|
|
217
|
+
Scan an MCP server for security vulnerabilities.
|
|
218
|
+
|
|
219
|
+
Options:
|
|
220
|
+
-t, --transport TEXT stdio | http (overrides config)
|
|
221
|
+
--cmd TEXT Command to launch MCP server (stdio)
|
|
222
|
+
--url TEXT URL of MCP server (http)
|
|
223
|
+
-m, --module TEXT all | static | dynamic | <module-name> (overrides config)
|
|
224
|
+
-o, --output-dir PATH
|
|
225
|
+
--fail-on TEXT Exit 1 if findings at this severity or above
|
|
226
|
+
(critical|high|medium|low)
|
|
227
|
+
--timeout INTEGER Request timeout in seconds (overrides config)
|
|
228
|
+
--sarif Also write SARIF 2.1.0 report
|
|
229
|
+
--header TEXT HTTP header "Key: Value" (repeatable, for http transport)
|
|
230
|
+
-c, --config PATH Path to corvus.toml config file
|
|
231
|
+
--plugin-dir TEXT Directory to load external modules from (repeatable)
|
|
232
|
+
--help Show this message and exit.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Other commands:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
corvus list-modules # list available modules with OWASP ID and type
|
|
239
|
+
corvus list-modules --plugin-dir ./plugins/ # include external plugins
|
|
240
|
+
corvus version # print version
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Output
|
|
244
|
+
|
|
245
|
+
Each scan creates a session directory under `corvus-sessions/<timestamp>/`:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
corvus-sessions/20260608-143022/
|
|
249
|
+
├── report.json # full structured result
|
|
250
|
+
├── report.md # human-readable with remediation guidance
|
|
251
|
+
└── report.sarif # SARIF 2.1.0 (only when --sarif is passed)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### SARIF integration
|
|
255
|
+
|
|
256
|
+
SARIF output is compatible with GitHub Advanced Security, VS Code SARIF Viewer, and any CI pipeline that consumes SARIF:
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
# GitHub Actions example
|
|
260
|
+
- name: Run Corvus
|
|
261
|
+
run: corvus scan --transport stdio --cmd "python server.py" --sarif --fail-on high
|
|
262
|
+
|
|
263
|
+
- name: Upload SARIF
|
|
264
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
265
|
+
with:
|
|
266
|
+
sarif_file: corvus-sessions/
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## CI Integration
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Exit 1 if any CRITICAL findings
|
|
273
|
+
corvus scan --transport stdio --cmd "python server.py" --fail-on critical
|
|
274
|
+
|
|
275
|
+
# Exit 1 if any HIGH or above
|
|
276
|
+
corvus scan --transport stdio --cmd "python server.py" --fail-on high
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Severity levels (ascending): `info` → `low` → `medium` → `high` → `critical`
|
|
280
|
+
|
|
281
|
+
## Plugin System
|
|
282
|
+
|
|
283
|
+
Add custom modules without modifying Corvus source.
|
|
284
|
+
|
|
285
|
+
### Directory-based plugins
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
corvus scan --transport stdio --cmd "python server.py" --plugin-dir ./my-modules/
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Each `.py` file in the directory is loaded as a module. The file must define a class that inherits from `BaseModule`:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from corvus.modules.base import BaseModule, Finding, Severity
|
|
295
|
+
|
|
296
|
+
class MyCustomModule(BaseModule):
|
|
297
|
+
name = "my-check"
|
|
298
|
+
owasp_id = "MCP-CUSTOM"
|
|
299
|
+
module_type = "static"
|
|
300
|
+
description = "Custom check for my organization"
|
|
301
|
+
|
|
302
|
+
async def run(self, surface, transport):
|
|
303
|
+
findings = []
|
|
304
|
+
for tool in surface.tools:
|
|
305
|
+
if "dangerous_pattern" in tool.description:
|
|
306
|
+
findings.append(Finding(
|
|
307
|
+
rule_id="MY001",
|
|
308
|
+
tool_name=tool.name,
|
|
309
|
+
severity=Severity.HIGH,
|
|
310
|
+
title="Dangerous pattern detected",
|
|
311
|
+
description=f"Tool '{tool.name}' contains a dangerous pattern.",
|
|
312
|
+
remediation="Remove or sanitize the pattern.",
|
|
313
|
+
))
|
|
314
|
+
return findings
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Package-based plugins
|
|
318
|
+
|
|
319
|
+
Register via `pyproject.toml` entry points:
|
|
320
|
+
|
|
321
|
+
```toml
|
|
322
|
+
[project.entry-points."corvus.modules"]
|
|
323
|
+
my-check = "my_package.modules.my_check:MyCustomModule"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
After `pip install my-package`, Corvus auto-discovers the module.
|
|
327
|
+
|
|
328
|
+
## Development
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
git clone https://github.com/CobaltoSec/corvus
|
|
332
|
+
cd corvus
|
|
333
|
+
pip install -e ".[dev]"
|
|
334
|
+
pytest
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## License
|
|
338
|
+
|
|
339
|
+
MIT
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Corvus
|
|
2
|
+
|
|
3
|
+
MCP server security testing framework. Tests MCP servers against the [OWASP MCP Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/) — both static analysis and live dynamic probing.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Corvus v0.7.0 MCP Security Scanner
|
|
7
|
+
Target : python my_mcp_server.py
|
|
8
|
+
Transport : stdio
|
|
9
|
+
Modules : tool-poisoning, schema-audit, shadow-tool, auth-audit, log-audit,
|
|
10
|
+
param-injection, info-disclosure, schema-bypass, response-flood, rug-pull
|
|
11
|
+
|
|
12
|
+
Enumerating surface...
|
|
13
|
+
Tools : 12
|
|
14
|
+
Resources : 3
|
|
15
|
+
Prompts : 2
|
|
16
|
+
Server : my-server 1.0.0
|
|
17
|
+
|
|
18
|
+
[MCP01] Tool Poisoning (static)
|
|
19
|
+
[HIGH] Potential prompt injection in description of 'execute_code'
|
|
20
|
+
|
|
21
|
+
[MCP02] Parameter Injection (dynamic)
|
|
22
|
+
[HIGH] Command injection confirmed in tool 'run_shell', param 'command'
|
|
23
|
+
[MEDIUM] Path traversal accepted in tool 'read_file', param 'path'
|
|
24
|
+
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
─── Summary ──────────────────────────────────────────────────────────────────
|
|
28
|
+
CRITICAL 0 HIGH 2 MEDIUM 1 LOW 3 INFO 4
|
|
29
|
+
Session : corvus-sessions/20260608-143022/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install cobaltosec-corvus
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or from source:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/CobaltoSec/corvus
|
|
42
|
+
cd corvus
|
|
43
|
+
pip install -e ".[dev]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Scan a stdio MCP server
|
|
50
|
+
corvus scan --transport stdio --cmd "python my_server.py"
|
|
51
|
+
|
|
52
|
+
# Scan an HTTP MCP server
|
|
53
|
+
corvus scan --transport http --url http://localhost:8080
|
|
54
|
+
|
|
55
|
+
# With authentication header
|
|
56
|
+
corvus scan --transport http --url http://localhost:8080 --header "Authorization: Bearer token"
|
|
57
|
+
|
|
58
|
+
# Static analysis only (no live tool calls)
|
|
59
|
+
corvus scan --transport stdio --cmd "python my_server.py" --module static
|
|
60
|
+
|
|
61
|
+
# Specific module
|
|
62
|
+
corvus scan --transport stdio --cmd "python my_server.py" --module param-injection
|
|
63
|
+
|
|
64
|
+
# SARIF output (for CI/CD integration)
|
|
65
|
+
corvus scan --transport stdio --cmd "python my_server.py" --sarif
|
|
66
|
+
|
|
67
|
+
# Fail CI on findings above threshold
|
|
68
|
+
corvus scan --transport stdio --cmd "python my_server.py" --fail-on high
|
|
69
|
+
|
|
70
|
+
# Load config from file
|
|
71
|
+
corvus scan --config corvus.toml
|
|
72
|
+
|
|
73
|
+
# Filter low-confidence findings (0-100)
|
|
74
|
+
corvus scan --transport stdio --cmd "python my_server.py" --min-confidence 70
|
|
75
|
+
|
|
76
|
+
# Capture raw JSON-RPC exchanges
|
|
77
|
+
corvus scan --transport stdio --cmd "python my_server.py" --log-requests
|
|
78
|
+
|
|
79
|
+
# List available modules
|
|
80
|
+
corvus list-modules
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Batch Scan
|
|
84
|
+
|
|
85
|
+
Scan multiple MCP servers in one invocation:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
# targets.yaml
|
|
89
|
+
targets:
|
|
90
|
+
- name: filesystem
|
|
91
|
+
transport: stdio
|
|
92
|
+
cmd: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
93
|
+
|
|
94
|
+
- name: my-http-server
|
|
95
|
+
transport: http
|
|
96
|
+
url: http://localhost:8080
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
corvus batch targets.yaml --output-dir results/ --sarif --min-confidence 70
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Produces a per-target `report.json` and a top-level `summary.md` table.
|
|
104
|
+
|
|
105
|
+
## Modules
|
|
106
|
+
|
|
107
|
+
Full coverage of OWASP MCP Top 10:
|
|
108
|
+
|
|
109
|
+
| Name | OWASP | Type | What it tests |
|
|
110
|
+
|------|-------|------|---------------|
|
|
111
|
+
| `tool-poisoning` | MCP01 | static | Hidden instructions, obfuscation, and prompt injection patterns in tool descriptions |
|
|
112
|
+
| `param-injection` | MCP02 | dynamic | Command, path, prompt, and SQL injection payloads per parameter — schema-aware |
|
|
113
|
+
| `shadow-tool` | MCP03 | static | Tool names that shadow built-ins or signal dangerous operations (namespace squatting, trust hijacking) |
|
|
114
|
+
| `info-disclosure` | MCP04 | dynamic | Credentials, filesystem paths, stack traces, and tokens leaked in tool responses |
|
|
115
|
+
| `schema-bypass` | MCP05 | dynamic | Whether tools properly reject inputs that violate their declared schema |
|
|
116
|
+
| `rug-pull` | MCP06 | dynamic | Re-enumerates the server after dynamic testing; diffs against initial snapshot to detect added, removed, or mutated tools |
|
|
117
|
+
| `response-flood` | MCP07 | dynamic | Excessively large or highly repetitive responses that could overflow an LLM context window or inject looping instructions |
|
|
118
|
+
| `auth-audit` | MCP08 | static | Tool names and descriptions suggesting missing, optional, or bypassable authentication |
|
|
119
|
+
| `schema-audit` | MCP09 | static | Weak schema definitions (missing required fields, unconstrained types) that expand the attack surface |
|
|
120
|
+
| `log-audit` | MCP10 | static | Tools that expose or tamper with audit logs — enables anti-forensic techniques or leaks operational data |
|
|
121
|
+
|
|
122
|
+
### Module groups
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# All modules (default)
|
|
126
|
+
--module all
|
|
127
|
+
|
|
128
|
+
# Static only (no live calls to the server)
|
|
129
|
+
--module static
|
|
130
|
+
|
|
131
|
+
# Dynamic only
|
|
132
|
+
--module dynamic
|
|
133
|
+
|
|
134
|
+
# Individual module
|
|
135
|
+
--module param-injection
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Transports
|
|
139
|
+
|
|
140
|
+
### stdio
|
|
141
|
+
|
|
142
|
+
Spawns the server process and communicates via stdin/stdout. Supports any command:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
corvus scan --transport stdio --cmd "python server.py"
|
|
146
|
+
corvus scan --transport stdio --cmd "npx @modelcontextprotocol/server-filesystem /tmp"
|
|
147
|
+
corvus scan --transport stdio --cmd "uvx my-mcp-server --arg value"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### HTTP
|
|
151
|
+
|
|
152
|
+
Connects to a running HTTP/SSE MCP server:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
corvus scan --transport http --url http://localhost:8080
|
|
156
|
+
|
|
157
|
+
# With auth
|
|
158
|
+
corvus scan --transport http --url http://localhost:8080 --header "Authorization: Bearer $TOKEN"
|
|
159
|
+
corvus scan --transport http --url http://localhost:8080 --header "X-API-Key: secret"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Config File
|
|
163
|
+
|
|
164
|
+
Create `corvus.toml` to avoid repeating CLI flags:
|
|
165
|
+
|
|
166
|
+
```toml
|
|
167
|
+
[scan]
|
|
168
|
+
transport = "stdio"
|
|
169
|
+
cmd = "python my_server.py"
|
|
170
|
+
modules = "all"
|
|
171
|
+
timeout = 30
|
|
172
|
+
sarif = false
|
|
173
|
+
fail_on = "high"
|
|
174
|
+
|
|
175
|
+
[scan.headers]
|
|
176
|
+
"Authorization" = "Bearer my-token"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Then run:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
corvus scan --config corvus.toml
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
CLI flags override config file values. The `--config` flag also accepts absolute paths.
|
|
186
|
+
|
|
187
|
+
## CLI Reference
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Usage: corvus scan [OPTIONS]
|
|
191
|
+
|
|
192
|
+
Scan an MCP server for security vulnerabilities.
|
|
193
|
+
|
|
194
|
+
Options:
|
|
195
|
+
-t, --transport TEXT stdio | http (overrides config)
|
|
196
|
+
--cmd TEXT Command to launch MCP server (stdio)
|
|
197
|
+
--url TEXT URL of MCP server (http)
|
|
198
|
+
-m, --module TEXT all | static | dynamic | <module-name> (overrides config)
|
|
199
|
+
-o, --output-dir PATH
|
|
200
|
+
--fail-on TEXT Exit 1 if findings at this severity or above
|
|
201
|
+
(critical|high|medium|low)
|
|
202
|
+
--timeout INTEGER Request timeout in seconds (overrides config)
|
|
203
|
+
--sarif Also write SARIF 2.1.0 report
|
|
204
|
+
--header TEXT HTTP header "Key: Value" (repeatable, for http transport)
|
|
205
|
+
-c, --config PATH Path to corvus.toml config file
|
|
206
|
+
--plugin-dir TEXT Directory to load external modules from (repeatable)
|
|
207
|
+
--help Show this message and exit.
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Other commands:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
corvus list-modules # list available modules with OWASP ID and type
|
|
214
|
+
corvus list-modules --plugin-dir ./plugins/ # include external plugins
|
|
215
|
+
corvus version # print version
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Output
|
|
219
|
+
|
|
220
|
+
Each scan creates a session directory under `corvus-sessions/<timestamp>/`:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
corvus-sessions/20260608-143022/
|
|
224
|
+
├── report.json # full structured result
|
|
225
|
+
├── report.md # human-readable with remediation guidance
|
|
226
|
+
└── report.sarif # SARIF 2.1.0 (only when --sarif is passed)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### SARIF integration
|
|
230
|
+
|
|
231
|
+
SARIF output is compatible with GitHub Advanced Security, VS Code SARIF Viewer, and any CI pipeline that consumes SARIF:
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
# GitHub Actions example
|
|
235
|
+
- name: Run Corvus
|
|
236
|
+
run: corvus scan --transport stdio --cmd "python server.py" --sarif --fail-on high
|
|
237
|
+
|
|
238
|
+
- name: Upload SARIF
|
|
239
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
240
|
+
with:
|
|
241
|
+
sarif_file: corvus-sessions/
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## CI Integration
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Exit 1 if any CRITICAL findings
|
|
248
|
+
corvus scan --transport stdio --cmd "python server.py" --fail-on critical
|
|
249
|
+
|
|
250
|
+
# Exit 1 if any HIGH or above
|
|
251
|
+
corvus scan --transport stdio --cmd "python server.py" --fail-on high
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Severity levels (ascending): `info` → `low` → `medium` → `high` → `critical`
|
|
255
|
+
|
|
256
|
+
## Plugin System
|
|
257
|
+
|
|
258
|
+
Add custom modules without modifying Corvus source.
|
|
259
|
+
|
|
260
|
+
### Directory-based plugins
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
corvus scan --transport stdio --cmd "python server.py" --plugin-dir ./my-modules/
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Each `.py` file in the directory is loaded as a module. The file must define a class that inherits from `BaseModule`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from corvus.modules.base import BaseModule, Finding, Severity
|
|
270
|
+
|
|
271
|
+
class MyCustomModule(BaseModule):
|
|
272
|
+
name = "my-check"
|
|
273
|
+
owasp_id = "MCP-CUSTOM"
|
|
274
|
+
module_type = "static"
|
|
275
|
+
description = "Custom check for my organization"
|
|
276
|
+
|
|
277
|
+
async def run(self, surface, transport):
|
|
278
|
+
findings = []
|
|
279
|
+
for tool in surface.tools:
|
|
280
|
+
if "dangerous_pattern" in tool.description:
|
|
281
|
+
findings.append(Finding(
|
|
282
|
+
rule_id="MY001",
|
|
283
|
+
tool_name=tool.name,
|
|
284
|
+
severity=Severity.HIGH,
|
|
285
|
+
title="Dangerous pattern detected",
|
|
286
|
+
description=f"Tool '{tool.name}' contains a dangerous pattern.",
|
|
287
|
+
remediation="Remove or sanitize the pattern.",
|
|
288
|
+
))
|
|
289
|
+
return findings
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Package-based plugins
|
|
293
|
+
|
|
294
|
+
Register via `pyproject.toml` entry points:
|
|
295
|
+
|
|
296
|
+
```toml
|
|
297
|
+
[project.entry-points."corvus.modules"]
|
|
298
|
+
my-check = "my_package.modules.my_check:MyCustomModule"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
After `pip install my-package`, Corvus auto-discovers the module.
|
|
302
|
+
|
|
303
|
+
## Development
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
git clone https://github.com/CobaltoSec/corvus
|
|
307
|
+
cd corvus
|
|
308
|
+
pip install -e ".[dev]"
|
|
309
|
+
pytest
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT
|