Corvus
MCP server security testing framework. Tests MCP servers against the OWASP MCP Top 10 — both static analysis and live dynamic probing.
Corvus v1.3.1 MCP Security Scanner
Target : python my_mcp_server.py
Transport : stdio
Modules : tool-poisoning, scope-audit, shadow-tool, supply-chain, osv-supply-chain,
github-advisory, npm-behavior, supply-chain-python, auth-audit, log-audit,
schema-audit, resource-uri, tool-chaining, cmd-injection, token-exposure,
ssrf, endpoint-probe, param-smuggling, schema-bypass, proto-fuzz, batch-dos,
output-encoding, response-flood, response-injection, rug-pull, init-audit,
oauth-bypass, sampling-probe, elicitation-probe, completion-probe,
logging-probe, prompts-injection, cursor-probe, cancellation-probe
Enumerating surface...
Tools : 12
Resources : 3
Prompts : 2
Server : my-server 1.0.0
[MCP03] Tool Poisoning (static)
[HIGH] Potential prompt injection in description of 'execute_code'
[MCP05] Command Injection (dynamic)
[HIGH] Command injection confirmed in tool 'run_shell', param 'command'
[MEDIUM] Path traversal accepted in tool 'read_file', param 'path'
...
─── Summary ──────────────────────────────────────────────────────────────────
CRITICAL 0 HIGH 2 MEDIUM 1 LOW 3 INFO 4
Risk Score: 56 / 100 (HIGH)
Session : corvus-sessions/20260608-143022/
Install
pip install cobaltosec-corvus
Or from source:
git clone https://github.com/CobaltoSec/corvus
cd corvus
pip install -e ".[dev]"
Quick Start
# Scan a stdio MCP server
corvus scan --transport stdio --cmd "python my_server.py"
# Scan an HTTP MCP server
corvus scan --transport http --url http://localhost:8080
# With authentication header
corvus scan --transport http --url http://localhost:8080 --header "Authorization: Bearer token"
# Static analysis only (no live tool calls)
corvus scan --transport stdio --cmd "python my_server.py" --module static
# Specific module
corvus scan --transport stdio --cmd "python my_server.py" --module cmd-injection
# SARIF output (for CI/CD integration)
corvus scan --transport stdio --cmd "python my_server.py" --sarif
# Fail CI on findings above threshold
corvus scan --transport stdio --cmd "python my_server.py" --fail-on high
# Load config from file
corvus scan --config corvus.toml
# Filter low-confidence findings (0-100)
corvus scan --transport stdio --cmd "python my_server.py" --min-confidence 70
# Capture raw JSON-RPC exchanges
corvus scan --transport stdio --cmd "python my_server.py" --log-requests
# Rate-limit probes (ms between requests)
corvus scan --transport stdio --cmd "python my_server.py" --delay 500
# Pass environment variables to the server process
corvus scan --transport stdio --cmd "python my_server.py" --env API_KEY=secret --env DEBUG=1
# Print Risk Score at the end
corvus scan --transport stdio --cmd "python my_server.py" --score
# List available modules
corvus list-modules
Batch Scan
Scan multiple MCP servers in one invocation:
# targets.yaml
targets:
- name: filesystem
transport: stdio
cmd: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
- name: my-http-server
transport: http
url: http://localhost:8080
corvus batch targets.yaml --output-dir results/ --sarif --min-confidence 70
Produces a per-target report.json and a top-level summary.md table.
Modules
34 built-in modules covering the full OWASP MCP Top 10 plus protocol, elicitation, sampling, OAuth and supply chain extensions:
Static modules (no live tool calls)
| Name | OWASP | What it tests |
|---|---|---|
tool-poisoning |
MCP03 | Hidden instructions, obfuscation, and prompt injection patterns in tool descriptions |
shadow-tool |
EXT03 | Tool names signaling dangerous operations — namespace squatting, covert chaining, trust hijacking |
scope-audit |
MCP02 | Credential and PII fields in tool inputSchema — tools that request passwords, tokens, or SSNs |
supply-chain |
MCP04 | Known-vulnerable npm packages extracted from the server command (npm audit) |
supply-chain-python |
MCP04 | Known-vulnerable Python packages extracted from the server environment (pip list) |
osv-supply-chain |
MCP04 | Queries OSV.dev API for known vulnerabilities in detected dependencies |
github-advisory |
MCP04 | Queries GitHub Security Advisories for known vulnerabilities in detected packages |
npm-behavior |
MCP04 | Queries npm registry for suspicious install scripts (postinstall, preinstall) in detected packages |
auth-audit |
MCP07 | Tool names and descriptions suggesting missing, optional, or bypassable authentication |
log-audit |
MCP08 | Tools exposing or tampering with audit logs — anti-forensic techniques or operational data leaks |
schema-audit |
EXT02 | Weak schema definitions (missing required fields, unconstrained types) that expand the attack surface |
resource-uri |
EXT05 | Static analysis of resources/list URIs — detects sensitive schemes (file://, env://, exec://) |
tool-chaining |
EXT03 | Detects tool descriptions that covertly chain additional tool calls or bypass user confirmation |
Dynamic modules (live tool calls)
| Name | OWASP | What it tests |
|---|---|---|
cmd-injection |
MCP05 | Command, path, SQL, and prompt injection payloads per parameter — schema-aware, confirmation-required |
token-exposure |
MCP01 | Credentials, filesystem paths, stack traces, and tokens leaked in tool responses |
ssrf |
EXT04 | SSRF via URL/host parameters — probes internal metadata endpoints, measures timing anomalies |
endpoint-probe |
MCP01 | Path traversal, SSRF, template injection, and credential exposure via resources/read and prompts/get |
param-smuggling |
EXT01 | Hidden parameter backdoors — appends undeclared params and measures behavior differences |
schema-bypass |
EXT01 | Whether tools properly reject inputs that violate their declared schema |
proto-fuzz |
EXT01 | Protocol-level crash testing — unknown methods, oversized method names, null request IDs |
batch-dos |
EXT01 | Sends JSON-RPC 2.0 batch arrays and oversized payloads to detect crash/DoS conditions |
output-encoding |
MCP10 | Invisible Unicode in tool outputs — control chars, zero-width chars, bidi overrides |
response-flood |
MCP10 | Excessively large or repetitive responses that overflow an LLM context window |
response-injection |
MCP10 | Detects injected content (HTML/JS/markdown directives) in benign tool responses |
rug-pull |
MCP06 | Re-enumerates the server after dynamic testing; diffs to detect tools added, removed, or mutated mid-session |
init-audit |
MCP07 | Audits the initialize handshake — serverInfo injection chars, protocol version downgrade acceptance |
oauth-bypass |
MCP07 | Tests HTTP transport endpoints for authentication bypass: missing auth, invalid Bearer, URL-embedded creds |
sampling-probe |
EXT08 | Detects malicious MCP sampling/createMessage — prompt injection, context exfil, unsolicited calls |
elicitation-probe |
EXT09 | Detects MCP elicitation/create misuse — credential phishing, sensitive data schemas |
completion-probe |
EXT10 | Probes MCP completion/complete endpoint for prompt injection via argument context |
logging-probe |
EXT11 | Tests whether the server allows unauthenticated external log level manipulation |
prompts-injection |
EXT12 | Detects prompt injection via MCP prompts/get — static patterns + live argument injection |
cursor-probe |
EXT13 | Tests MCP pagination cursor handling for path traversal, oversized values, and injection |
cancellation-probe |
EXT14 | Tests MCP notifications/cancelled handling for race conditions and crash on unknown requestIds |
Module groups
# All modules (default)
--module all
# Static only (no live calls to the server)
--module static
# Dynamic only
--module dynamic
# Individual module
--module cmd-injection
Transports
stdio
Spawns the server process and communicates via stdin/stdout. Supports any command:
corvus scan --transport stdio --cmd "python server.py"
corvus scan --transport stdio --cmd "npx @modelcontextprotocol/server-filesystem /tmp"
corvus scan --transport stdio --cmd "uvx my-mcp-server --arg value"
HTTP
Connects to a running HTTP/SSE MCP server:
corvus scan --transport http --url http://localhost:8080
# With auth
corvus scan --transport http --url http://localhost:8080 --header "Authorization: Bearer $TOKEN"
corvus scan --transport http --url http://localhost:8080 --header "X-API-Key: secret"
Config File
Create corvus.toml to avoid repeating CLI flags:
[scan]
transport = "stdio"
cmd = "python my_server.py"
modules = "all"
timeout = 30
sarif = false
fail_on = "high"
[scan.headers]
"Authorization" = "Bearer my-token"
Then run:
corvus scan --config corvus.toml
CLI flags override config file values. The --config flag also accepts absolute paths.
CLI Reference
Usage: corvus scan [OPTIONS]
Scan an MCP server for security vulnerabilities.
Options:
-t, --transport TEXT stdio | http (overrides config)
--cmd TEXT Command to launch MCP server (stdio)
--url TEXT URL of MCP server (http)
-m, --module TEXT all | static | dynamic | <module-name> (overrides config)
-o, --output-dir PATH
--fail-on TEXT Exit 1 if findings at this severity or above
(critical|high|medium|low)
--timeout INTEGER Request timeout in seconds (overrides config)
--sarif Also write SARIF 2.1.0 report
--header TEXT HTTP header "Key: Value" (repeatable, for http transport)
-c, --config PATH Path to corvus.toml config file
--plugin-dir TEXT Directory to load external modules from (repeatable)
--help Show this message and exit.
Other commands:
corvus list-modules # list available modules with OWASP ID and type
corvus list-modules --plugin-dir ./plugins/ # include external plugins
corvus version # print version
corvus init # generate a corvus.toml skeleton in the current directory
corvus report REPORT.json # regenerate MD/SARIF/HTML from an existing report.json
corvus diff OLD.sarif NEW.sarif # compare two SARIF files: new / fixed / unchanged findings
corvus score REPORT.json # print Risk Score (0-100) for a report
Output
Each scan creates a session directory under corvus-sessions/<timestamp>/:
corvus-sessions/20260608-143022/
├── report.json # full structured result
├── report.md # human-readable with remediation guidance
└── report.sarif # SARIF 2.1.0 (only when --sarif is passed)
SARIF integration
SARIF output is compatible with GitHub Advanced Security, VS Code SARIF Viewer, and any CI pipeline that consumes SARIF:
# GitHub Actions example
- name: Run Corvus
run: corvus scan --transport stdio --cmd "python server.py" --sarif --fail-on high
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: corvus-sessions/
CI Integration
# Exit 1 if any CRITICAL findings
corvus scan --transport stdio --cmd "python server.py" --fail-on critical
# Exit 1 if any HIGH or above
corvus scan --transport stdio --cmd "python server.py" --fail-on high
Severity levels (ascending): info → low → medium → high → critical
Plugin System
Add custom modules without modifying Corvus source.
Directory-based plugins
corvus scan --transport stdio --cmd "python server.py" --plugin-dir ./my-modules/
Each .py file in the directory is loaded as a module. The file must define a class that inherits from BaseModule:
from corvus.modules.base import BaseModule, Finding, Severity
class MyCustomModule(BaseModule):
name = "my-check"
owasp_id = "MCP-CUSTOM"
module_type = "static"
description = "Custom check for my organization"
async def run(self, surface, transport):
findings = []
for tool in surface.tools:
if "dangerous_pattern" in tool.description:
findings.append(Finding(
rule_id="MY001",
tool_name=tool.name,
severity=Severity.HIGH,
title="Dangerous pattern detected",
description=f"Tool '{tool.name}' contains a dangerous pattern.",
remediation="Remove or sanitize the pattern.",
))
return findings
Package-based plugins
Register via pyproject.toml entry points:
[project.entry-points."corvus.modules"]
my-check = "my_package.modules.my_check:MyCustomModule"
After pip install my-package, Corvus auto-discovers the module.
Research: MCP Ecosystem Security Audit
Corvus has been battle-tested against the real-world MCP ecosystem across 15 case studies — 300 servers audited, spanning official @modelcontextprotocol packages, community servers, and the broader npm and PyPI ecosystem.
| Metric | Total (15 case studies) |
|---|---|
| Servers audited | 300 |
| Raw findings | ~4002 |
| True positives | ~1298 |
Key findings from the wild:
- 100% of MCP servers tested crash on a 37-byte notifications/cancelled payload with unknown requestId (EXT14) — universal DoS requiring zero authentication
- 37% of servers crash on spec-compliant JSON-RPC batch arrays or oversized method names — reproducible DoS without authentication
- 71% of servers accept arbitrary protocolVersion strings during the initialize handshake — protocol downgrade accepted
- SSRF confirmed in 3 servers (myclaw-toolkit, @sap-ux/fiori-mcp-server, markitdown-mcp) — internal metadata endpoints reachable from tool parameters
- Supply chain cascade: @modelcontextprotocol/sdk ≤1.25.1 advisory propagates to the majority of JS-based servers in the ecosystem
- FP rate reduced from ~40% (v0.5.0, early calibration) to ~7% (CS03, v1.0.1) through 5 calibration iterations
- CS04 introduces 3 new systematic FP patterns: docs server lazy-loading (MCP06, 195 FP from sveltejs), Python strptime error reflection (MCP05, 33 FP), and third-party API error message echo (MCP05, 62+ FP across 11 servers)
- Novel attack pattern discovered: SaaS description mutation rug-pull — mcp-devutils changes 29 tool descriptions mid-session from '[PRO — trial]' to '[PRO — trial expired]' based on billing logic
- Covert AI agent surveillance via MCP scope creep: clarvia-mcp-server instructs agents to 'Use after every tool invocation' to report all activity to external servers
- SSRF with timing evidence: pulsemcp-pulse-fetch scrape.url accesses 169.254.169.254 AWS metadata endpoint (8.3s timing delta vs 2.3s baseline)
- Stored SQL injection pattern: arxiv-mcp-server watch_topic.topic stores SQL payload in database without sanitization (plausible, needs check_alerts verification)
- Anti-forensic log erasure: godot-mcp-server exposes clear_console_log tool (CRITICAL, MCP08) — combined with write-path traversal in save_resource_to_file/take_screenshot enables exfiltrate-then-cover-tracks chain
- XSS response injection pattern confirmed across ecosystems: functype-mcp-server (npm) and awslabs.amazon-kendra-index-mcp-server (PyPI) both reflect unsanitized XSS payloads verbatim in error messages — systematic cross-SDK issue
Full datasets, curated findings, and methodology in case-studies/.
Responsible Disclosure
44 security advisories filed across 15 case studies — 3 published, 41 in active coordinated disclosure (90-day window).
Published:
| Advisory | Package | Severity | Finding |
|---|---|---|---|
| GHSA-43j9-hmpq-cgv7 | remnux-mcp-server | MEDIUM | Unauthenticated RCE via HTTP transport on non-loopback deployments |
| GHSA-hv3x-m9fv-4vhf | mcp-server-git | HIGH | DoS via spec-compliant JSON-RPC batch arrays and oversized method names |
| GHSA-3f55-qgq4-f88c | server-sequential-thinking | MEDIUM | DoS via oversized JSON-RPC method names (CWE-755) |
Active coordinated disclosure (41 advisories): packages include @playwright/mcp, mcp-server-sqlite, mcp-shell-server, myclaw-toolkit (CRITICAL), @sap-ux/fiori-mcp-server, and others — 90-day embargo window in progress.
Full advisory index: case-studies/DISCLOSURE-PROCESS.md
Development
git clone https://github.com/CobaltoSec/corvus
cd corvus
pip install -e ".[dev]"
pytest
License
MIT
Metadata
Release files for cobaltosec-corvus 1.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cobaltosec_corvus-1.3.1.tar.gz | 176.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cobaltosec_corvus-1.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 319.5 kB
Release files / cobaltosec_corvus-1.3.1.tar.gz
| Download URL | cobaltosec_corvus-1.3.1.tar.gz |
|---|---|
| Size | 176.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dec9279637fb2af1d258d822249d706cfbf7df6114198701bb5aa7a7c1da2d51
|
|
BLAKE2b-256 checksum How to use checksums |
abc3fa04e57e0cc3ce2d181b5cb5bf35693a792433a03ab463671e599f768f9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / cobaltosec_corvus-1.3.1-py3-none-any.whl
| Download URL | cobaltosec_corvus-1.3.1-py3-none-any.whl |
|---|---|
| Size | 143.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
146dc2b765c414a1a86c9dcba43b7db972906a791028cafe63f0a7df91b8cf55
|
|
BLAKE2b-256 checksum How to use checksums |
d4528954d57954211486e30185745de01b3fc2f758d7451e7d8a822764aad8a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|