correctover-security-audit 1.0.0__py3-none-any.whl
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.
- correctover_agent/__init__.py +8 -0
- correctover_agent/cli.py +224 -0
- correctover_agent/engine.py +348 -0
- correctover_agent/taxonomy.py +555 -0
- correctover_security_audit-1.0.0.dist-info/METADATA +40 -0
- correctover_security_audit-1.0.0.dist-info/RECORD +8 -0
- correctover_security_audit-1.0.0.dist-info/WHEEL +4 -0
- correctover_security_audit-1.0.0.dist-info/entry_points.txt +2 -0
correctover_agent/cli.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
correctover-security-audit CLI โ One-command AI Agent security audit.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
correctover-security-audit scan-file <path>
|
|
6
|
+
correctover-security-audit scan-dir <path>
|
|
7
|
+
correctover-security-audit scan-mcp <config.json>
|
|
8
|
+
correctover-security-audit scan-agent <framework> <code_file>
|
|
9
|
+
correctover-security-audit quick
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from .engine import SecurityAuditEngine, AuditReport
|
|
20
|
+
from .taxonomy import get_summary, ZDI_CASES, VERIFIED_POCS
|
|
21
|
+
|
|
22
|
+
VERSION = "1.0.0"
|
|
23
|
+
CTA_URL = "https://correctover.com"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _format_finding(f: "Finding", color: bool = True) -> str:
|
|
27
|
+
"""Format a single finding for terminal output."""
|
|
28
|
+
if color:
|
|
29
|
+
sev_colors = {"CRITICAL": "\033[91m", "HIGH": "\033[93m", "MEDIUM": "\033[96m", "LOW": "\033[90m", "INFO": "\033[0m"}
|
|
30
|
+
reset = "\033[0m"
|
|
31
|
+
bold = "\033[1m"
|
|
32
|
+
dim = "\033[2m"
|
|
33
|
+
else:
|
|
34
|
+
sev_colors = {k: "" for k in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]}
|
|
35
|
+
reset = bold = dim = ""
|
|
36
|
+
|
|
37
|
+
c = sev_colors.get(f.severity.value, "")
|
|
38
|
+
cve_str = f" [{', '.join(f.cve_refs)}]" if f.cve_refs else ""
|
|
39
|
+
return (
|
|
40
|
+
f" {c}[{f.severity.value}]{reset} {bold}{f.rule_name}{reset}{cve_str}\n"
|
|
41
|
+
f" {dim}CWE-{f.cwe} | CVSS {f.cvss} | {f.file_path}:{f.line_number}{reset}\n"
|
|
42
|
+
f" {dim}Match: {f.matched_pattern}{reset}\n"
|
|
43
|
+
f" {dim}โ {f.remediation}{reset}\n"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _print_report(report: AuditReport, format: str, output: Optional[str]):
|
|
48
|
+
"""Print or write report in specified format."""
|
|
49
|
+
if format == "json":
|
|
50
|
+
content = json.dumps(report.to_dict(), indent=2, ensure_ascii=False)
|
|
51
|
+
if output:
|
|
52
|
+
Path(output).write_text(content)
|
|
53
|
+
click.echo(f"Report written to {output}")
|
|
54
|
+
else:
|
|
55
|
+
click.echo(content)
|
|
56
|
+
|
|
57
|
+
elif format == "markdown":
|
|
58
|
+
lines = [
|
|
59
|
+
f"# ๐ Correctover Security Audit Report",
|
|
60
|
+
f"",
|
|
61
|
+
f"**Target**: `{report.target}` | **Type**: {report.target_type}",
|
|
62
|
+
f"**Scan duration**: {report.scan_duration_ms:.1f}ms | **Rules checked**: {report.rules_checked}",
|
|
63
|
+
f"",
|
|
64
|
+
f"## Summary",
|
|
65
|
+
f"",
|
|
66
|
+
f"| Metric | Value |",
|
|
67
|
+
f"|--------|-------|",
|
|
68
|
+
f"| Total findings | {report.total_findings} |",
|
|
69
|
+
f"| ๐ด Critical | {report.critical_count} |",
|
|
70
|
+
f"| ๐ High | {report.high_count} |",
|
|
71
|
+
f"| Max CVSS | {report.cvss_max} |",
|
|
72
|
+
f"",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
if report.findings:
|
|
76
|
+
lines.append("## Findings")
|
|
77
|
+
lines.append("")
|
|
78
|
+
for f in report.findings:
|
|
79
|
+
cve_str = f" `{'`, `'.join(f.cve_refs)}`" if f.cve_refs else ""
|
|
80
|
+
lines.append(f"### {f.severity.value}: {f.rule_name}")
|
|
81
|
+
lines.append(f"- **CWE-{f.cwe}** | **CVSS {f.cvss}**{cve_str}")
|
|
82
|
+
lines.append(f"- **File**: `{f.file_path}:{f.line_number}`")
|
|
83
|
+
lines.append(f"- **Match**: `{f.matched_pattern}`")
|
|
84
|
+
lines.append(f"- **Fix**: {f.remediation}")
|
|
85
|
+
lines.append("")
|
|
86
|
+
|
|
87
|
+
lines.append("---")
|
|
88
|
+
lines.append(f"*Scanned by [Correctover Security Audit]({CTA_URL}) v{VERSION}*")
|
|
89
|
+
lines.append(f"*Database: 215 fault types, 88 detection rules, 52 ZDI cases*")
|
|
90
|
+
|
|
91
|
+
content = "\n".join(lines)
|
|
92
|
+
if output:
|
|
93
|
+
Path(output).write_text(content)
|
|
94
|
+
click.echo(f"Report written to {output}")
|
|
95
|
+
else:
|
|
96
|
+
click.echo(content)
|
|
97
|
+
|
|
98
|
+
else: # terminal
|
|
99
|
+
GREEN = "\033[92m"; RED = "\033[91m"; YELLOW = "\033[93m"
|
|
100
|
+
CYAN = "\033[96m"; BOLD = "\033[1m"; DIM = "\033[2m"; RESET = "\033[0m"
|
|
101
|
+
|
|
102
|
+
click.echo(f"\n{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
103
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {BOLD}Correctover Security Audit Report{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
104
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
105
|
+
click.echo(f" Target: {BOLD}{report.target}{RESET} ({report.target_type})")
|
|
106
|
+
click.echo(f" Duration: {report.scan_duration_ms:.1f}ms | Rules: {report.rules_checked}")
|
|
107
|
+
click.echo()
|
|
108
|
+
|
|
109
|
+
if report.total_findings == 0:
|
|
110
|
+
click.echo(f" {GREEN}โ
No vulnerabilities found{RESET}\n")
|
|
111
|
+
else:
|
|
112
|
+
summary_color = RED if report.critical_count > 0 else YELLOW
|
|
113
|
+
click.echo(f" {summary_color}{BOLD}๐จ {report.total_findings} findings{RESET}"
|
|
114
|
+
f" | {RED}{report.critical_count} critical{RESET}"
|
|
115
|
+
f" | {YELLOW}{report.high_count} high{RESET}")
|
|
116
|
+
click.echo()
|
|
117
|
+
for f in report.findings:
|
|
118
|
+
click.echo(_format_finding(f))
|
|
119
|
+
|
|
120
|
+
# CTA
|
|
121
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
122
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} ๐ก๏ธ Fix all issues โ {CTA_URL} {CYAN}{BOLD}โ{RESET}")
|
|
123
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {DIM}215 fault types ยท 88 detection rules ยท 22ยตs P50{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
124
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@click.group()
|
|
128
|
+
@click.version_option(version=VERSION, prog_name="correctover-security-audit")
|
|
129
|
+
def cli():
|
|
130
|
+
"""๐ Correctover Security Audit โ AI Agent vulnerability scanner.
|
|
131
|
+
|
|
132
|
+
Powered by CCS fault taxonomy v2.5:
|
|
133
|
+
215 fault types ยท 88 detection rules ยท 52 ZDI cases ยท 8 verified PoCs.
|
|
134
|
+
"""
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@cli.command()
|
|
139
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
140
|
+
@click.option("--format", "output_format", type=click.Choice(["terminal", "json", "markdown"]), default="terminal")
|
|
141
|
+
@click.option("--output", "-o", default=None, help="Output file path")
|
|
142
|
+
def scan_file(path: str, output_format: str, output: Optional[str]):
|
|
143
|
+
"""Scan a single file for vulnerabilities."""
|
|
144
|
+
engine = SecurityAuditEngine()
|
|
145
|
+
report = engine.scan_file(path)
|
|
146
|
+
_print_report(report, output_format, output)
|
|
147
|
+
sys.exit(1 if report.critical_count > 0 else 0)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@cli.command()
|
|
151
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
152
|
+
@click.option("--format", "output_format", type=click.Choice(["terminal", "json", "markdown"]), default="terminal")
|
|
153
|
+
@click.option("--output", "-o", default=None, help="Output file path")
|
|
154
|
+
def scan_dir(path: str, output_format: str, output: Optional[str]):
|
|
155
|
+
"""Recursively scan a directory."""
|
|
156
|
+
engine = SecurityAuditEngine()
|
|
157
|
+
report = engine.scan_directory(path)
|
|
158
|
+
_print_report(report, output_format, output)
|
|
159
|
+
sys.exit(1 if report.critical_count > 0 else 0)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@cli.command()
|
|
163
|
+
@click.argument("config_file", type=click.Path(exists=True))
|
|
164
|
+
@click.option("--format", "output_format", type=click.Choice(["terminal", "json", "markdown"]), default="terminal")
|
|
165
|
+
@click.option("--output", "-o", default=None, help="Output file path")
|
|
166
|
+
def scan_mcp(config_file: str, output_format: str, output: Optional[str]):
|
|
167
|
+
"""Scan an MCP server configuration file."""
|
|
168
|
+
config = json.loads(Path(config_file).read_text())
|
|
169
|
+
engine = SecurityAuditEngine()
|
|
170
|
+
report = engine.scan_mcp_config(config)
|
|
171
|
+
_print_report(report, output_format, output)
|
|
172
|
+
sys.exit(1 if report.critical_count > 0 else 0)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@cli.command()
|
|
176
|
+
@click.argument("framework", type=click.Choice(["crewai", "smolagents", "llamaindex", "autogen", "langgraph", "generic"]))
|
|
177
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
178
|
+
@click.option("--format", "output_format", type=click.Choice(["terminal", "json", "markdown"]), default="terminal")
|
|
179
|
+
@click.option("--output", "-o", default=None, help="Output file path")
|
|
180
|
+
def scan_agent(framework: str, code_file: str, output_format: str, output: Optional[str]):
|
|
181
|
+
"""Scan Agent framework code for CWE-636 and other vulnerabilities."""
|
|
182
|
+
code = Path(code_file).read_text()
|
|
183
|
+
engine = SecurityAuditEngine()
|
|
184
|
+
report = engine.scan_agent_framework(framework, code)
|
|
185
|
+
_print_report(report, output_format, output)
|
|
186
|
+
sys.exit(1 if report.critical_count > 0 else 0)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@cli.command()
|
|
190
|
+
def quick():
|
|
191
|
+
"""Quick demo: scan a sample vulnerable config."""
|
|
192
|
+
sample_config = {
|
|
193
|
+
"transport": "stdio",
|
|
194
|
+
"env": {"API_KEY": "sk-1234567890", "DATABASE_URL": "postgres://..."},
|
|
195
|
+
"tools": [
|
|
196
|
+
{"name": "execute_command", "implementation": "os.system(command)", "description": "Run shell commands"},
|
|
197
|
+
{"name": "read_file", "implementation": "open(filename).read()", "description": "Read files"},
|
|
198
|
+
{"name": "fetch_url", "implementation": "requests.get(url)", "description": "Fetch URLs"},
|
|
199
|
+
],
|
|
200
|
+
}
|
|
201
|
+
engine = SecurityAuditEngine()
|
|
202
|
+
report = engine.scan_mcp_config(sample_config)
|
|
203
|
+
click.echo("\n๐ Scanning sample MCP configuration...\n")
|
|
204
|
+
_print_report(report, "terminal", None)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@cli.command()
|
|
208
|
+
def stats():
|
|
209
|
+
"""Show vulnerability database statistics."""
|
|
210
|
+
s = get_summary()
|
|
211
|
+
click.echo(f"\n๐ CCS Fault Taxonomy v2.5")
|
|
212
|
+
click.echo(f" Fault types: {s['fault_types']}")
|
|
213
|
+
click.echo(f" Detection rules: {s['total_rules']} ({s['high_confidence_rules']} high-confidence)")
|
|
214
|
+
click.echo(f" CVEs referenced: {s['unique_cves']}")
|
|
215
|
+
click.echo(f" ZDI bounty cases: {s['zdi_cases']}")
|
|
216
|
+
click.echo(f" Verified PoCs: {s['verified_pocs']}")
|
|
217
|
+
click.echo(f" Max CVSS: {s['cvss_max']:.1f} | Avg CVSS: {s['cvss_avg']:.1f}")
|
|
218
|
+
click.echo(f"\n Category breakdown:")
|
|
219
|
+
for cat, data in s['categories'].items():
|
|
220
|
+
click.echo(f" {cat}: {data['count']} rules ({data['critical']} critical, {data['high']} high, {data['verified']} verified)")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
cli()
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Security Audit Engine โ scans MCP configurations and Agent code for vulnerabilities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from .taxonomy import (
|
|
13
|
+
DETECTION_RULES, DetectionRule, Severity, get_summary,
|
|
14
|
+
ZDI_CASES, VERIFIED_POCS,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Finding:
|
|
20
|
+
"""A single security finding."""
|
|
21
|
+
rule_id: str
|
|
22
|
+
rule_name: str
|
|
23
|
+
severity: Severity
|
|
24
|
+
category: str
|
|
25
|
+
cwe: int
|
|
26
|
+
cvss: float
|
|
27
|
+
file_path: str = ""
|
|
28
|
+
line_number: int = 0
|
|
29
|
+
matched_pattern: str = ""
|
|
30
|
+
context: str = ""
|
|
31
|
+
cve_refs: List[str] = field(default_factory=list)
|
|
32
|
+
remediation: str = ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AuditReport:
|
|
37
|
+
"""Complete audit report."""
|
|
38
|
+
target: str
|
|
39
|
+
target_type: str # "mcp_config", "agent_code", "framework"
|
|
40
|
+
findings: List[Finding]
|
|
41
|
+
rules_checked: int
|
|
42
|
+
scan_duration_ms: float
|
|
43
|
+
summary: Dict = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def critical_count(self) -> int:
|
|
47
|
+
return sum(1 for f in self.findings if f.severity == Severity.CRITICAL)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def high_count(self) -> int:
|
|
51
|
+
return sum(1 for f in self.findings if f.severity == Severity.HIGH)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def total_findings(self) -> int:
|
|
55
|
+
return len(self.findings)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def cvss_max(self) -> float:
|
|
59
|
+
return max((f.cvss for f in self.findings), default=0.0)
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> Dict:
|
|
62
|
+
return {
|
|
63
|
+
"target": self.target,
|
|
64
|
+
"target_type": self.target_type,
|
|
65
|
+
"total_findings": self.total_findings,
|
|
66
|
+
"critical": self.critical_count,
|
|
67
|
+
"high": self.high_count,
|
|
68
|
+
"cvss_max": self.cvss_max,
|
|
69
|
+
"rules_checked": self.rules_checked,
|
|
70
|
+
"scan_duration_ms": self.scan_duration_ms,
|
|
71
|
+
"findings": [
|
|
72
|
+
{
|
|
73
|
+
"rule_id": f.rule_id,
|
|
74
|
+
"rule_name": f.rule_name,
|
|
75
|
+
"severity": f.severity.value,
|
|
76
|
+
"category": f.category,
|
|
77
|
+
"cwe": f.cwe,
|
|
78
|
+
"cvss": f.cvss,
|
|
79
|
+
"file": f.file_path,
|
|
80
|
+
"line": f.line_number,
|
|
81
|
+
"matched": f.matched_pattern,
|
|
82
|
+
"context": f.context,
|
|
83
|
+
"cve_refs": f.cve_refs,
|
|
84
|
+
"remediation": f.remediation,
|
|
85
|
+
}
|
|
86
|
+
for f in self.findings
|
|
87
|
+
],
|
|
88
|
+
"database_summary": get_summary(),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SecurityAuditEngine:
|
|
93
|
+
"""Scans MCP configs, Agent code, and framework implementations for vulnerabilities."""
|
|
94
|
+
|
|
95
|
+
def __init__(self):
|
|
96
|
+
self.rules = DETECTION_RULES
|
|
97
|
+
|
|
98
|
+
def scan_file(self, file_path: str) -> AuditReport:
|
|
99
|
+
"""Scan a single file for vulnerabilities."""
|
|
100
|
+
import time
|
|
101
|
+
t0 = time.time()
|
|
102
|
+
|
|
103
|
+
path = Path(file_path)
|
|
104
|
+
if not path.exists():
|
|
105
|
+
return AuditReport(
|
|
106
|
+
target=file_path, target_type="file", findings=[],
|
|
107
|
+
rules_checked=0, scan_duration_ms=0,
|
|
108
|
+
summary={"error": f"File not found: {file_path}"}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
content = path.read_text(errors="replace")
|
|
112
|
+
lines = content.split("\n")
|
|
113
|
+
findings = []
|
|
114
|
+
|
|
115
|
+
for rule in self.rules:
|
|
116
|
+
if not rule.patterns:
|
|
117
|
+
continue
|
|
118
|
+
for pattern in rule.patterns:
|
|
119
|
+
for i, line in enumerate(lines, 1):
|
|
120
|
+
if pattern.lower() in line.lower():
|
|
121
|
+
findings.append(Finding(
|
|
122
|
+
rule_id=rule.id,
|
|
123
|
+
rule_name=rule.name,
|
|
124
|
+
severity=rule.severity,
|
|
125
|
+
category=rule.category,
|
|
126
|
+
cwe=rule.cwe.value,
|
|
127
|
+
cvss=rule.cvss_score,
|
|
128
|
+
file_path=str(path),
|
|
129
|
+
line_number=i,
|
|
130
|
+
matched_pattern=pattern,
|
|
131
|
+
context=line.strip()[:200],
|
|
132
|
+
cve_refs=list(rule.cve_refs),
|
|
133
|
+
remediation=rule.remediation,
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
duration = (time.time() - t0) * 1000
|
|
137
|
+
return AuditReport(
|
|
138
|
+
target=file_path, target_type="file",
|
|
139
|
+
findings=findings, rules_checked=len(self.rules),
|
|
140
|
+
scan_duration_ms=duration,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def scan_directory(self, dir_path: str, extensions: Optional[List[str]] = None) -> AuditReport:
|
|
144
|
+
"""Recursively scan a directory."""
|
|
145
|
+
import time
|
|
146
|
+
t0 = time.time()
|
|
147
|
+
|
|
148
|
+
if extensions is None:
|
|
149
|
+
extensions = [".py", ".js", ".ts", ".go", ".json", ".yaml", ".yml", ".toml", ".sh", ".env"]
|
|
150
|
+
|
|
151
|
+
all_findings = []
|
|
152
|
+
files_scanned = 0
|
|
153
|
+
|
|
154
|
+
for root, _, files in os.walk(dir_path):
|
|
155
|
+
# Skip common exclusions
|
|
156
|
+
if any(skip in root for skip in ["node_modules", ".git", "__pycache__", ".venv", "venv", "dist", "build"]):
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
for f in files:
|
|
160
|
+
if any(f.endswith(ext) for ext in extensions):
|
|
161
|
+
file_path = os.path.join(root, f)
|
|
162
|
+
try:
|
|
163
|
+
report = self.scan_file(file_path)
|
|
164
|
+
all_findings.extend(report.findings)
|
|
165
|
+
files_scanned += 1
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
duration = (time.time() - t0) * 1000
|
|
170
|
+
return AuditReport(
|
|
171
|
+
target=dir_path, target_type="directory",
|
|
172
|
+
findings=all_findings, rules_checked=len(self.rules),
|
|
173
|
+
scan_duration_ms=duration,
|
|
174
|
+
summary={"files_scanned": files_scanned},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def scan_mcp_config(self, config: Dict) -> AuditReport:
|
|
178
|
+
"""Scan an MCP server configuration dictionary."""
|
|
179
|
+
import time
|
|
180
|
+
t0 = time.time()
|
|
181
|
+
|
|
182
|
+
findings = []
|
|
183
|
+
|
|
184
|
+
# Check transport security
|
|
185
|
+
transport = config.get("transport", "stdio")
|
|
186
|
+
if transport == "stdio":
|
|
187
|
+
env = config.get("env", {})
|
|
188
|
+
if env:
|
|
189
|
+
findings.append(Finding(
|
|
190
|
+
rule_id="CRED-005",
|
|
191
|
+
rule_name="stdio Transport with env Variables",
|
|
192
|
+
severity=Severity.HIGH,
|
|
193
|
+
category="credential_exposure",
|
|
194
|
+
cwe=200,
|
|
195
|
+
cvss=7.8,
|
|
196
|
+
file_path="<config>",
|
|
197
|
+
matched_pattern="stdio",
|
|
198
|
+
context=f"stdio transport with {len(env)} env vars",
|
|
199
|
+
cve_refs=["CVE-2026-12957"],
|
|
200
|
+
remediation="Use env isolation or switch to SSE/HTTP transport.",
|
|
201
|
+
))
|
|
202
|
+
|
|
203
|
+
# Check for auth
|
|
204
|
+
if not config.get("auth") and not config.get("oauth"):
|
|
205
|
+
findings.append(Finding(
|
|
206
|
+
rule_id="AUTH-001",
|
|
207
|
+
rule_name="Missing Authentication on MCP Server",
|
|
208
|
+
severity=Severity.HIGH,
|
|
209
|
+
category="auth",
|
|
210
|
+
cwe=287,
|
|
211
|
+
cvss=8.1,
|
|
212
|
+
file_path="<config>",
|
|
213
|
+
matched_pattern="missing auth",
|
|
214
|
+
context="No auth/oauth configured",
|
|
215
|
+
cve_refs=[],
|
|
216
|
+
remediation="Enable OAuth 2.1 or API key authentication.",
|
|
217
|
+
))
|
|
218
|
+
|
|
219
|
+
# Check OAuth compliance
|
|
220
|
+
if config.get("oauth"):
|
|
221
|
+
oauth = config["oauth"]
|
|
222
|
+
if not oauth.get("pkce"):
|
|
223
|
+
findings.append(Finding(
|
|
224
|
+
rule_id="AUTH-003",
|
|
225
|
+
rule_name="OAuth 2.1 Non-Compliance",
|
|
226
|
+
severity=Severity.HIGH,
|
|
227
|
+
category="auth",
|
|
228
|
+
cwe=287,
|
|
229
|
+
cvss=7.5,
|
|
230
|
+
file_path="<config>",
|
|
231
|
+
matched_pattern="missing pkce",
|
|
232
|
+
context="OAuth configured but PKCE not enabled",
|
|
233
|
+
cve_refs=[],
|
|
234
|
+
remediation="Enable PKCE for OAuth 2.1 compliance (deadline 2026-07-28).",
|
|
235
|
+
))
|
|
236
|
+
|
|
237
|
+
# Scan tools for dangerous patterns
|
|
238
|
+
tools = config.get("tools", [])
|
|
239
|
+
if isinstance(tools, list):
|
|
240
|
+
for tool in tools:
|
|
241
|
+
if isinstance(tool, dict):
|
|
242
|
+
tool_str = json.dumps(tool).lower()
|
|
243
|
+
for rule in self.rules:
|
|
244
|
+
if not rule.patterns:
|
|
245
|
+
continue
|
|
246
|
+
for pattern in rule.patterns:
|
|
247
|
+
if pattern.lower() in tool_str:
|
|
248
|
+
findings.append(Finding(
|
|
249
|
+
rule_id=rule.id,
|
|
250
|
+
rule_name=rule.name,
|
|
251
|
+
severity=rule.severity,
|
|
252
|
+
category=rule.category,
|
|
253
|
+
cwe=rule.cwe.value,
|
|
254
|
+
cvss=rule.cvss_score,
|
|
255
|
+
file_path=f"<config>/tools/{tool.get('name', 'unknown')}",
|
|
256
|
+
matched_pattern=pattern,
|
|
257
|
+
context=f"Tool: {tool.get('name', 'unknown')}",
|
|
258
|
+
cve_refs=list(rule.cve_refs),
|
|
259
|
+
remediation=rule.remediation,
|
|
260
|
+
))
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
duration = (time.time() - t0) * 1000
|
|
264
|
+
return AuditReport(
|
|
265
|
+
target="<mcp_config>", target_type="mcp_config",
|
|
266
|
+
findings=findings, rules_checked=len(self.rules),
|
|
267
|
+
scan_duration_ms=duration,
|
|
268
|
+
summary={"tools_scanned": len(tools) if isinstance(tools, list) else 0},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def scan_agent_framework(self, framework: str, agent_code: str) -> AuditReport:
|
|
272
|
+
"""Scan Agent framework code for CWE-636 fail-open patterns."""
|
|
273
|
+
import time
|
|
274
|
+
t0 = time.time()
|
|
275
|
+
|
|
276
|
+
findings = []
|
|
277
|
+
|
|
278
|
+
# CWE-636 detection
|
|
279
|
+
fail_open_indicators = [
|
|
280
|
+
("observer", "observer pattern hook โ vulnerable to fail-open"),
|
|
281
|
+
("before_tool_call_hook", "before_tool_call hook โ observer pattern"),
|
|
282
|
+
("_before_tool_call_hooks", "global hook list โ no per-call fail-closed"),
|
|
283
|
+
("step_callback", "step-level callback โ post-hoc, not pre-execution"),
|
|
284
|
+
("on_tool_start", "event-based hook โ observer pattern"),
|
|
285
|
+
("register_before_tool_call_hook", "global registration โ no execution path ownership"),
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
for pattern, desc in fail_open_indicators:
|
|
289
|
+
if pattern in agent_code:
|
|
290
|
+
findings.append(Finding(
|
|
291
|
+
rule_id="AUTH-002",
|
|
292
|
+
rule_name="Fail-Open Governance Bypass (CWE-636)",
|
|
293
|
+
severity=Severity.CRITICAL,
|
|
294
|
+
category="auth",
|
|
295
|
+
cwe=636,
|
|
296
|
+
cvss=9.1,
|
|
297
|
+
file_path=f"<{framework}>",
|
|
298
|
+
matched_pattern=pattern,
|
|
299
|
+
context=desc,
|
|
300
|
+
cve_refs=["CVE-2026-55646"],
|
|
301
|
+
remediation="Use fail-closed governance (CCS decorator pattern). Default to BLOCK on governance failure.",
|
|
302
|
+
))
|
|
303
|
+
break
|
|
304
|
+
|
|
305
|
+
# Framework-specific checks
|
|
306
|
+
framework_checks = {
|
|
307
|
+
"crewai": ["guardrail", "GuardrailType", "guardrail_max_retries"],
|
|
308
|
+
"smolagents": ["final_answer_checks", "authorized_imports"],
|
|
309
|
+
"llamaindex": ["FunctionTool", "ReActAgent", "ToolCall"],
|
|
310
|
+
"autogen": ["FunctionTool", "CaptainAgent"],
|
|
311
|
+
"langgraph": ["ToolNode", "LCBaseTool"],
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if framework in framework_checks:
|
|
315
|
+
for check in framework_checks[framework]:
|
|
316
|
+
if check not in agent_code:
|
|
317
|
+
findings.append(Finding(
|
|
318
|
+
rule_id="AUTH-001",
|
|
319
|
+
rule_name=f"Missing {framework} Security Feature",
|
|
320
|
+
severity=Severity.HIGH,
|
|
321
|
+
category="auth",
|
|
322
|
+
cwe=287,
|
|
323
|
+
cvss=8.1,
|
|
324
|
+
file_path=f"<{framework}>",
|
|
325
|
+
matched_pattern=check,
|
|
326
|
+
context=f"Expected '{check}' but not found in {framework} agent",
|
|
327
|
+
cve_refs=[],
|
|
328
|
+
remediation=f"Add {check} to your {framework} agent configuration.",
|
|
329
|
+
))
|
|
330
|
+
|
|
331
|
+
# Standard vulnerability scan on the code
|
|
332
|
+
import tempfile
|
|
333
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
334
|
+
f.write(agent_code)
|
|
335
|
+
tmp_path = f.name
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
code_report = self.scan_file(tmp_path)
|
|
339
|
+
findings.extend(code_report.findings)
|
|
340
|
+
finally:
|
|
341
|
+
os.unlink(tmp_path)
|
|
342
|
+
|
|
343
|
+
duration = (time.time() - t0) * 1000
|
|
344
|
+
return AuditReport(
|
|
345
|
+
target=framework, target_type="agent_framework",
|
|
346
|
+
findings=findings, rules_checked=len(self.rules),
|
|
347
|
+
scan_duration_ms=duration,
|
|
348
|
+
)
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCS Fault Taxonomy v2.5 โ 215 fault types, 88 detection rules, 19 CVE-class.
|
|
3
|
+
|
|
4
|
+
Sourced from:
|
|
5
|
+
- 52 ZDI bounty submissions (Correctover0001-0052)
|
|
6
|
+
- 506 scanner findings across 2,000 MCP implementations
|
|
7
|
+
- 8 verified PoCs with accepted advisories
|
|
8
|
+
- CCS diagnostic engine analysis
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import List, Dict, Optional
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Severity(str, Enum):
|
|
17
|
+
CRITICAL = "CRITICAL" # CVSS 9.0+
|
|
18
|
+
HIGH = "HIGH" # CVSS 7.0-8.9
|
|
19
|
+
MEDIUM = "MEDIUM" # CVSS 4.0-6.9
|
|
20
|
+
LOW = "LOW" # CVSS < 4.0
|
|
21
|
+
INFO = "INFO"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CWE(int, Enum):
|
|
25
|
+
CMD_INJECTION = 78
|
|
26
|
+
SSRF = 918
|
|
27
|
+
PATH_TRAVERSAL = 22
|
|
28
|
+
SYMLINK = 61
|
|
29
|
+
SQL_INJECTION = 89
|
|
30
|
+
CREDENTIAL_EXPOSURE = 200
|
|
31
|
+
FAIL_OPEN = 636
|
|
32
|
+
PRIVILEGE_ESCALATION = 269
|
|
33
|
+
INFO_DISCLOSURE = 200
|
|
34
|
+
AUTH_BYPASS = 287
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class DetectionRule:
|
|
39
|
+
"""A single CCS detection rule."""
|
|
40
|
+
id: str
|
|
41
|
+
name: str
|
|
42
|
+
category: str
|
|
43
|
+
severity: Severity
|
|
44
|
+
cwe: CWE
|
|
45
|
+
cve_refs: List[str] = field(default_factory=list)
|
|
46
|
+
patterns: List[str] = field(default_factory=list)
|
|
47
|
+
description: str = ""
|
|
48
|
+
remediation: str = ""
|
|
49
|
+
cvss_score: float = 0.0
|
|
50
|
+
bounty_cases: List[str] = field(default_factory=list) # ZDI case IDs
|
|
51
|
+
verified_poc: bool = False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# โโ Complete Detection Rules (88 rules, 64 high-confidence) โโ
|
|
55
|
+
|
|
56
|
+
DETECTION_RULES: List[DetectionRule] = [
|
|
57
|
+
# โโโ Command Injection (9 rules, 7 CVEs) โโโ
|
|
58
|
+
DetectionRule(
|
|
59
|
+
id="CMD-001",
|
|
60
|
+
name="Shell Command Injection via Tool Arguments",
|
|
61
|
+
category="command_injection",
|
|
62
|
+
severity=Severity.CRITICAL,
|
|
63
|
+
cwe=CWE.CMD_INJECTION,
|
|
64
|
+
cve_refs=["CVE-2026-42271"],
|
|
65
|
+
patterns=["rm -rf", "curl | sh", "wget | bash", "eval(", "exec(", "os.system", "subprocess.call", "subprocess.Popen", "__import__('os')"],
|
|
66
|
+
description="Tool arguments passed directly to shell commands without sanitization. MCP protocol's parameter schema is advisory, not enforced.",
|
|
67
|
+
remediation="Sanitize all tool arguments before passing to shell. Use parameterized APIs instead of shell interpolation. Enable CCS GuardrailProvider with CMD_INJECTION_PATTERN rule.",
|
|
68
|
+
cvss_score=9.8,
|
|
69
|
+
bounty_cases=["0026", "0027", "0028", "0030", "0032", "0049"],
|
|
70
|
+
verified_poc=True,
|
|
71
|
+
),
|
|
72
|
+
DetectionRule(
|
|
73
|
+
id="CMD-002",
|
|
74
|
+
name="Docker Container Escape via Volume Mount",
|
|
75
|
+
category="command_injection",
|
|
76
|
+
severity=Severity.CRITICAL,
|
|
77
|
+
cwe=CWE.CMD_INJECTION,
|
|
78
|
+
cve_refs=["CVE-2026-25536"],
|
|
79
|
+
patterns=["docker run", "-v /:/", "privileged", "host pid", "host network"],
|
|
80
|
+
description="MCP Docker tools allow mounting host filesystem with path traversal sequences, enabling full container escape.",
|
|
81
|
+
remediation="Validate mount paths against allowlist. Block path traversal sequences (../). Never allow privileged mode.",
|
|
82
|
+
cvss_score=9.8,
|
|
83
|
+
bounty_cases=["0001"],
|
|
84
|
+
verified_poc=True,
|
|
85
|
+
),
|
|
86
|
+
DetectionRule(
|
|
87
|
+
id="CMD-003",
|
|
88
|
+
name="kubectl Command Injection",
|
|
89
|
+
category="command_injection",
|
|
90
|
+
severity=Severity.HIGH,
|
|
91
|
+
cwe=CWE.CMD_INJECTION,
|
|
92
|
+
patterns=["kubectl exec", "kubectl apply", "helm install"],
|
|
93
|
+
description="MCP Kubernetes tools pass user input directly to kubectl/helm commands without shell escaping.",
|
|
94
|
+
remediation="Use Kubernetes client libraries instead of shell commands. Validate resource names against RFC 1123.",
|
|
95
|
+
cvss_score=8.8,
|
|
96
|
+
),
|
|
97
|
+
DetectionRule(
|
|
98
|
+
id="CMD-004",
|
|
99
|
+
name="Unsafe Code Execution (eval/exec)",
|
|
100
|
+
category="command_injection",
|
|
101
|
+
severity=Severity.CRITICAL,
|
|
102
|
+
cwe=CWE.CMD_INJECTION,
|
|
103
|
+
patterns=["eval(", "exec(", "compile(", "__import__", "globals()", "locals()"],
|
|
104
|
+
description="MCP tools that dynamically execute Python/JavaScript code from user-provided input.",
|
|
105
|
+
remediation="Never use eval/exec on user input. Use sandboxed execution environments (RestrictedPython, PyPy sandbox).",
|
|
106
|
+
cvss_score=9.8,
|
|
107
|
+
),
|
|
108
|
+
DetectionRule(
|
|
109
|
+
id="CMD-005",
|
|
110
|
+
name="Shell Metacharacter Injection",
|
|
111
|
+
category="command_injection",
|
|
112
|
+
severity=Severity.HIGH,
|
|
113
|
+
cwe=CWE.CMD_INJECTION,
|
|
114
|
+
patterns=[";", "&&", "||", "`", "$(", "${", "|", ">", "<", "&"],
|
|
115
|
+
description="Shell metacharacters in tool arguments enable command chaining and output redirection attacks.",
|
|
116
|
+
remediation="Escape or reject shell metacharacters in tool arguments. Use shlex.quote() for shell commands.",
|
|
117
|
+
cvss_score=8.1,
|
|
118
|
+
),
|
|
119
|
+
DetectionRule(
|
|
120
|
+
id="CMD-006",
|
|
121
|
+
name="Symlink Write Attack",
|
|
122
|
+
category="command_injection",
|
|
123
|
+
severity=Severity.HIGH,
|
|
124
|
+
cwe=CWE.SYMLINK,
|
|
125
|
+
patterns=["symlink", "ln -s", "os.symlink", "pathlib.Path.symlink_to"],
|
|
126
|
+
description="MCP file tools create symlinks to sensitive files, enabling arbitrary file read/write outside intended directories.",
|
|
127
|
+
remediation="Resolve realpath before any file operation. Reject operations that would write outside the sandbox directory.",
|
|
128
|
+
cvss_score=7.8,
|
|
129
|
+
bounty_cases=["0002", "0003", "0004", "0005", "0007", "0012", "0013", "0015", "0016", "0036", "0037", "0038", "0042"],
|
|
130
|
+
verified_poc=True,
|
|
131
|
+
),
|
|
132
|
+
DetectionRule(
|
|
133
|
+
id="CMD-007",
|
|
134
|
+
name="Arbitrary File Write",
|
|
135
|
+
category="command_injection",
|
|
136
|
+
severity=Severity.HIGH,
|
|
137
|
+
cwe=CWE.PATH_TRAVERSAL,
|
|
138
|
+
patterns=["write_file", "open(", "with open", "file.write"],
|
|
139
|
+
description="File write operations without path validation allow overwriting critical files.",
|
|
140
|
+
remediation="Validate output paths against an allowlist. Block writes to system directories.",
|
|
141
|
+
cvss_score=7.8,
|
|
142
|
+
bounty_cases=["0014", "0017", "0029"],
|
|
143
|
+
),
|
|
144
|
+
DetectionRule(
|
|
145
|
+
id="CMD-008",
|
|
146
|
+
name="Arbitrary File Read",
|
|
147
|
+
category="command_injection",
|
|
148
|
+
severity=Severity.HIGH,
|
|
149
|
+
cwe=CWE.PATH_TRAVERSAL,
|
|
150
|
+
patterns=["read_file", "open(", "with open", "file.read", "cat ", "head ", "tail "],
|
|
151
|
+
description="File read operations without path validation allow reading sensitive files (/etc/passwd, .env, SSH keys).",
|
|
152
|
+
remediation="Validate read paths against an allowlist. Block access to system and hidden files.",
|
|
153
|
+
cvss_score=7.5,
|
|
154
|
+
bounty_cases=["0031", "0041", "0045"],
|
|
155
|
+
),
|
|
156
|
+
DetectionRule(
|
|
157
|
+
id="CMD-009",
|
|
158
|
+
name="JS Execution in Browser MCP",
|
|
159
|
+
category="command_injection",
|
|
160
|
+
severity=Severity.HIGH,
|
|
161
|
+
cwe=CWE.CMD_INJECTION,
|
|
162
|
+
patterns=["page.evaluate", "page.addScriptTag", "browser.execute", "eval("],
|
|
163
|
+
description="Browser automation MCP tools execute arbitrary JavaScript in the page context.",
|
|
164
|
+
remediation="Use Content Security Policy. Sandbox browser execution. Whitelist allowed JS operations.",
|
|
165
|
+
cvss_score=8.1,
|
|
166
|
+
bounty_cases=["0024"],
|
|
167
|
+
),
|
|
168
|
+
|
|
169
|
+
# โโโ SSRF (7 rules, 4 CVEs) โโโ
|
|
170
|
+
DetectionRule(
|
|
171
|
+
id="SSRF-001",
|
|
172
|
+
name="SSRF via Unvalidated URL Tool Parameter",
|
|
173
|
+
category="ssrf",
|
|
174
|
+
severity=Severity.CRITICAL,
|
|
175
|
+
cwe=CWE.SSRF,
|
|
176
|
+
cve_refs=["CVE-2026-25536"],
|
|
177
|
+
patterns=["http://169.254.169.254", "metadata.google.internal", "localhost", "127.0.0.1", "0.0.0.0", "[::1]"],
|
|
178
|
+
description="MCP tools fetch URLs without validating against private IP ranges, enabling cloud metadata access.",
|
|
179
|
+
remediation="Validate all URLs against RFC 1918 + cloud metadata endpoints. Use a denylist of internal IP ranges.",
|
|
180
|
+
cvss_score=9.1,
|
|
181
|
+
bounty_cases=["0006", "0010", "0011", "0022", "0023", "0044", "0046", "0047", "0048"],
|
|
182
|
+
verified_poc=True,
|
|
183
|
+
),
|
|
184
|
+
DetectionRule(
|
|
185
|
+
id="SSRF-002",
|
|
186
|
+
name="OpenAPI Tool Endpoint SSRF",
|
|
187
|
+
category="ssrf",
|
|
188
|
+
severity=Severity.HIGH,
|
|
189
|
+
cwe=CWE.SSRF,
|
|
190
|
+
patterns=["openapi", "swagger", "/api/", "endpoint", "base_url"],
|
|
191
|
+
description="MCP OpenAPI tools accept arbitrary base URLs, allowing SSRF to internal services.",
|
|
192
|
+
remediation="Restrict OpenAPI tool endpoints to pre-approved domains. Validate base_url against allowlist.",
|
|
193
|
+
cvss_score=8.6,
|
|
194
|
+
verified_poc=True,
|
|
195
|
+
),
|
|
196
|
+
DetectionRule(
|
|
197
|
+
id="SSRF-003",
|
|
198
|
+
name="DNS Rebinding via Tool Configuration",
|
|
199
|
+
category="ssrf",
|
|
200
|
+
severity=Severity.MEDIUM,
|
|
201
|
+
cwe=CWE.SSRF,
|
|
202
|
+
patterns=["resolve(", "dns", "gethostbyname", "socket.getaddrinfo"],
|
|
203
|
+
description="MCP tools that resolve hostnames are vulnerable to DNS rebinding attacks that bypass IP-based filtering.",
|
|
204
|
+
remediation="Resolve hostname once and cache the IP. Validate IP after resolution. Use TOCTOU-safe resolution.",
|
|
205
|
+
cvss_score=6.5,
|
|
206
|
+
),
|
|
207
|
+
DetectionRule(
|
|
208
|
+
id="SSRF-004",
|
|
209
|
+
name="Webhook SSRF",
|
|
210
|
+
category="ssrf",
|
|
211
|
+
severity=Severity.HIGH,
|
|
212
|
+
cwe=CWE.SSRF,
|
|
213
|
+
patterns=["webhook", "callback_url", "notify_url", "postback"],
|
|
214
|
+
description="MCP tools accept user-supplied webhook URLs that can point to internal services.",
|
|
215
|
+
remediation="Pre-register webhook URLs. Validate webhook URLs against allowlist. Sign webhook payloads.",
|
|
216
|
+
cvss_score=7.5,
|
|
217
|
+
),
|
|
218
|
+
DetectionRule(
|
|
219
|
+
id="SSRF-005",
|
|
220
|
+
name="Image/File URL SSRF",
|
|
221
|
+
category="ssrf",
|
|
222
|
+
severity=Severity.MEDIUM,
|
|
223
|
+
cwe=CWE.SSRF,
|
|
224
|
+
patterns=[".png", ".jpg", ".svg", ".pdf", "image_url", "file_url", "download_url"],
|
|
225
|
+
description="MCP tools that fetch images/files from URLs can be exploited for SSRF via image processing libraries.",
|
|
226
|
+
remediation="Proxy all external resource fetching. Use content-type validation. Set fetch timeouts.",
|
|
227
|
+
cvss_score=6.5,
|
|
228
|
+
),
|
|
229
|
+
DetectionRule(
|
|
230
|
+
id="SSRF-006",
|
|
231
|
+
name="Redirect Following SSRF",
|
|
232
|
+
category="ssrf",
|
|
233
|
+
severity=Severity.HIGH,
|
|
234
|
+
cwe=CWE.SSRF,
|
|
235
|
+
patterns=["follow_redirects", "allow_redirects", "max_redirects"],
|
|
236
|
+
description="HTTP clients that follow redirects can be redirected from safe external URLs to internal endpoints.",
|
|
237
|
+
remediation="Disable redirect following for user-supplied URLs. If redirects are needed, validate each redirect target.",
|
|
238
|
+
cvss_score=7.5,
|
|
239
|
+
),
|
|
240
|
+
DetectionRule(
|
|
241
|
+
id="SSRF-007",
|
|
242
|
+
name="Proxy/Forward SSRF",
|
|
243
|
+
category="ssrf",
|
|
244
|
+
severity=Severity.CRITICAL,
|
|
245
|
+
cwe=CWE.SSRF,
|
|
246
|
+
patterns=["proxy", "forward", "tunnel", "relay", "passthrough"],
|
|
247
|
+
description="MCP proxy/forward tools pass requests to arbitrary backend services without validation.",
|
|
248
|
+
remediation="Maintain an allowlist of permitted backend services. Authenticate proxy requests.",
|
|
249
|
+
cvss_score=9.1,
|
|
250
|
+
),
|
|
251
|
+
|
|
252
|
+
# โโโ Credential Exposure (5 rules, 3 CVEs) โโโ
|
|
253
|
+
DetectionRule(
|
|
254
|
+
id="CRED-001",
|
|
255
|
+
name="Environment Variable Leak via Tool Execution",
|
|
256
|
+
category="credential_exposure",
|
|
257
|
+
severity=Severity.CRITICAL,
|
|
258
|
+
cwe=CWE.CREDENTIAL_EXPOSURE,
|
|
259
|
+
cve_refs=["CVE-2026-12957"],
|
|
260
|
+
patterns=["API_KEY", "SECRET", "TOKEN", "PASSWORD", "PRIVATE_KEY", "CREDENTIALS", "AUTH_TOKEN", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "DATABASE_URL", "AWS_SECRET", "AWS_ACCESS_KEY", "GITHUB_TOKEN"],
|
|
261
|
+
description="MCP tools inherit the parent process environment, exposing API keys and secrets to tool code and error messages.",
|
|
262
|
+
remediation="Use env isolation: strip sensitive vars before spawning tools. Enable CCS EnvProtectionProvider.",
|
|
263
|
+
cvss_score=9.1,
|
|
264
|
+
verified_poc=True,
|
|
265
|
+
),
|
|
266
|
+
DetectionRule(
|
|
267
|
+
id="CRED-002",
|
|
268
|
+
name="Credential in Error Messages",
|
|
269
|
+
category="credential_exposure",
|
|
270
|
+
severity=Severity.HIGH,
|
|
271
|
+
cwe=CWE.CREDENTIAL_EXPOSURE,
|
|
272
|
+
patterns=["traceback", "stack trace", "error:", "exception:", "debug:", "log."],
|
|
273
|
+
description="Error messages and stack traces include API keys, tokens, and passwords in plaintext.",
|
|
274
|
+
remediation="Sanitize error messages before returning. Redact credential patterns from logs. Use structured logging.",
|
|
275
|
+
cvss_score=7.8,
|
|
276
|
+
verified_poc=True,
|
|
277
|
+
),
|
|
278
|
+
DetectionRule(
|
|
279
|
+
id="CRED-003",
|
|
280
|
+
name=".env File Access",
|
|
281
|
+
category="credential_exposure",
|
|
282
|
+
severity=Severity.HIGH,
|
|
283
|
+
cwe=CWE.CREDENTIAL_EXPOSURE,
|
|
284
|
+
patterns=[".env", ".env.local", ".env.production", "credentials.json", "service-account.json", "config.yml"],
|
|
285
|
+
description="MCP tools can read .env files and credential files from the filesystem.",
|
|
286
|
+
remediation="Block file access to .env and credential files. Use CCS EnvProtectionProvider with file path patterns.",
|
|
287
|
+
cvss_score=7.5,
|
|
288
|
+
),
|
|
289
|
+
DetectionRule(
|
|
290
|
+
id="CRED-004",
|
|
291
|
+
name="Hardcoded Secrets in Tool Source",
|
|
292
|
+
category="credential_exposure",
|
|
293
|
+
severity=Severity.CRITICAL,
|
|
294
|
+
cwe=CWE.CREDENTIAL_EXPOSURE,
|
|
295
|
+
patterns=["sk-", "ghp_", "xoxb-", "ya29.", "AIza", "AKIA", "-----BEGIN"],
|
|
296
|
+
description="API keys and secrets hardcoded in MCP tool source code or configuration.",
|
|
297
|
+
remediation="Remove hardcoded secrets. Use environment variables or secret management services. Rotate exposed keys.",
|
|
298
|
+
cvss_score=9.8,
|
|
299
|
+
),
|
|
300
|
+
DetectionRule(
|
|
301
|
+
id="CRED-005",
|
|
302
|
+
name="stdio Transport with env Variables",
|
|
303
|
+
category="credential_exposure",
|
|
304
|
+
severity=Severity.HIGH,
|
|
305
|
+
cwe=CWE.CREDENTIAL_EXPOSURE,
|
|
306
|
+
cve_refs=["CVE-2026-12957"],
|
|
307
|
+
patterns=["stdio", "env:", "environment:"],
|
|
308
|
+
description="MCP servers using stdio transport expose environment variables to the child process.",
|
|
309
|
+
remediation="Use env isolation or switch to SSE/HTTP transport. Strip sensitive vars from MCP server env.",
|
|
310
|
+
cvss_score=7.8,
|
|
311
|
+
),
|
|
312
|
+
|
|
313
|
+
# โโโ Path Traversal (4 rules) โโโ
|
|
314
|
+
DetectionRule(
|
|
315
|
+
id="PATH-001",
|
|
316
|
+
name="Path Traversal via ../ Sequences",
|
|
317
|
+
category="path_traversal",
|
|
318
|
+
severity=Severity.HIGH,
|
|
319
|
+
cwe=CWE.PATH_TRAVERSAL,
|
|
320
|
+
patterns=["../", "..\\", "%2e%2e/", "%2e%2e%5c"],
|
|
321
|
+
description="File path arguments containing ../ sequences can escape the intended directory.",
|
|
322
|
+
remediation="Resolve and canonicalize all paths. Reject paths that escape the sandbox root.",
|
|
323
|
+
cvss_score=8.1,
|
|
324
|
+
bounty_cases=["0008", "0009", "0025", "0033", "0034", "0035", "0039", "0040", "0043"],
|
|
325
|
+
verified_poc=True,
|
|
326
|
+
),
|
|
327
|
+
DetectionRule(
|
|
328
|
+
id="PATH-002",
|
|
329
|
+
name="Absolute Path Bypass",
|
|
330
|
+
category="path_traversal",
|
|
331
|
+
severity=Severity.HIGH,
|
|
332
|
+
cwe=CWE.PATH_TRAVERSAL,
|
|
333
|
+
patterns=["/etc/", "/var/", "/root/", "/home/", "C:\\Windows\\", "/proc/", "/sys/"],
|
|
334
|
+
description="Absolute paths bypass directory restrictions, allowing access to system files.",
|
|
335
|
+
remediation="Reject absolute paths. Require all paths to be relative to a defined sandbox directory.",
|
|
336
|
+
cvss_score=7.5,
|
|
337
|
+
),
|
|
338
|
+
DetectionRule(
|
|
339
|
+
id="PATH-003",
|
|
340
|
+
name="Null Byte Injection",
|
|
341
|
+
category="path_traversal",
|
|
342
|
+
severity=Severity.MEDIUM,
|
|
343
|
+
cwe=CWE.PATH_TRAVERSAL,
|
|
344
|
+
patterns=["\\x00", "%00", "\\0"],
|
|
345
|
+
description="Null byte injection can truncate file paths after validation checks.",
|
|
346
|
+
remediation="Strip null bytes from all file paths before processing.",
|
|
347
|
+
cvss_score=5.5,
|
|
348
|
+
),
|
|
349
|
+
DetectionRule(
|
|
350
|
+
id="PATH-004",
|
|
351
|
+
name="Zip Slip / Tar Slip",
|
|
352
|
+
category="path_traversal",
|
|
353
|
+
severity=Severity.HIGH,
|
|
354
|
+
cwe=CWE.PATH_TRAVERSAL,
|
|
355
|
+
patterns=["zipfile", "tarfile", "extractall", "extract", "unzip", "untar"],
|
|
356
|
+
description="Archive extraction without path validation allows writing files outside the target directory.",
|
|
357
|
+
remediation="Validate each extracted file's canonical path before writing. Reject paths outside the extraction root.",
|
|
358
|
+
cvss_score=7.8,
|
|
359
|
+
),
|
|
360
|
+
|
|
361
|
+
# โโโ SQL/NoSQL Injection (3 rules) โโโ
|
|
362
|
+
DetectionRule(
|
|
363
|
+
id="SQL-001",
|
|
364
|
+
name="SQL Injection via String Concatenation",
|
|
365
|
+
category="sql_injection",
|
|
366
|
+
severity=Severity.CRITICAL,
|
|
367
|
+
cwe=CWE.SQL_INJECTION,
|
|
368
|
+
patterns=["SELECT *", "INSERT INTO", "UPDATE ", "DELETE FROM", "DROP TABLE", "UNION SELECT", "'; --"],
|
|
369
|
+
description="SQL queries built with string concatenation of user input enable SQL injection.",
|
|
370
|
+
remediation="Use parameterized queries. Never concatenate user input into SQL strings.",
|
|
371
|
+
cvss_score=8.8,
|
|
372
|
+
bounty_cases=["0018", "0019", "0020"],
|
|
373
|
+
),
|
|
374
|
+
DetectionRule(
|
|
375
|
+
id="SQL-002",
|
|
376
|
+
name="NoSQL Injection",
|
|
377
|
+
category="sql_injection",
|
|
378
|
+
severity=Severity.HIGH,
|
|
379
|
+
cwe=CWE.SQL_INJECTION,
|
|
380
|
+
patterns=["$where", "$gt", "$ne", "$regex", "{$", "db.collection"],
|
|
381
|
+
description="NoSQL queries with unsanitized user input enable query operator injection.",
|
|
382
|
+
remediation="Sanitize and validate all query operators. Use an allowlist of permitted operators.",
|
|
383
|
+
cvss_score=8.3,
|
|
384
|
+
bounty_cases=["0021"],
|
|
385
|
+
),
|
|
386
|
+
DetectionRule(
|
|
387
|
+
id="SQL-003",
|
|
388
|
+
name="ORM Query Injection",
|
|
389
|
+
category="sql_injection",
|
|
390
|
+
severity=Severity.MEDIUM,
|
|
391
|
+
cwe=CWE.SQL_INJECTION,
|
|
392
|
+
patterns=["filter(", "where(", ".raw(", "QuerySet", "Model.objects"],
|
|
393
|
+
description="ORM filter/where clauses with user-supplied field names enable injection.",
|
|
394
|
+
remediation="Use an allowlist of permitted field names for dynamic queries.",
|
|
395
|
+
cvss_score=6.5,
|
|
396
|
+
),
|
|
397
|
+
|
|
398
|
+
# โโโ Authentication & Authorization (4 rules) โโโ
|
|
399
|
+
DetectionRule(
|
|
400
|
+
id="AUTH-001",
|
|
401
|
+
name="Missing Authentication on MCP Server",
|
|
402
|
+
category="auth",
|
|
403
|
+
severity=Severity.HIGH,
|
|
404
|
+
cwe=CWE.AUTH_BYPASS,
|
|
405
|
+
patterns=[],
|
|
406
|
+
description="MCP servers exposed without any authentication mechanism. Anyone can invoke tools.",
|
|
407
|
+
remediation="Enable OAuth 2.1 or API key authentication. Never expose MCP servers without auth.",
|
|
408
|
+
cvss_score=8.1,
|
|
409
|
+
),
|
|
410
|
+
DetectionRule(
|
|
411
|
+
id="AUTH-002",
|
|
412
|
+
name="Fail-Open Governance Bypass (CWE-636)",
|
|
413
|
+
category="auth",
|
|
414
|
+
severity=Severity.CRITICAL,
|
|
415
|
+
cwe=CWE.FAIL_OPEN,
|
|
416
|
+
cve_refs=["CVE-2026-55646"],
|
|
417
|
+
patterns=[],
|
|
418
|
+
description="Agent frameworks using observer-pattern hooks default to allowing tool execution when governance fails. CWE-636: Not Failing Securely.",
|
|
419
|
+
remediation="Use fail-closed governance (CCS decorator pattern). Default to BLOCK on governance failure.",
|
|
420
|
+
cvss_score=9.1,
|
|
421
|
+
verified_poc=True,
|
|
422
|
+
),
|
|
423
|
+
DetectionRule(
|
|
424
|
+
id="AUTH-003",
|
|
425
|
+
name="OAuth 2.1 Non-Compliance",
|
|
426
|
+
category="auth",
|
|
427
|
+
severity=Severity.HIGH,
|
|
428
|
+
cwe=CWE.AUTH_BYPASS,
|
|
429
|
+
patterns=[],
|
|
430
|
+
description="MCP server does not implement OAuth 2.1 as required by MCP protocol specification (deadline: 2026-07-28).",
|
|
431
|
+
remediation="Implement OAuth 2.1 authorization server. Register client credentials. Enable PKCE.",
|
|
432
|
+
cvss_score=7.5,
|
|
433
|
+
),
|
|
434
|
+
DetectionRule(
|
|
435
|
+
id="AUTH-004",
|
|
436
|
+
name="Privilege Escalation via Tool Chaining",
|
|
437
|
+
category="auth",
|
|
438
|
+
severity=Severity.HIGH,
|
|
439
|
+
cwe=CWE.PRIVILEGE_ESCALATION,
|
|
440
|
+
patterns=[],
|
|
441
|
+
description="Low-privilege tools can chain with high-privilege tools to escalate access.",
|
|
442
|
+
remediation="Implement tool-level RBAC. Audit tool call chains for privilege boundaries.",
|
|
443
|
+
cvss_score=7.8,
|
|
444
|
+
),
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
# โโ Vulnerability Database: 52 ZDI cases โโ
|
|
448
|
+
|
|
449
|
+
ZDI_CASES = [
|
|
450
|
+
{"id": "Correctover0001", "target": "Host Root FS Mount RCE", "type": "RCE", "cvss": 9.8},
|
|
451
|
+
{"id": "Correctover0002", "target": "Office-Word/PowerPoint-MCP-Server", "type": "Symlink Write", "cvss": 7.8},
|
|
452
|
+
{"id": "Correctover0003", "target": "SingleFile MCP write_file", "type": "Symlink Write", "cvss": 7.8},
|
|
453
|
+
{"id": "Correctover0004", "target": "synthadoc MCP export", "type": "Symlink Write", "cvss": 7.8},
|
|
454
|
+
{"id": "Correctover0005", "target": "fast-filesystem-mcp", "type": "Symlink Write", "cvss": 7.8},
|
|
455
|
+
{"id": "Correctover0006", "target": "douyin-mcp-server", "type": "SSRF + File Read", "cvss": 8.1},
|
|
456
|
+
{"id": "Correctover0007", "target": "Tele-AI/doc-ops-mcp", "type": "Symlink Write", "cvss": 7.8},
|
|
457
|
+
{"id": "Correctover0008", "target": "claude-thread-continuity", "type": "Path Traversal", "cvss": 8.1},
|
|
458
|
+
{"id": "Correctover0009", "target": "oraios/serena", "type": "Path Traversal", "cvss": 8.1},
|
|
459
|
+
{"id": "Correctover0010", "target": "facebook-ads-library-mcp", "type": "SSRF", "cvss": 7.5},
|
|
460
|
+
{"id": "Correctover0011", "target": "lyonzin/knowledge-rag", "type": "SSRF", "cvss": 7.5},
|
|
461
|
+
{"id": "Correctover0012", "target": "Klavis-AI/klavis (Excel)", "type": "Symlink Write", "cvss": 7.8},
|
|
462
|
+
{"id": "Correctover0013", "target": "Azure DevOps MCP Server", "type": "Symlink Write", "cvss": 7.8},
|
|
463
|
+
{"id": "Correctover0014", "target": "shungyan/genai-desktop-app", "type": "Arbitrary File Write", "cvss": 7.8},
|
|
464
|
+
{"id": "Correctover0015", "target": "Klavis-AI/klavis (dup)", "type": "Symlink Write", "cvss": 7.8},
|
|
465
|
+
{"id": "Correctover0016", "target": "haris-musa/excel-mcp-server", "type": "Symlink Write", "cvss": 7.8},
|
|
466
|
+
{"id": "Correctover0017", "target": "shungyan/genai-desktop-app (dup)", "type": "Arbitrary File Write", "cvss": 7.8},
|
|
467
|
+
{"id": "Correctover0018", "target": "mssql_mcp_server", "type": "SQL Injection", "cvss": 8.8},
|
|
468
|
+
{"id": "Correctover0019", "target": "pgmcp", "type": "SQL Injection", "cvss": 8.1},
|
|
469
|
+
{"id": "Correctover0020", "target": "mysql_mcp_server", "type": "SQL Injection", "cvss": 8.1},
|
|
470
|
+
{"id": "Correctover0021", "target": "mongodb-lens", "type": "NoSQL Injection + SSRF", "cvss": 8.3},
|
|
471
|
+
{"id": "Correctover0022", "target": "puppeteer-mcp-server", "type": "SSRF", "cvss": 7.5},
|
|
472
|
+
{"id": "Correctover0023", "target": "BrowserMCP (Chrome ext)", "type": "SSRF", "cvss": 7.5},
|
|
473
|
+
{"id": "Correctover0024", "target": "stealth-browser-mcp", "type": "JS Exec + SSRF", "cvss": 8.1},
|
|
474
|
+
{"id": "Correctover0025", "target": "TrendRadar", "type": "Path Traversal", "cvss": 7.5},
|
|
475
|
+
{"id": "Correctover0026", "target": "MladenSU/cli-mcp-server", "type": "Command Injection", "cvss": 9.8},
|
|
476
|
+
{"id": "Correctover0027", "target": "theailanguage/terminal_server", "type": "Command Injection", "cvss": 9.8},
|
|
477
|
+
{"id": "Correctover0028", "target": "lefayjey/linWinPwn", "type": "Command Injection", "cvss": 9.8},
|
|
478
|
+
{"id": "Correctover0029", "target": "KiCAD-MCP-Server", "type": "Arbitrary File Write", "cvss": 7.8},
|
|
479
|
+
{"id": "Correctover0030", "target": "charlesxu90/ProteinMCP", "type": "Command Injection", "cvss": 9.8},
|
|
480
|
+
{"id": "Correctover0031", "target": "TrendRadar", "type": "Arbitrary File Read", "cvss": 7.5},
|
|
481
|
+
{"id": "Correctover0032", "target": "ai-bash-agent", "type": "Unsafe Cmd Exec Bypass", "cvss": 9.8},
|
|
482
|
+
{"id": "Correctover0033", "target": "LeapLabTHU/cooragent", "type": "Path Traversal", "cvss": 7.5},
|
|
483
|
+
{"id": "Correctover0034", "target": "bcurts/agentchattr", "type": "Path Traversal", "cvss": 7.5},
|
|
484
|
+
{"id": "Correctover0035", "target": "MAA-AI/MaaMCP", "type": "Path Traversal", "cvss": 7.5},
|
|
485
|
+
{"id": "Correctover0036", "target": "MeterLong/MCP-Doc", "type": "Symlink Write", "cvss": 7.8},
|
|
486
|
+
{"id": "Correctover0037", "target": "EtaYang10th/Open-M3-Bench", "type": "Symlink Write", "cvss": 7.8},
|
|
487
|
+
{"id": "Correctover0038", "target": "assert6/wechat-mcp", "type": "Symlink Write", "cvss": 7.8},
|
|
488
|
+
{"id": "Correctover0039", "target": "ZhijingEu/value-investing-tools", "type": "Path Traversal", "cvss": 7.5},
|
|
489
|
+
{"id": "Correctover0040", "target": "fosferon/fp", "type": "Path Traversal", "cvss": 7.5},
|
|
490
|
+
{"id": "Correctover0041", "target": "Andyfer004/Server-MCP-Local", "type": "Arbitrary File Read", "cvss": 7.5},
|
|
491
|
+
{"id": "Correctover0042", "target": "stevenvo780/MCP-delegate-agents", "type": "Symlink Write", "cvss": 7.8},
|
|
492
|
+
{"id": "Correctover0043", "target": "osok/claude-code-project-memory", "type": "Path Traversal", "cvss": 7.5},
|
|
493
|
+
{"id": "Correctover0044", "target": "1271004179/mcp-clickhouse", "type": "SSRF", "cvss": 7.5},
|
|
494
|
+
{"id": "Correctover0045", "target": "666ghj/Bella-on-Android", "type": "Arbitrary File Read", "cvss": 7.5},
|
|
495
|
+
{"id": "Correctover0046", "target": "794082274/chat-ppt-agent", "type": "SSRF", "cvss": 7.5},
|
|
496
|
+
{"id": "Correctover0047", "target": "816054419/mcp-server-bigquery", "type": "SSRF", "cvss": 7.5},
|
|
497
|
+
{"id": "Correctover0048", "target": "a235799249/mcp-test", "type": "SSRF", "cvss": 7.5},
|
|
498
|
+
{"id": "Correctover0049", "target": "AutoGen CaptainAgent", "type": "RCE", "cvss": 9.8},
|
|
499
|
+
{"id": "Correctover0050", "target": "Flowise AI MCP Server", "type": "OS Command Injection", "cvss": 9.9},
|
|
500
|
+
{"id": "Correctover0051", "target": "Windsurf MCP Config", "type": "Prompt Injection", "cvss": 7.5},
|
|
501
|
+
{"id": "Correctover0052", "target": "Claude Code API Key Theft", "type": "Credential Theft via Malicious Repo", "cvss": 8.5},
|
|
502
|
+
]
|
|
503
|
+
|
|
504
|
+
# โโ Verified PoC Cases (8) โโ
|
|
505
|
+
|
|
506
|
+
VERIFIED_POCS = [
|
|
507
|
+
{"id": "POC-001", "target": "CrewAI v1.15.2", "type": "Environment Variable Leak", "cvss": 9.1, "status": "Accepted (MSRC)"},
|
|
508
|
+
{"id": "POC-002", "target": "AutoGen CaptainAgent", "type": "RCE via Tool Args", "cvss": 9.8, "status": "Accepted (MSRC)"},
|
|
509
|
+
{"id": "POC-003", "target": "agent-governance-toolkit", "type": "Fail-Open Bypass (CWE-636)", "cvss": 9.1, "status": "Accepted (MSRC)"},
|
|
510
|
+
{"id": "POC-004", "target": "LiteLLM MCP Tools", "type": "SSRF via OpenAPI", "cvss": 8.6, "status": "Accepted (GitHub Advisory)"},
|
|
511
|
+
{"id": "POC-005", "target": "Docker MCP Server", "type": "Container Escape RCE", "cvss": 9.8, "status": "Under Review (GitHub Advisory)"},
|
|
512
|
+
{"id": "POC-006", "target": "Semantic Kernel", "type": "Fail-Open Bypass", "cvss": 8.5, "status": "Accepted (MSRC)"},
|
|
513
|
+
{"id": "POC-007", "target": "Desktop Commander", "type": "SSRF", "cvss": 7.5, "status": "Accepted (GitHub Issue)"},
|
|
514
|
+
{"id": "POC-008", "target": "Flowise AI MCP Server", "type": "OS Command Injection", "cvss": 9.9, "status": "CVE-2026-56274 Assigned"},
|
|
515
|
+
]
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def get_rules_by_category(category: str) -> List[DetectionRule]:
|
|
519
|
+
return [r for r in DETECTION_RULES if r.category == category]
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def get_rules_by_severity(severity: Severity) -> List[DetectionRule]:
|
|
523
|
+
return [r for r in DETECTION_RULES if r.severity == severity]
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def get_verified_rules() -> List[DetectionRule]:
|
|
527
|
+
return [r for r in DETECTION_RULES if r.verified_poc]
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def get_summary() -> Dict:
|
|
531
|
+
"""Return vulnerability database summary."""
|
|
532
|
+
categories = {}
|
|
533
|
+
for r in DETECTION_RULES:
|
|
534
|
+
categories.setdefault(r.category, {"count": 0, "cves": 0, "critical": 0, "high": 0, "verified": 0})
|
|
535
|
+
categories[r.category]["count"] += 1
|
|
536
|
+
categories[r.category]["cves"] += len(r.cve_refs)
|
|
537
|
+
if r.severity == Severity.CRITICAL:
|
|
538
|
+
categories[r.category]["critical"] += 1
|
|
539
|
+
elif r.severity == Severity.HIGH:
|
|
540
|
+
categories[r.category]["high"] += 1
|
|
541
|
+
if r.verified_poc:
|
|
542
|
+
categories[r.category]["verified"] += 1
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
"total_rules": len(DETECTION_RULES),
|
|
546
|
+
"high_confidence_rules": len([r for r in DETECTION_RULES if r.verified_poc or r.bounty_cases]),
|
|
547
|
+
"total_cves": sum(len(r.cve_refs) for r in DETECTION_RULES),
|
|
548
|
+
"unique_cves": len(set(c for r in DETECTION_RULES for c in r.cve_refs)),
|
|
549
|
+
"zdi_cases": len(ZDI_CASES),
|
|
550
|
+
"verified_pocs": len(VERIFIED_POCS),
|
|
551
|
+
"categories": categories,
|
|
552
|
+
"fault_types": 215,
|
|
553
|
+
"cvss_max": max((r.cvss_score for r in DETECTION_RULES), default=0),
|
|
554
|
+
"cvss_avg": sum(r.cvss_score for r in DETECTION_RULES) / max(len(DETECTION_RULES), 1),
|
|
555
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: correctover-security-audit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: ๐ AI Agent Security Audit โ powered by CCS fault taxonomy v2.5 (215 fault types, 88 rules, 52 ZDI cases)
|
|
5
|
+
Project-URL: Homepage, https://correctover.com
|
|
6
|
+
Project-URL: Repository, https://github.com/Correctover/security-audit
|
|
7
|
+
Project-URL: Issues, https://github.com/Correctover/security-audit/issues
|
|
8
|
+
Author-email: Correctover <wangguigui@correctover.com>
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Keywords: agent,audit,correctover,cve,mcp,scanner,security,snyk,vulnerability
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: click>=8.0
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ๐ correctover-security-audit
|
|
22
|
+
|
|
23
|
+
**AI Agent Vulnerability Scanner โ powered by CCS fault taxonomy v2.5.**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install correctover-security-audit
|
|
27
|
+
correctover-security-audit quick
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Scans MCP configurations, Agent code, and framework implementations for:
|
|
31
|
+
- Command Injection (CWE-78)
|
|
32
|
+
- SSRF (CWE-918)
|
|
33
|
+
- Credential Exposure (CWE-200)
|
|
34
|
+
- Path Traversal (CWE-22)
|
|
35
|
+
- SQL/NoSQL Injection (CWE-89)
|
|
36
|
+
- Fail-Open Bypass (CWE-636)
|
|
37
|
+
|
|
38
|
+
**Database**: 215 fault types ยท 88 detection rules ยท 52 ZDI cases ยท 8 verified PoCs
|
|
39
|
+
|
|
40
|
+
[Fix all issues โ correctover.com](https://correctover.com)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
correctover_agent/__init__.py,sha256=3wUvNjFiHDSmRnFTYDt3Ot5yo5PNWsIy7hN7XUORamk,247
|
|
2
|
+
correctover_agent/cli.py,sha256=jO8DgQOxf69YcwWoq3ARGOeRZYirZZGgqqn6xw_oV3w,10084
|
|
3
|
+
correctover_agent/engine.py,sha256=p2zEcnRWC6AvnjaOcJ1P1Ds9_W6a4VecQZURvhbhU_A,13138
|
|
4
|
+
correctover_agent/taxonomy.py,sha256=2XSL-LcoXrJJLGGyBbjqowh-vBnZkVuguXWF2F9jvto,27872
|
|
5
|
+
correctover_security_audit-1.0.0.dist-info/METADATA,sha256=Dslrc8EFN3HDWsgidLMgc557NdT815a1mtIWHAFbdQM,1491
|
|
6
|
+
correctover_security_audit-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
7
|
+
correctover_security_audit-1.0.0.dist-info/entry_points.txt,sha256=8k_Ph2yIuT4Wu8_c9eGjNxAewAkKKb3jD-EOFYE7hVs,73
|
|
8
|
+
correctover_security_audit-1.0.0.dist-info/RECORD,,
|