correctover-runtime-guard 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- correctover_runtime_guard-1.0.0/.gitignore +3 -0
- correctover_runtime_guard-1.0.0/PKG-INFO +21 -0
- correctover_runtime_guard-1.0.0/README.md +18 -0
- correctover_runtime_guard-1.0.0/correctover_agent/__init__.py +8 -0
- correctover_runtime_guard-1.0.0/correctover_agent/cli.py +139 -0
- correctover_runtime_guard-1.0.0/correctover_agent/guard.py +190 -0
- correctover_runtime_guard-1.0.0/correctover_agent/mcp_server.py +103 -0
- correctover_runtime_guard-1.0.0/pyproject.toml +38 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: correctover-runtime-guard
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Runtime RCE/SSRF/Env leak interception โ 22ยตs P50 via correctover GuardrailProvider
|
|
5
|
+
Project-URL: Homepage, https://correctover.com
|
|
6
|
+
Project-URL: Repository, https://github.com/Correctover/runtime-guard
|
|
7
|
+
Project-URL: Issues, https://github.com/Correctover/runtime-guard/issues
|
|
8
|
+
Author-email: Correctover <wangguigui@correctover.com>
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Keywords: correctover,env-leak,guard,mcp,rce,runtime,security,ssrf
|
|
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
|
+
Requires-Dist: fastmcp>=2.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ๐ก๏ธ correctover-runtime-guard
|
|
2
|
+
|
|
3
|
+
**Real-time RCE/SSRF/Env Leak interception โ 22ยตs P50.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install correctover-runtime-guard
|
|
7
|
+
correctover-runtime-guard start
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Runtime guardrail for AI Agent tool calls:
|
|
11
|
+
- Intercept RCE attempts (CWE-78)
|
|
12
|
+
- Block SSRF attacks (CWE-918)
|
|
13
|
+
- Prevent environment variable leaks (CWE-200)
|
|
14
|
+
- MCP Server mode: diagnose_error / get_fault_pattern / get_repair_suggestion
|
|
15
|
+
|
|
16
|
+
**Pricing**: $1,999/month Enterprise
|
|
17
|
+
|
|
18
|
+
[Deploy now โ correctover.com](https://correctover.com)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
correctover-runtime-guard CLI โ Runtime RCE/SSRF/Env leak interception.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
correctover-runtime-guard start [--transport stdio|sse|streamable-http]
|
|
6
|
+
correctover-runtime-guard diagnose <error_message>
|
|
7
|
+
correctover-runtime-guard stats
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from .guard import RuntimeGuard, FAULT_PATTERNS
|
|
17
|
+
|
|
18
|
+
VERSION = "1.0.0"
|
|
19
|
+
CTA_URL = "https://correctover.com"
|
|
20
|
+
PRICING = "$1,999/month (Enterprise)"
|
|
21
|
+
LATENCY_P50 = "22ยตs"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
@click.version_option(version=VERSION, prog_name="correctover-runtime-guard")
|
|
26
|
+
def cli():
|
|
27
|
+
"""Correctover Runtime Guard โ real-time RCE/SSRF/Env leak interception.
|
|
28
|
+
|
|
29
|
+
Powered by correctover GuardrailProvider runtime hooks.
|
|
30
|
+
22ยตs P50 detection latency. 8 fault patterns across 4 categories.
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@cli.command()
|
|
36
|
+
@click.option("--transport", type=click.Choice(["stdio", "sse", "streamable-http"]), default="stdio")
|
|
37
|
+
@click.option("--host", default="0.0.0.0", help="Host for SSE/HTTP transport")
|
|
38
|
+
@click.option("--port", default=8080, help="Port for SSE/HTTP transport")
|
|
39
|
+
def start(transport: str, host: str, port: int):
|
|
40
|
+
"""Start the Runtime Guard MCP server."""
|
|
41
|
+
GREEN = "\033[92m"
|
|
42
|
+
CYAN = "\033[96m"
|
|
43
|
+
BOLD = "\033[1m"
|
|
44
|
+
DIM = "\033[2m"
|
|
45
|
+
RESET = "\033[0m"
|
|
46
|
+
|
|
47
|
+
click.echo(f"\n{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
48
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {BOLD}Correctover Runtime Guard{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
49
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
50
|
+
click.echo(f" Transport: {BOLD}{transport}{RESET}")
|
|
51
|
+
click.echo(f" Detection: {GREEN}{LATENCY_P50} P50{RESET}")
|
|
52
|
+
click.echo(f" Patterns: {len(FAULT_PATTERNS)} ({', '.join(set(p.category for p in FAULT_PATTERNS))})")
|
|
53
|
+
click.echo(f" Pricing: {PRICING}")
|
|
54
|
+
click.echo()
|
|
55
|
+
|
|
56
|
+
from .mcp_server import serve
|
|
57
|
+
serve(transport=transport, host=host, port=port)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@cli.command()
|
|
61
|
+
@click.argument("error_message")
|
|
62
|
+
def diagnose(error_message: str):
|
|
63
|
+
"""Diagnose an error message for fault patterns."""
|
|
64
|
+
RED = "\033[91m"
|
|
65
|
+
YELLOW = "\033[93m"
|
|
66
|
+
GREEN = "\033[92m"
|
|
67
|
+
CYAN = "\033[96m"
|
|
68
|
+
BOLD = "\033[1m"
|
|
69
|
+
DIM = "\033[2m"
|
|
70
|
+
RESET = "\033[0m"
|
|
71
|
+
|
|
72
|
+
guard = RuntimeGuard()
|
|
73
|
+
events = guard.diagnose_error(error_message)
|
|
74
|
+
|
|
75
|
+
click.echo(f"\n{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
76
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {BOLD}Error Diagnosis Report{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
77
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
78
|
+
|
|
79
|
+
click.echo(f" Input: {DIM}{error_message[:120]}{'...' if len(error_message) > 120 else ''}{RESET}")
|
|
80
|
+
click.echo()
|
|
81
|
+
|
|
82
|
+
if not events:
|
|
83
|
+
click.echo(f" {GREEN}No fault patterns detected โ input appears safe.{RESET}")
|
|
84
|
+
else:
|
|
85
|
+
click.echo(f" {RED}{BOLD}{len(events)} pattern(s) detected{RESET}")
|
|
86
|
+
click.echo()
|
|
87
|
+
for e in events:
|
|
88
|
+
sev_color = RED if e.pattern.severity == "CRITICAL" else YELLOW
|
|
89
|
+
click.echo(f" {sev_color}[{e.pattern.severity}]{RESET} {DIM}[{e.pattern.pattern_id}]{RESET} {BOLD}{e.pattern.category}{RESET}")
|
|
90
|
+
click.echo(f" {DIM}{e.pattern.description}{RESET}")
|
|
91
|
+
click.echo(f" {GREEN}Fix: {e.pattern.repair}{RESET}")
|
|
92
|
+
click.echo()
|
|
93
|
+
|
|
94
|
+
# CTA
|
|
95
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
96
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} ๐ก๏ธ Deploy Guard โ {CTA_URL} {CYAN}{BOLD}โ{RESET}")
|
|
97
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {DIM}{LATENCY_P50} P50 ยท {PRICING}{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
98
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@cli.command()
|
|
102
|
+
def stats():
|
|
103
|
+
"""Show guard statistics and registered patterns."""
|
|
104
|
+
GREEN = "\033[92m"
|
|
105
|
+
RED = "\033[91m"
|
|
106
|
+
CYAN = "\033[96m"
|
|
107
|
+
BOLD = "\033[1m"
|
|
108
|
+
DIM = "\033[2m"
|
|
109
|
+
RESET = "\033[0m"
|
|
110
|
+
|
|
111
|
+
click.echo(f"\n{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
112
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {BOLD}Runtime Guard Statistics{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
113
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
114
|
+
|
|
115
|
+
click.echo(f" Detection latency: {GREEN}{LATENCY_P50} P50{RESET}")
|
|
116
|
+
click.echo(f" Fault patterns: {len(FAULT_PATTERNS)} total")
|
|
117
|
+
click.echo(f" Pricing: {PRICING}")
|
|
118
|
+
click.echo()
|
|
119
|
+
|
|
120
|
+
from collections import Counter
|
|
121
|
+
cats = Counter(p.category for p in FAULT_PATTERNS)
|
|
122
|
+
click.echo(f" {BOLD}Category breakdown:{RESET}")
|
|
123
|
+
for cat, count in sorted(cats.items()):
|
|
124
|
+
click.echo(f" {cat}: {count} patterns")
|
|
125
|
+
|
|
126
|
+
click.echo(f"\n {BOLD}Registered patterns:{RESET}")
|
|
127
|
+
for p in FAULT_PATTERNS:
|
|
128
|
+
sev_color = RED if p.severity == "CRITICAL" else "\033[93m"
|
|
129
|
+
click.echo(f" {sev_color}[{p.severity}]{RESET} {DIM}[{p.pattern_id}]{RESET} {p.description}")
|
|
130
|
+
click.echo(f" {GREEN}โ {p.repair}{RESET}")
|
|
131
|
+
|
|
132
|
+
click.echo(f"\n{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}")
|
|
133
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} ๐ก๏ธ Deploy Guard โ {CTA_URL} {CYAN}{BOLD}โ{RESET}")
|
|
134
|
+
click.echo(f"{CYAN}{BOLD}โ{RESET} {DIM}{LATENCY_P50} P50 ยท {PRICING}{RESET} {CYAN}{BOLD}โ{RESET}")
|
|
135
|
+
click.echo(f"{CYAN}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
cli()
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Runtime Guard Engine โ RCE/SSRF/Env leak interception via correctover SDK hooks.
|
|
3
|
+
22ยตs P50 detection latency.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Callable, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class FaultPattern:
|
|
15
|
+
pattern_id: str
|
|
16
|
+
category: str # RCE | SSRF | ENV_LEAK | INJECTION
|
|
17
|
+
regex: str
|
|
18
|
+
severity: str # CRITICAL | HIGH | MEDIUM
|
|
19
|
+
description: str
|
|
20
|
+
repair: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class DetectionEvent:
|
|
25
|
+
timestamp: float
|
|
26
|
+
pattern: FaultPattern
|
|
27
|
+
input_data: str
|
|
28
|
+
source: str
|
|
29
|
+
latency_us: float
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class GuardStats:
|
|
34
|
+
total_checks: int = 0
|
|
35
|
+
blocked: int = 0
|
|
36
|
+
allowed: int = 0
|
|
37
|
+
avg_latency_us: float = 0.0
|
|
38
|
+
p50_latency_us: float = 0.0
|
|
39
|
+
p99_latency_us: float = 0.0
|
|
40
|
+
latencies: list = field(default_factory=list)
|
|
41
|
+
recent_events: list = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
def record(self, latency_us: float, blocked: bool):
|
|
44
|
+
self.total_checks += 1
|
|
45
|
+
if blocked:
|
|
46
|
+
self.blocked += 1
|
|
47
|
+
else:
|
|
48
|
+
self.allowed += 1
|
|
49
|
+
self.latencies.append(latency_us)
|
|
50
|
+
if len(self.latencies) > 10000:
|
|
51
|
+
self.latencies = self.latencies[-5000:]
|
|
52
|
+
sorted_lats = sorted(self.latencies)
|
|
53
|
+
n = len(sorted_lats)
|
|
54
|
+
self.avg_latency_us = sum(sorted_lats) / n if n else 0
|
|
55
|
+
self.p50_latency_us = sorted_lats[n // 2] if n else 0
|
|
56
|
+
self.p99_latency_us = sorted_lats[int(n * 0.99)] if n else 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
FAULT_PATTERNS: list[FaultPattern] = [
|
|
60
|
+
FaultPattern(
|
|
61
|
+
"RCE-001", "RCE",
|
|
62
|
+
r"(?:os\.system|subprocess\.(?:call|run|Popen)|exec\(|eval\(|__import__\(|compile\()",
|
|
63
|
+
"CRITICAL",
|
|
64
|
+
"Shell command execution detected",
|
|
65
|
+
"Use sandboxed execution or allowlist commands via CCS GuardrailProvider",
|
|
66
|
+
),
|
|
67
|
+
FaultPattern(
|
|
68
|
+
"RCE-002", "RCE",
|
|
69
|
+
r"(?:rm\s+-rf|mkfs\.|dd\s+if=|:\(\)\s*\{)",
|
|
70
|
+
"CRITICAL",
|
|
71
|
+
"Destructive shell command detected (fork bomb / disk wipe)",
|
|
72
|
+
"Block destructive commands; route through CCS sandbox executor",
|
|
73
|
+
),
|
|
74
|
+
FaultPattern(
|
|
75
|
+
"SSRF-001", "SSRF",
|
|
76
|
+
r"(?:requests\.(?:get|post|put|delete)|urllib\.request|httpx\.(?:get|post)|fetch\()",
|
|
77
|
+
"HIGH",
|
|
78
|
+
"Outbound HTTP request detected โ potential SSRF",
|
|
79
|
+
"Validate URL against allowlist; block internal IP ranges (10.x, 169.254.x, ::1)",
|
|
80
|
+
),
|
|
81
|
+
FaultPattern(
|
|
82
|
+
"SSRF-002", "SSRF",
|
|
83
|
+
r"(?:http://(?:localhost|127\.0\.0\.1|10\.\d+|172\.(?:1[6-9]|2\d|3[01])|192\.168\.|0\.0\.0\.0|\[::1\]|169\.254\.))",
|
|
84
|
+
"CRITICAL",
|
|
85
|
+
"Request to internal/private IP detected",
|
|
86
|
+
"Block all requests to private IP ranges at the GuardrailProvider layer",
|
|
87
|
+
),
|
|
88
|
+
FaultPattern(
|
|
89
|
+
"ENV-001", "ENV_LEAK",
|
|
90
|
+
r"(?:os\.environ|os\.getenv|getenv\(|\.env\[|process\.env)",
|
|
91
|
+
"HIGH",
|
|
92
|
+
"Environment variable access detected โ potential secret leak",
|
|
93
|
+
"Use CCS env scoping; only expose allowlisted variables",
|
|
94
|
+
),
|
|
95
|
+
FaultPattern(
|
|
96
|
+
"ENV-002", "ENV_LEAK",
|
|
97
|
+
r"(?:API_KEY|SECRET|TOKEN|PASSWORD|DATABASE_URL|AWS_ACCESS|GITHUB_TOKEN)",
|
|
98
|
+
"CRITICAL",
|
|
99
|
+
"Sensitive credential pattern in output/response",
|
|
100
|
+
"Redact credentials; use CCS secret manager instead of env vars",
|
|
101
|
+
),
|
|
102
|
+
FaultPattern(
|
|
103
|
+
"INJ-001", "INJECTION",
|
|
104
|
+
r"(?:(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s+.+(?:FROM|INTO|TABLE|DATABASE))",
|
|
105
|
+
"HIGH",
|
|
106
|
+
"Potential SQL injection via raw query",
|
|
107
|
+
"Use parameterized queries; route through CCS query sanitizer",
|
|
108
|
+
),
|
|
109
|
+
FaultPattern(
|
|
110
|
+
"INJ-002", "INJECTION",
|
|
111
|
+
r"(?:<script|javascript:|onerror=|onload=|<iframe)",
|
|
112
|
+
"HIGH",
|
|
113
|
+
"Potential XSS payload detected",
|
|
114
|
+
"Sanitize output with CCS HTML sanitizer; use CSP headers",
|
|
115
|
+
),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class RuntimeGuard:
|
|
120
|
+
"""Runtime guard engine using correctover GuardrailProvider hooks."""
|
|
121
|
+
|
|
122
|
+
def __init__(self):
|
|
123
|
+
self.patterns = FAULT_PATTERNS
|
|
124
|
+
self.stats = GuardStats()
|
|
125
|
+
self._compiled: dict[str, re.Pattern] = {}
|
|
126
|
+
self._block_callback: Optional[Callable] = None
|
|
127
|
+
|
|
128
|
+
def _get_regex(self, pattern: str) -> re.Pattern:
|
|
129
|
+
if pattern not in self._compiled:
|
|
130
|
+
self._compiled[pattern] = re.compile(pattern, re.IGNORECASE)
|
|
131
|
+
return self._compiled[pattern]
|
|
132
|
+
|
|
133
|
+
def scan(self, data: str, source: str = "unknown") -> list[DetectionEvent]:
|
|
134
|
+
"""Scan input data against all fault patterns. Returns detected events."""
|
|
135
|
+
events: list[DetectionEvent] = []
|
|
136
|
+
t0 = time.perf_counter()
|
|
137
|
+
|
|
138
|
+
for p in self.patterns:
|
|
139
|
+
regex = self._get_regex(p.regex)
|
|
140
|
+
if regex.search(data):
|
|
141
|
+
t1 = time.perf_counter()
|
|
142
|
+
event = DetectionEvent(
|
|
143
|
+
timestamp=time.time(),
|
|
144
|
+
pattern=p,
|
|
145
|
+
input_data=data[:200],
|
|
146
|
+
source=source,
|
|
147
|
+
latency_us=(t1 - t0) * 1_000_000,
|
|
148
|
+
)
|
|
149
|
+
events.append(event)
|
|
150
|
+
self.stats.record(event.latency_us, blocked=True)
|
|
151
|
+
if len(self.stats.recent_events) >= 100:
|
|
152
|
+
self.stats.recent_events.pop(0)
|
|
153
|
+
self.stats.recent_events.append(event)
|
|
154
|
+
|
|
155
|
+
if not events:
|
|
156
|
+
t1 = time.perf_counter()
|
|
157
|
+
self.stats.record((t1 - t0) * 1_000_000, blocked=False)
|
|
158
|
+
|
|
159
|
+
return events
|
|
160
|
+
|
|
161
|
+
def is_safe(self, data: str, source: str = "unknown") -> tuple[bool, list[DetectionEvent]]:
|
|
162
|
+
"""Check if data is safe. Returns (is_safe, events)."""
|
|
163
|
+
events = self.scan(data, source)
|
|
164
|
+
return len(events) == 0, events
|
|
165
|
+
|
|
166
|
+
def diagnose_error(self, error_message: str) -> list[DetectionEvent]:
|
|
167
|
+
"""Diagnose an error message for known fault patterns."""
|
|
168
|
+
return self.scan(error_message, source="error_diagnosis")
|
|
169
|
+
|
|
170
|
+
def get_fault_pattern(self, category: Optional[str] = None) -> list[FaultPattern]:
|
|
171
|
+
"""Get registered fault patterns, optionally filtered by category."""
|
|
172
|
+
if category:
|
|
173
|
+
return [p for p in self.patterns if p.category == category.upper()]
|
|
174
|
+
return list(self.patterns)
|
|
175
|
+
|
|
176
|
+
def get_repair_suggestion(self, pattern_id: str) -> Optional[dict]:
|
|
177
|
+
"""Get repair suggestion for a specific fault pattern."""
|
|
178
|
+
for p in self.patterns:
|
|
179
|
+
if p.pattern_id == pattern_id:
|
|
180
|
+
return {
|
|
181
|
+
"pattern_id": p.pattern_id,
|
|
182
|
+
"category": p.category,
|
|
183
|
+
"severity": p.severity,
|
|
184
|
+
"description": p.description,
|
|
185
|
+
"repair": p.repair,
|
|
186
|
+
}
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def get_stats(self) -> GuardStats:
|
|
190
|
+
return self.stats
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Server โ expose Runtime Guard as MCP tools.
|
|
3
|
+
Tools: diagnose_error, get_fault_pattern, get_repair_suggestion
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from fastmcp import FastMCP
|
|
10
|
+
HAS_FASTMCP = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_FASTMCP = False
|
|
13
|
+
|
|
14
|
+
from .guard import RuntimeGuard
|
|
15
|
+
|
|
16
|
+
guard = RuntimeGuard()
|
|
17
|
+
|
|
18
|
+
if HAS_FASTMCP:
|
|
19
|
+
mcp = FastMCP("correctover-runtime-guard")
|
|
20
|
+
|
|
21
|
+
@mcp.tool()
|
|
22
|
+
def diagnose_error(error_message: str) -> dict:
|
|
23
|
+
"""Diagnose an error message for known fault patterns (RCE/SSRF/Env leak/Injection).
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
error_message: The error message or stack trace to analyze.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
dict with 'events' list of detected patterns, each containing
|
|
30
|
+
pattern_id, category, severity, description, and repair suggestion.
|
|
31
|
+
"""
|
|
32
|
+
events = guard.diagnose_error(error_message)
|
|
33
|
+
return {
|
|
34
|
+
"events": [
|
|
35
|
+
{
|
|
36
|
+
"pattern_id": e.pattern.pattern_id,
|
|
37
|
+
"category": e.pattern.category,
|
|
38
|
+
"severity": e.pattern.severity,
|
|
39
|
+
"description": e.pattern.description,
|
|
40
|
+
"repair": e.pattern.repair,
|
|
41
|
+
"source": e.source,
|
|
42
|
+
"latency_us": e.latency_us,
|
|
43
|
+
}
|
|
44
|
+
for e in events
|
|
45
|
+
],
|
|
46
|
+
"total": len(events),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@mcp.tool()
|
|
50
|
+
def get_fault_pattern(category: Optional[str] = None) -> dict:
|
|
51
|
+
"""Get registered fault patterns, optionally filtered by category.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
category: Optional filter โ RCE, SSRF, ENV_LEAK, or INJECTION.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
dict with 'patterns' list of fault pattern definitions.
|
|
58
|
+
"""
|
|
59
|
+
patterns = guard.get_fault_pattern(category)
|
|
60
|
+
return {
|
|
61
|
+
"patterns": [
|
|
62
|
+
{
|
|
63
|
+
"pattern_id": p.pattern_id,
|
|
64
|
+
"category": p.category,
|
|
65
|
+
"severity": p.severity,
|
|
66
|
+
"description": p.description,
|
|
67
|
+
"repair": p.repair,
|
|
68
|
+
}
|
|
69
|
+
for p in patterns
|
|
70
|
+
],
|
|
71
|
+
"total": len(patterns),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@mcp.tool()
|
|
75
|
+
def get_repair_suggestion(pattern_id: str) -> dict:
|
|
76
|
+
"""Get repair suggestion for a specific fault pattern.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
pattern_id: The fault pattern ID (e.g. RCE-001, SSRF-001, ENV-001).
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
dict with pattern details and repair guidance, or error if not found.
|
|
83
|
+
"""
|
|
84
|
+
suggestion = guard.get_repair_suggestion(pattern_id)
|
|
85
|
+
if suggestion:
|
|
86
|
+
return {"found": True, **suggestion}
|
|
87
|
+
return {"found": False, "error": f"Pattern '{pattern_id}' not found"}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def serve(transport: str = "stdio", host: str = "0.0.0.0", port: int = 8080):
|
|
91
|
+
"""Start the MCP server with the given transport."""
|
|
92
|
+
if not HAS_FASTMCP:
|
|
93
|
+
print("Error: fastmcp not installed. Run: pip install fastmcp")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
if transport == "stdio":
|
|
97
|
+
mcp.run(transport="stdio")
|
|
98
|
+
elif transport == "sse":
|
|
99
|
+
mcp.run(transport="sse", host=host, port=port)
|
|
100
|
+
elif transport == "streamable-http":
|
|
101
|
+
mcp.run(transport="streamable-http", host=host, port=port)
|
|
102
|
+
else:
|
|
103
|
+
print(f"Error: Unknown transport '{transport}'. Use stdio, sse, or streamable-http.")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "correctover-runtime-guard"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Runtime RCE/SSRF/Env leak interception โ 22ยตs P50 via correctover GuardrailProvider"
|
|
9
|
+
license = { text = "Apache-2.0" }
|
|
10
|
+
authors = [{ name = "Correctover", email = "wangguigui@correctover.com" }]
|
|
11
|
+
keywords = ["runtime", "guard", "rce", "ssrf", "env-leak", "mcp", "correctover", "security"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Topic :: Security",
|
|
16
|
+
"License :: OSI Approved :: Apache Software License",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.11"
|
|
21
|
+
dependencies = [
|
|
22
|
+
"click>=8.0",
|
|
23
|
+
"fastmcp>=2.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = ["pytest>=7.0"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://correctover.com"
|
|
31
|
+
Repository = "https://github.com/Correctover/runtime-guard"
|
|
32
|
+
Issues = "https://github.com/Correctover/runtime-guard/issues"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
correctover-runtime-guard = "correctover_agent.cli:cli"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["correctover_agent"]
|