correctover 2.1.0__tar.gz → 2.2.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-2.2.0/PKG-INFO +12 -0
- correctover-2.2.0/pyproject.toml +21 -0
- {correctover-2.1.0 → correctover-2.2.0}/setup.cfg +4 -4
- correctover-2.2.0/src/correctover/__init__.py +22 -0
- correctover-2.2.0/src/correctover/guardrail.py +211 -0
- correctover-2.2.0/src/correctover/validator.py +37 -0
- correctover-2.2.0/src/correctover.egg-info/PKG-INFO +12 -0
- correctover-2.2.0/src/correctover.egg-info/SOURCES.txt +9 -0
- correctover-2.2.0/src/correctover.egg-info/requires.txt +1 -0
- correctover-2.1.0/LICENSE +0 -7
- correctover-2.1.0/MANIFEST.in +0 -2
- correctover-2.1.0/PKG-INFO +0 -49
- correctover-2.1.0/README.md +0 -21
- correctover-2.1.0/correctover/__init__.py +0 -310
- correctover-2.1.0/correctover/__pycache__/__init__.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_checkup.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_engine.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_fixes.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_fixes2.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_guard.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_ping.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_security.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_types.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/_version.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/bounty_rules.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/ccs_scanner.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/chain.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/cli.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/cloudkill.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/license.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/__pycache__/mcs_scanner.cpython-312.pyc +0 -0
- correctover-2.1.0/correctover/_checkup.py +0 -335
- correctover-2.1.0/correctover/_core.py +0 -1557
- correctover-2.1.0/correctover/_device.py +0 -294
- correctover-2.1.0/correctover/_engine.py +0 -2779
- correctover-2.1.0/correctover/_fixes.py +0 -209
- correctover-2.1.0/correctover/_fixes2.py +0 -178
- correctover-2.1.0/correctover/_guard.py +0 -169
- correctover-2.1.0/correctover/_ping.py +0 -296
- correctover-2.1.0/correctover/_security.py +0 -127
- correctover-2.1.0/correctover/_sentry.py +0 -360
- correctover-2.1.0/correctover/_stats.py +0 -519
- correctover-2.1.0/correctover/_tracker.py +0 -191
- correctover-2.1.0/correctover/_types.py +0 -64
- correctover-2.1.0/correctover/_version.py +0 -5
- correctover-2.1.0/correctover/benchmark/__init__.py +0 -6
- correctover-2.1.0/correctover/benchmark/fault_injector.py +0 -129
- correctover-2.1.0/correctover/benchmark/metrics_collector.py +0 -222
- correctover-2.1.0/correctover/benchmark/run_benchmark.py +0 -211
- correctover-2.1.0/correctover/benchmark/scenarios.py +0 -123
- correctover-2.1.0/correctover/bounty_rules.py +0 -806
- correctover-2.1.0/correctover/carbon.py +0 -454
- correctover-2.1.0/correctover/ccs_scanner.py +0 -653
- correctover-2.1.0/correctover/chain.py +0 -951
- correctover-2.1.0/correctover/checkpoint.py +0 -805
- correctover-2.1.0/correctover/classifier.py +0 -212
- correctover-2.1.0/correctover/cli.py +0 -1048
- correctover-2.1.0/correctover/client.py +0 -400
- correctover-2.1.0/correctover/cloudkill.py +0 -446
- correctover-2.1.0/correctover/cost_tracker.py +0 -62
- correctover-2.1.0/correctover/dashboard.py +0 -1157
- correctover-2.1.0/correctover/demo.py +0 -466
- correctover-2.1.0/correctover/diagnoser.py +0 -34
- correctover-2.1.0/correctover/drift.py +0 -262
- correctover-2.1.0/correctover/free_provider.py +0 -186
- correctover-2.1.0/correctover/gateway.py +0 -732
- correctover-2.1.0/correctover/health_scorer.py +0 -54
- correctover-2.1.0/correctover/integrity.py +0 -56
- correctover-2.1.0/correctover/license.py +0 -861
- correctover-2.1.0/correctover/logging_config.py +0 -84
- correctover-2.1.0/correctover/mcs_scanner.py +0 -561
- correctover-2.1.0/correctover/router.py +0 -279
- correctover-2.1.0/correctover/run.py +0 -139
- correctover-2.1.0/correctover/shield.py +0 -366
- correctover-2.1.0/correctover/smart_router.py +0 -13
- correctover-2.1.0/correctover/state_machine.py +0 -84
- correctover-2.1.0/correctover/status.py +0 -514
- correctover-2.1.0/correctover/telemetry.py +0 -308
- correctover-2.1.0/correctover/tracing.py +0 -89
- correctover-2.1.0/correctover.egg-info/PKG-INFO +0 -49
- correctover-2.1.0/correctover.egg-info/SOURCES.txt +0 -77
- correctover-2.1.0/correctover.egg-info/entry_points.txt +0 -5
- correctover-2.1.0/correctover.egg-info/requires.txt +0 -2
- correctover-2.1.0/pyproject.toml +0 -43
- correctover-2.1.0/setup.py +0 -40
- {correctover-2.1.0 → correctover-2.2.0/src}/correctover.egg-info/dependency_links.txt +0 -0
- {correctover-2.1.0 → correctover-2.2.0/src}/correctover.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: correctover
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Correctover - AI Agent Runtime Assurance. CCS standard + GuardrailProvider + MCP Security Scanner.
|
|
5
|
+
Author-email: Guigui Wang <wangguigui@correctover.com>
|
|
6
|
+
License: Proprietary Commercial License
|
|
7
|
+
Project-URL: Homepage, https://correctover.com
|
|
8
|
+
Project-URL: CCS Standard, https://correctover.com/ccs
|
|
9
|
+
Project-URL: GitHub, https://github.com/Correctover
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: pydantic>=1.10
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "correctover"
|
|
7
|
+
version = "2.2.0"
|
|
8
|
+
authors = [{name="Guigui Wang", email="wangguigui@correctover.com"}]
|
|
9
|
+
description = "Correctover - AI Agent Runtime Assurance. CCS standard + GuardrailProvider + MCP Security Scanner."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = {text = "Proprietary Commercial License"}
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = ["pydantic>=1.10"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://correctover.com"
|
|
17
|
+
"CCS Standard" = "https://correctover.com/ccs"
|
|
18
|
+
GitHub = "https://github.com/Correctover"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["src"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[egg_info]
|
|
2
|
-
tag_build =
|
|
3
|
-
tag_date = 0
|
|
4
|
-
|
|
1
|
+
[egg_info]
|
|
2
|
+
tag_build =
|
|
3
|
+
tag_date = 0
|
|
4
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Correctover v2.2.0 - AI Agent Runtime Assurance"""
|
|
2
|
+
__version__ = "2.2.0"
|
|
3
|
+
__author__ = "Guigui Wang"
|
|
4
|
+
__email__ = "wangguigui@correctover.com"
|
|
5
|
+
from .validator import CCSValidator, ValidationResult
|
|
6
|
+
from .guardrail import (
|
|
7
|
+
GuardrailDecisionV1, ActionEnvelopeV1, ToolCallContext,
|
|
8
|
+
GuardrailProvider, AllowAllGuardrailProvider, DenyAllGuardrailProvider,
|
|
9
|
+
ToolListGuardrailProvider, CKGGuardrailProvider, EnvProtectionProvider,
|
|
10
|
+
CompositeGuardrailProvider, AuditTrail, GuardrailContext,
|
|
11
|
+
make_guardrail_hook, detect_missing_guardrail, compute_decision_id,
|
|
12
|
+
canonical_json, MCPSecurityValidator,
|
|
13
|
+
)
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CCSValidator","ValidationResult",
|
|
16
|
+
"GuardrailDecisionV1","ActionEnvelopeV1","ToolCallContext",
|
|
17
|
+
"GuardrailProvider","AllowAllGuardrailProvider","DenyAllGuardrailProvider",
|
|
18
|
+
"ToolListGuardrailProvider","CKGGuardrailProvider","EnvProtectionProvider",
|
|
19
|
+
"CompositeGuardrailProvider","AuditTrail","GuardrailContext",
|
|
20
|
+
"make_guardrail_hook","detect_missing_guardrail","compute_decision_id",
|
|
21
|
+
"canonical_json","MCPSecurityValidator",
|
|
22
|
+
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
correctover.guardrail - Framework-agnostic runtime security guardrail system.
|
|
3
|
+
CCS reference implementation.
|
|
4
|
+
"""
|
|
5
|
+
import hashlib, json, time
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Set
|
|
9
|
+
|
|
10
|
+
def canonical_json(obj):
|
|
11
|
+
return json.dumps(obj, sort_keys=True, separators=(",",":"), default=str)
|
|
12
|
+
|
|
13
|
+
def compute_decision_id(claims, expires_at=None):
|
|
14
|
+
preimage = claims.copy()
|
|
15
|
+
if expires_at is not None:
|
|
16
|
+
preimage["_expires_at"] = expires_at
|
|
17
|
+
return hashlib.sha256(canonical_json(preimage).encode("utf-8")).hexdigest()
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class GuardrailDecisionV1:
|
|
21
|
+
decision_id: str
|
|
22
|
+
authorized: bool
|
|
23
|
+
claims: Dict[str, Any]
|
|
24
|
+
expires_at: Optional[float] = None
|
|
25
|
+
def is_expired(self):
|
|
26
|
+
if self.expires_at is None: return False
|
|
27
|
+
return time.time() > self.expires_at
|
|
28
|
+
def verify_integrity(self):
|
|
29
|
+
return self.decision_id == compute_decision_id(self.claims, self.expires_at)
|
|
30
|
+
def to_dict(self):
|
|
31
|
+
return {"decision_id":self.decision_id,"authorized":self.authorized,"claims":self.claims,"expires_at":self.expires_at}
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ActionEnvelopeV1:
|
|
35
|
+
decision_id: str
|
|
36
|
+
tool_result_digest: str
|
|
37
|
+
executed_at: float
|
|
38
|
+
duration_ms: float
|
|
39
|
+
@staticmethod
|
|
40
|
+
def digest_result(result):
|
|
41
|
+
raw = canonical_json(result) if result is not None else ""
|
|
42
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
43
|
+
def to_dict(self):
|
|
44
|
+
return {"decision_id":self.decision_id,"tool_result_digest":self.tool_result_digest,"executed_at":self.executed_at,"duration_ms":self.duration_ms}
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ToolCallContext:
|
|
48
|
+
tool_name: str
|
|
49
|
+
tool_args: Dict[str, Any] = field(default_factory=dict)
|
|
50
|
+
agent_id: str = "unknown"
|
|
51
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
class GuardrailProvider(ABC):
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def authorize(self, context: ToolCallContext) -> GuardrailDecisionV1: ...
|
|
56
|
+
|
|
57
|
+
class AllowAllGuardrailProvider(GuardrailProvider):
|
|
58
|
+
def authorize(self, context):
|
|
59
|
+
claims = {"provider":"AllowAllGuardrailProvider","tool_name":context.tool_name,"agent_id":context.agent_id}
|
|
60
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=True, claims=claims)
|
|
61
|
+
|
|
62
|
+
class DenyAllGuardrailProvider(GuardrailProvider):
|
|
63
|
+
def authorize(self, context):
|
|
64
|
+
claims = {"provider":"DenyAllGuardrailProvider","tool_name":context.tool_name,"reason":"deny-all safety lock active"}
|
|
65
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=False, claims=claims)
|
|
66
|
+
|
|
67
|
+
class ToolListGuardrailProvider(GuardrailProvider):
|
|
68
|
+
def __init__(self, allowed_tools=None, denied_tools=None):
|
|
69
|
+
self.allowed_tools = allowed_tools
|
|
70
|
+
self.denied_tools = denied_tools or set()
|
|
71
|
+
def authorize(self, context):
|
|
72
|
+
claims = {"provider":"ToolListGuardrailProvider","tool_name":context.tool_name,"agent_id":context.agent_id}
|
|
73
|
+
if self.allowed_tools is not None:
|
|
74
|
+
authorized = context.tool_name in self.allowed_tools
|
|
75
|
+
claims["policy"] = "allowlist"
|
|
76
|
+
claims["allowed_tools"] = sorted(self.allowed_tools)
|
|
77
|
+
elif context.tool_name in self.denied_tools:
|
|
78
|
+
authorized = False
|
|
79
|
+
claims["policy"] = "denylist"
|
|
80
|
+
claims["denied_tools"] = sorted(self.denied_tools)
|
|
81
|
+
else:
|
|
82
|
+
authorized = True
|
|
83
|
+
claims["policy"] = "default-allow"
|
|
84
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=authorized, claims=claims)
|
|
85
|
+
|
|
86
|
+
class CKGGuardrailProvider(GuardrailProvider):
|
|
87
|
+
def __init__(self):
|
|
88
|
+
self._constraints = []
|
|
89
|
+
def add_constraint(self, predicate, **kwargs):
|
|
90
|
+
self._constraints.append({"predicate":predicate, **kwargs})
|
|
91
|
+
return self
|
|
92
|
+
def authorize(self, context):
|
|
93
|
+
claims = {"provider":"CKGGuardrailProvider","tool_name":context.tool_name,"agent_id":context.agent_id}
|
|
94
|
+
satisfied, failed_predicate = True, None
|
|
95
|
+
for c in self._constraints:
|
|
96
|
+
p = c["predicate"]
|
|
97
|
+
if p=="tool_name_in" and context.tool_name not in c["tools"]: satisfied,failed_predicate=False,p
|
|
98
|
+
elif p=="tool_name_not_in" and context.tool_name in c["tools"]: satisfied,failed_predicate=False,p
|
|
99
|
+
elif p=="agent_id_in" and context.agent_id not in c["agents"]: satisfied,failed_predicate=False,p
|
|
100
|
+
elif p=="param_matches" and context.tool_args.get(c["name"])!=c["value"]: satisfied,failed_predicate=False,p
|
|
101
|
+
elif p=="has_param" and c["name"] not in context.tool_args: satisfied,failed_predicate=False,p
|
|
102
|
+
elif p=="no_param" and c["name"] in context.tool_args: satisfied,failed_predicate=False,p
|
|
103
|
+
if not satisfied: claims["failed_predicate"]=failed_predicate
|
|
104
|
+
claims["constraints_count"]=len(self._constraints)
|
|
105
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=satisfied, claims=claims)
|
|
106
|
+
|
|
107
|
+
class EnvProtectionProvider(GuardrailProvider):
|
|
108
|
+
DANGEROUS_PATTERNS = {
|
|
109
|
+
"env_vars":["API_KEY","API_SECRET","SECRET_KEY","PRIVATE_KEY","ACCESS_TOKEN","AUTH_TOKEN","DB_PASSWORD","DATABASE_URL","AWS_SECRET","AWS_ACCESS_KEY","GITHUB_TOKEN","OPENAI_API_KEY","ANTHROPIC_API_KEY"],
|
|
110
|
+
"env_commands":["os.environ","process.env","getenv","env ","printenv","echo $","${"],
|
|
111
|
+
"file_paths":[".env",".env.local",".env.production","credentials.json","service-account.json"],
|
|
112
|
+
}
|
|
113
|
+
def __init__(self, extra_patterns=None):
|
|
114
|
+
self.extra_patterns = extra_patterns or []
|
|
115
|
+
def authorize(self, context):
|
|
116
|
+
claims = {"provider":"EnvProtectionProvider","tool_name":context.tool_name,"agent_id":context.agent_id}
|
|
117
|
+
args_str = canonical_json(context.tool_args).lower()
|
|
118
|
+
matched = [f"{cat}:{p}" for cat,pats in self.DANGEROUS_PATTERNS.items() for p in pats if p.lower() in args_str]
|
|
119
|
+
matched += [f"custom:{p}" for p in self.extra_patterns if p.lower() in args_str]
|
|
120
|
+
if matched:
|
|
121
|
+
claims["matched_patterns"]=matched
|
|
122
|
+
claims["reason"]="Environment variable access attempt detected"
|
|
123
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=False, claims=claims)
|
|
124
|
+
claims["reason"]="No environment variable access detected"
|
|
125
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=True, claims=claims)
|
|
126
|
+
|
|
127
|
+
class CompositeGuardrailProvider(GuardrailProvider):
|
|
128
|
+
def __init__(self, providers, mode="AND"):
|
|
129
|
+
if mode not in ("AND","OR"): raise ValueError(f"mode must be AND or OR, got {mode}")
|
|
130
|
+
self.providers = providers
|
|
131
|
+
self.mode = mode
|
|
132
|
+
def authorize(self, context):
|
|
133
|
+
results = [p.authorize(context) for p in self.providers]
|
|
134
|
+
authorized = all(d.authorized for d in results) if self.mode=="AND" else any(d.authorized for d in results)
|
|
135
|
+
claims = {"provider":"CompositeGuardrailProvider","mode":self.mode,"tool_name":context.tool_name,"agent_id":context.agent_id,"sub_decisions":[d.decision_id for d in results],"sub_providers":[d.claims.get("provider","unknown") for d in results]}
|
|
136
|
+
return GuardrailDecisionV1(decision_id=compute_decision_id(claims), authorized=authorized, claims=claims)
|
|
137
|
+
|
|
138
|
+
class AuditTrail:
|
|
139
|
+
def __init__(self):
|
|
140
|
+
self._decisions = {}
|
|
141
|
+
self._envelopes = {}
|
|
142
|
+
def record_decision(self, decision): self._decisions[decision.decision_id] = decision
|
|
143
|
+
def record_envelope(self, envelope): self._envelopes[envelope.decision_id] = envelope
|
|
144
|
+
def get_decision(self, did): return self._decisions.get(did)
|
|
145
|
+
def get_envelope(self, did): return self._envelopes.get(did)
|
|
146
|
+
def get_all_decisions(self): return list(self._decisions.values())
|
|
147
|
+
def get_all_envelopes(self): return list(self._envelopes.values())
|
|
148
|
+
def clear(self): self._decisions.clear(); self._envelopes.clear()
|
|
149
|
+
@property
|
|
150
|
+
def decision_count(self): return len(self._decisions)
|
|
151
|
+
@property
|
|
152
|
+
def envelope_count(self): return len(self._envelopes)
|
|
153
|
+
def verify_all(self): return all(d.verify_integrity() for d in self._decisions.values())
|
|
154
|
+
def export(self): return {"decisions":[d.to_dict() for d in self._decisions.values()],"envelopes":[e.to_dict() for e in self._envelopes.values()],"verified":self.verify_all()}
|
|
155
|
+
|
|
156
|
+
class GuardrailContext:
|
|
157
|
+
def __init__(self, provider, trail=None, on_deny=None):
|
|
158
|
+
self.provider = provider; self.trail = trail or AuditTrail(); self.on_deny = on_deny
|
|
159
|
+
def authorize(self, context):
|
|
160
|
+
decision = self.provider.authorize(context)
|
|
161
|
+
self.trail.record_decision(decision)
|
|
162
|
+
if not decision.authorized and self.on_deny: self.on_deny(decision)
|
|
163
|
+
return decision
|
|
164
|
+
def after_tool_call(self, decision, result, start_time):
|
|
165
|
+
duration_ms = (time.time()-start_time)*1000
|
|
166
|
+
envelope = ActionEnvelopeV1(decision_id=decision.decision_id, tool_result_digest=ActionEnvelopeV1.digest_result(result), executed_at=time.time(), duration_ms=duration_ms)
|
|
167
|
+
self.trail.record_envelope(envelope)
|
|
168
|
+
return envelope
|
|
169
|
+
|
|
170
|
+
def make_guardrail_hook(provider, trail=None, on_deny=None):
|
|
171
|
+
ctx = GuardrailContext(provider=provider, trail=trail, on_deny=on_deny)
|
|
172
|
+
def _hook(tool_name, tool_args=None, agent_id="unknown", **kwargs):
|
|
173
|
+
context = ToolCallContext(tool_name=tool_name, tool_args=tool_args or {}, agent_id=agent_id, metadata=kwargs)
|
|
174
|
+
decision = ctx.authorize(context)
|
|
175
|
+
if not decision.authorized: return False
|
|
176
|
+
return None
|
|
177
|
+
_hook._guardrail_context = ctx
|
|
178
|
+
return _hook
|
|
179
|
+
|
|
180
|
+
def detect_missing_guardrail(agents):
|
|
181
|
+
findings = []
|
|
182
|
+
for agent in agents:
|
|
183
|
+
agent_name = getattr(agent,"role",getattr(agent,"name",getattr(agent,"id","unknown")))
|
|
184
|
+
has_hooks = getattr(agent,"_before_tool_call_hooks",None) or getattr(agent,"before_tool_call",None) or getattr(agent,"_guardrails",None) or getattr(agent,"guardrail_provider",None)
|
|
185
|
+
if not has_hooks:
|
|
186
|
+
findings.append({"severity":"CRITICAL","pattern":"AS-GUARDRAIL-MISS-001","agent":agent_name,"message":f"Agent '{agent_name}' has no registered GuardrailProvider","remediation":"Register a GuardrailProvider via make_guardrail_hook()","ccs_ref":"https://correctover.com/ccs"})
|
|
187
|
+
return findings
|
|
188
|
+
|
|
189
|
+
class MCPSecurityValidator:
|
|
190
|
+
DANGEROUS_COMMANDS = ["rm -rf","curl | sh","wget | bash","eval(","exec(","os.system","subprocess.call"]
|
|
191
|
+
SENSITIVE_ENV_PATTERNS = ["API_KEY","SECRET","TOKEN","PASSWORD","PRIVATE_KEY","CREDENTIALS","AUTH"]
|
|
192
|
+
@staticmethod
|
|
193
|
+
def validate_tool_definition(tool_def):
|
|
194
|
+
issues = []
|
|
195
|
+
impl = str(tool_def.get("implementation",""))
|
|
196
|
+
for cmd in MCPSecurityValidator.DANGEROUS_COMMANDS:
|
|
197
|
+
if cmd in impl: issues.append({"severity":"CRITICAL","type":"command_injection","pattern":cmd,"cve_ref":"CVE-2026-42271"})
|
|
198
|
+
env_section = str(tool_def.get("env",{}))
|
|
199
|
+
for pattern in MCPSecurityValidator.SENSITIVE_ENV_PATTERNS:
|
|
200
|
+
if pattern.upper() in env_section.upper(): issues.append({"severity":"HIGH","type":"env_exposure","pattern":pattern,"cve_ref":"CVE-2026-12957"})
|
|
201
|
+
return {"safe":len(issues)==0,"issues":issues,"tool_name":tool_def.get("name","unknown")}
|
|
202
|
+
@staticmethod
|
|
203
|
+
def validate_mcp_config(config):
|
|
204
|
+
issues = []
|
|
205
|
+
transport = config.get("transport","stdio")
|
|
206
|
+
if transport == "stdio":
|
|
207
|
+
env = config.get("env",{})
|
|
208
|
+
if env: issues.append({"severity":"HIGH","type":"stdio_env_exposure","message":"stdio transport with env variables","cve_ref":"CVE-2026-12957","remediation":"Use env_isolation or switch to sse/http transport"})
|
|
209
|
+
for tool in config.get("tools",[]):
|
|
210
|
+
issues.extend(MCPSecurityValidator.validate_tool_definition(tool)["issues"])
|
|
211
|
+
return {"safe":len(issues)==0,"issues":issues,"config_name":config.get("name","unknown")}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Dict, List, Any, Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import hashlib, hmac, json
|
|
5
|
+
|
|
6
|
+
class ValidationResult(BaseModel):
|
|
7
|
+
is_valid: bool
|
|
8
|
+
errors: List[str] = Field(default_factory=list)
|
|
9
|
+
warnings: List[str] = Field(default_factory=list)
|
|
10
|
+
validated_at: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
|
|
11
|
+
trace_id: Optional[str] = None
|
|
12
|
+
|
|
13
|
+
class CCSValidator:
|
|
14
|
+
def __init__(self, required_fields=None, supported_fields=None, forbidden_fields=None, enable_integrity=False, integrity_key=None):
|
|
15
|
+
self.required_fields = set(required_fields or [])
|
|
16
|
+
self.supported_fields = set(supported_fields or [])
|
|
17
|
+
self.forbidden_fields = set(forbidden_fields or [])
|
|
18
|
+
self.enable_integrity = enable_integrity
|
|
19
|
+
self.integrity_key = integrity_key or "default-key"
|
|
20
|
+
def validate(self, output, trace_id=None):
|
|
21
|
+
errors, warnings = [], []
|
|
22
|
+
for f in self.required_fields:
|
|
23
|
+
if f not in output: errors.append(f"Missing required field: {f}")
|
|
24
|
+
for f in self.forbidden_fields:
|
|
25
|
+
if f in output: errors.append(f"Forbidden field present: {f}")
|
|
26
|
+
unexpected = set(output.keys()) - self.required_fields - self.supported_fields - self.forbidden_fields
|
|
27
|
+
for f in unexpected: warnings.append(f"Unexpected field: {f}")
|
|
28
|
+
if self.enable_integrity:
|
|
29
|
+
stored_hash = output.get("integrity_hash")
|
|
30
|
+
computed = self._compute_integrity(output)
|
|
31
|
+
if stored_hash is None: warnings.append("No integrity hash in output")
|
|
32
|
+
elif stored_hash != computed: errors.append("Integrity hash mismatch")
|
|
33
|
+
return ValidationResult(is_valid=len(errors)==0, errors=errors, warnings=warnings, trace_id=trace_id)
|
|
34
|
+
def _compute_integrity(self, output):
|
|
35
|
+
data = {k:v for k,v in output.items() if k != "integrity_hash"}
|
|
36
|
+
canonical = json.dumps(data, sort_keys=True, separators=(",",":"))
|
|
37
|
+
return hmac.new(self.integrity_key.encode(), canonical.encode(), hashlib.sha256).hexdigest()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: correctover
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Correctover - AI Agent Runtime Assurance. CCS standard + GuardrailProvider + MCP Security Scanner.
|
|
5
|
+
Author-email: Guigui Wang <wangguigui@correctover.com>
|
|
6
|
+
License: Proprietary Commercial License
|
|
7
|
+
Project-URL: Homepage, https://correctover.com
|
|
8
|
+
Project-URL: CCS Standard, https://correctover.com/ccs
|
|
9
|
+
Project-URL: GitHub, https://github.com/Correctover
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: pydantic>=1.10
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/correctover/__init__.py
|
|
3
|
+
src/correctover/guardrail.py
|
|
4
|
+
src/correctover/validator.py
|
|
5
|
+
src/correctover.egg-info/PKG-INFO
|
|
6
|
+
src/correctover.egg-info/SOURCES.txt
|
|
7
|
+
src/correctover.egg-info/dependency_links.txt
|
|
8
|
+
src/correctover.egg-info/requires.txt
|
|
9
|
+
src/correctover.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pydantic>=1.10
|
correctover-2.1.0/LICENSE
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
CORRECTOVER SDK — PROPRIETARY COMMERCIAL LICENSE
|
|
2
|
-
Copyright (c) 2024-2026 Correctover Team. All rights reserved.
|
|
3
|
-
|
|
4
|
-
This software is NOT open source. It is distributed as compiled
|
|
5
|
-
bytecode only. Redistribution requires a valid commercial license.
|
|
6
|
-
|
|
7
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
correctover-2.1.0/MANIFEST.in
DELETED
correctover-2.1.0/PKG-INFO
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: correctover
|
|
3
|
-
Version: 2.1.0
|
|
4
|
-
Summary: Correctover — Failure is not fatal. Protocol-level contract validation with automatic verified failover for LLM APIs.
|
|
5
|
-
Home-page: https://correctover.com
|
|
6
|
-
Author: Correctover Team
|
|
7
|
-
Author-email: Correctover Team <team@correctover.com>
|
|
8
|
-
Maintainer-email: Correctover Team <team@correctover.com>
|
|
9
|
-
License: Proprietary Commercial License — see LICENSE
|
|
10
|
-
Project-URL: Homepage, https://correctover.com
|
|
11
|
-
Project-URL: Documentation, https://correctover.com/docs
|
|
12
|
-
Project-URL: Changelog, https://correctover.com/docs/changelog
|
|
13
|
-
Project-URL: Bug Tracker, https://correctover.com/support
|
|
14
|
-
Keywords: llm,self-healing,failover,circuit-breaker,api-resilience,openai,anthropic,deepseek,contract-validation,correctover,ai-gateway,semantic-verification
|
|
15
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
-
Classifier: Intended Audience :: Developers
|
|
17
|
-
Classifier: License :: Other/Proprietary License
|
|
18
|
-
Classifier: Operating System :: OS Independent
|
|
19
|
-
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Requires-Python: >=3.12
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Requires-Dist: httpx>=0.24.0
|
|
27
|
-
Requires-Dist: aiohttp>=3.8.0
|
|
28
|
-
|
|
29
|
-
# Correctover SDK
|
|
30
|
-
|
|
31
|
-
**Failure is not fatal.** Protocol-level contract validation with automatic verified failover for LLM APIs.
|
|
32
|
-
|
|
33
|
-
## Install
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
pip install correctover
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Quick Start
|
|
40
|
-
|
|
41
|
-
```python
|
|
42
|
-
import correctover
|
|
43
|
-
result = correctover.run("Hello!")
|
|
44
|
-
print(result)
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## License
|
|
48
|
-
|
|
49
|
-
Proprietary Commercial License. See LICENSE file.
|
correctover-2.1.0/README.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Correctover SDK
|
|
2
|
-
|
|
3
|
-
**Failure is not fatal.** Protocol-level contract validation with automatic verified failover for LLM APIs.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pip install correctover
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```python
|
|
14
|
-
import correctover
|
|
15
|
-
result = correctover.run("Hello!")
|
|
16
|
-
print(result)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## License
|
|
20
|
-
|
|
21
|
-
Proprietary Commercial License. See LICENSE file.
|