Corvus
⭐ If Corvus found a vulnerability in your MCP server, star the repo — it helps other security teams discover the tool.
Vulnerabilities found: GHSA-hv3x (mcp-server-git, DoS HIGH) · GHSA-43j9 (remnux-mcp-server, RCE MEDIUM) · GHSA-jgxf (campertunity-ai-tools, SSRF HIGH) · GHSA-3f55 (server-sequential-thinking, DoS MEDIUM) — +54 in coordinated disclosure
MCP server security testing framework. Tests MCP servers against the MCP Security Top 10 (MST-10) — an independent taxonomy of MCP-specific security risks, covering 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
# Try it now — scan a real MCP server in one command (needs Node.js):
pip install cobaltosec-corvus
corvus scan "npx -y @modelcontextprotocol/server-filesystem /tmp"
# 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 18 case studies — 414 servers audited, spanning official @modelcontextprotocol packages, community servers, and the broader npm and PyPI ecosystem.
| Metric | Total (18 case studies) |
|---|---|
| Servers audited | 414 |
| Raw findings | ~5856 |
| True positives | ~1350 |
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
174 security advisories filed across 18 case studies — 37 published, 137 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) |
| GHSA-jgxf-j67w-w284 | campertunity-ai-tools | HIGH | SSRF via booking API URL parameter — internal metadata endpoints reachable |
| GHSA-prc4-649r-564g | localparse-mcp | HIGH | SSRF confirmed in parse_url — timing and timeout signals confirmed (CWE-918) |
| GHSA-32vx-mq6h-p8f3 | emilia-protocol | HIGH | Prompt template injection via trust_decision/receipt_quality_check + forced compliance gate (EXT03/EXT12) |
| GHSA-wx78-8jx3-wcv9 | @tensorfeed/mcp-server | HIGH | XSS reflection cluster across 6 tools — unsanitized payloads echoed verbatim (MCP05) |
| GHSA-qwwj-38wj-ffvw | myclaw-toolkit | CRITICAL | SSRF + LFI via unvalidated URL scheme in rss_feed and read_page MCP tools |
| GHSA-7763-c5gf-v5fj | mcp-shell-server | HIGH | Injection via unsanitized working_directory and entry_id parameters in shell tools |
| GHSA-2g9w-p2x3-97pp | mcp-devutils | CRITICAL | RSA private key returned in plaintext by rsa_keygen; tool descriptions mutate mid-session |
| GHSA-w5c8-hjv7-p95r | @aryanbv/pdf-toolkit-mcp | MEDIUM | Prompt injection via unescaped argument interpolation in 5 MCP prompts |
| GHSA-6j6r-pf9m-gqxf | @benborla29/mcp-server-mysql | MEDIUM | Race-condition DoS via MCP request cancellation |
| GHSA-q974-p8xv-f7c7 | @jpisnice/shadcn-ui-mcp-server | HIGH | Prompt template injection in three MCP prompt handlers |
| GHSA-m6h2-xr6q-9m7p | @idachev/mcp-javadc | HIGH | Injection via unsanitized classFilePath and packageName in Java decompilation tools |
| GHSA-m2x9-5c27-vvc3 | @cyanheads/pubmed-mcp-server | HIGH | Injection in pubmed_search_articles parameters allows query manipulation |
| GHSA-rqqc-2cx5-vp44 | mcp-server-nationalparks | HIGH | Injection via unvalidated stateCode parameter in findParks MCP tool |
| GHSA-3734-qff5-qvrr | llama_index | HIGH | Sandbox bypass via operator.attrgetter() string-literal dunder access in EvaporateExtractor |
| GHSA-2vg9-w9p2-56m2 | superagi | CRITICAL | RCE via unsafe eval() on LLM output in task queue handler |
| GHSA-526j-x7rf-h576 | cheshire-cat-ai | CRITICAL | Auth bypass via unauthenticated OAuth callback — admin JWT without credentials |
Listing 19 of 37 published advisories — the remaining 18 are public on the GitHub Advisory Database and pending a curated description here.
Active coordinated disclosure (137 advisories): 90-day embargo window in progress.
Full advisory index: case-studies/DISCLOSURE-PROCESS.md
FAQ
How does Corvus compare to mcp-scan / Snyk Agent Scan?
mcp-scan (acquired by Snyk in 2025, now Snyk Agent Scan) is the most widely deployed MCP security tool and does a great job on prompt injection and tool poisoning. If that's your primary concern, it's worth running both.
The tools approach MCP security from different angles:
| mcp-scan / Snyk Agent Scan | Corvus | |
|---|---|---|
| Scope | Prompt injection, tool poisoning, toxic flows, cross-origin escalation | Full MCP Security Top 10 (MST-10) — 34 modules |
| Perspective | Client-side: reads your MCP config file | Server-side: connects directly via stdio or HTTP |
| Probing | Static analysis + optional proxy monitoring | Static analysis + live dynamic probing (actual tool calls with payloads) |
| CI/CD | Basic | SARIF output, --fail-on high, batch scanning |
| Coverage | Prompt injection / poisoning focused | Adds cmd injection, SSRF, auth bypass, supply chain, schema bypass, parameter smuggling, DoS, OAuth, sampling/elicitation misuse |
| Real-time | ✅ Proxy mode for live monitoring | ❌ Point-in-time scan only |
| Disclosures | No public advisory program | 63 GHSAs filed (9 published, 54 in active disclosure) |
| Backing | Snyk (enterprise) | Independent, security research focused |
When to use mcp-scan: you want real-time proxy monitoring of agent traffic, or you need the Snyk enterprise integration.
When to use Corvus: you're doing a full security audit, need CI/CD-compatible output, or want to test attack surfaces beyond prompt injection (command injection, SSRF, auth bypass, supply chain, etc.).
They're complementary — mcp-scan excels at runtime monitoring, Corvus at point-in-time depth.
Development
git clone https://github.com/CobaltoSec/corvus
cd corvus
pip install -e ".[dev]"
pytest
License
MIT
⭐ Corvus is open source and actively maintained. If it helped you secure your MCP stack, a GitHub star goes a long way.
Metadata
Release files for cobaltosec-corvus 1.3.2
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.2.tar.gz | 197.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cobaltosec_corvus-1.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 355.3 kB
Release files / cobaltosec_corvus-1.3.2.tar.gz
| Download URL | cobaltosec_corvus-1.3.2.tar.gz |
|---|---|
| Size | 197.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e6b7fd4c5c4005a00286e4c75e7f132670400aa19d8a8f7a3cbd9d80d9ab9825
|
|
BLAKE2b-256 checksum How to use checksums |
79c2e8f61beae9bd2baa14aa250c015767e883ecfd21187f29a9a9235634f0f2
|
| 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.2-py3-none-any.whl
| Download URL | cobaltosec_corvus-1.3.2-py3-none-any.whl |
|---|---|
| Size | 157.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3b9c084b42e90f64004ea5b4d7c28901a9517fa36095c57175e22073a19946f4
|
|
BLAKE2b-256 checksum How to use checksums |
6db6dd83e86129402a330240d8e0f1d4916e5535704a1815f5dbd140c202d472
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|