correctover-runtime-guard 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.
@@ -0,0 +1,8 @@
1
+ """
2
+ correctover-runtime-guard — Runtime interception for RCE/SSRF/Env leaks.
3
+ 22µs P50 detection latency via correctover GuardrailProvider hooks.
4
+
5
+ Pricing: $1,999/month (Enterprise)
6
+ """
7
+
8
+ __version__ = "1.0.0"
@@ -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,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,8 @@
1
+ correctover_agent/__init__.py,sha256=R7Tfa79s3lzCtMvFzHfk-qRdpOI4unV7UejQftW75og,211
2
+ correctover_agent/cli.py,sha256=bbvcjfiM8RaanVsHFydlHqM-ttZ8EB-IQQMYIe3KxF4,6553
3
+ correctover_agent/guard.py,sha256=Gj6qXcqZ0UPh_JovPIWXmUYBv43sijp5XEY6DZWUIa0,6724
4
+ correctover_agent/mcp_server.py,sha256=VzSoNP9WOn_OipqHFktnYd0Xwkp-pLkM8489D8a4Y50,3350
5
+ correctover_runtime_guard-1.0.0.dist-info/METADATA,sha256=0Sz41jX8OY-1nPUhVlgLpN7mYgxQO38AlaGSwf9SnvA,920
6
+ correctover_runtime_guard-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ correctover_runtime_guard-1.0.0.dist-info/entry_points.txt,sha256=63MS1QPL1IrLdKGSvAMr6xtMZcVO6qy7JLmDkq1KiSQ,72
8
+ correctover_runtime_guard-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ correctover-runtime-guard = correctover_agent.cli:cli