correctover-test 0.3.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.
@@ -0,0 +1,31 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build-and-publish:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ id-token: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: '3.11'
21
+
22
+ - name: Install build tools
23
+ run: pip install build twine
24
+
25
+ - name: Build package
26
+ run: python -m build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
30
+ with:
31
+ password: ${{ secrets.PYPI_TOKEN }}
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ *.egg
8
+ .env
9
+ .venv/
10
+ venv/
11
+ .DS_Store
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: correctover-test
3
+ Version: 0.3.0
4
+ Summary: 🔒 Correctover Agent Security Audit — test your AI agents for guardrail gaps in 5 seconds. Supports CrewAI, Smolagents, LlamaIndex.
5
+ Project-URL: Homepage, https://correctover.com
6
+ Project-URL: Documentation, https://correctover.com/docs
7
+ Project-URL: Repository, https://github.com/Correctover/correctover-test
8
+ Project-URL: Issues, https://github.com/Correctover/correctover-test/issues
9
+ Author-email: NeuralBridge <wangguigui@correctover.com>
10
+ License: Apache-2.0
11
+ Keywords: agent,ai,audit,correctover,guardrail,llm,security,self-healing
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: click>=8.0
22
+ Requires-Dist: correctover>=2.2.0
23
+ Requires-Dist: pydantic>=2.0
24
+ Provides-Extra: all
25
+ Requires-Dist: crewai; extra == 'all'
26
+ Requires-Dist: llama-index-core; extra == 'all'
27
+ Requires-Dist: smolagents; extra == 'all'
28
+ Provides-Extra: crewai
29
+ Requires-Dist: crewai; extra == 'crewai'
30
+ Provides-Extra: llamaindex
31
+ Requires-Dist: llama-index-core; extra == 'llamaindex'
32
+ Provides-Extra: smolagents
33
+ Requires-Dist: smolagents; extra == 'smolagents'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # 🔒 correctover-test
37
+
38
+ **AI Agent Security Audit — 5 seconds to know if your agents are protected.**
39
+
40
+ ```bash
41
+ pip install correctover-test
42
+ correctover-test --quick
43
+ ```
44
+
45
+ [Fix issues → correctover.com](https://correctover.com)
@@ -0,0 +1,10 @@
1
+ # 🔒 correctover-test
2
+
3
+ **AI Agent Security Audit — 5 seconds to know if your agents are protected.**
4
+
5
+ ```bash
6
+ pip install correctover-test
7
+ correctover-test --quick
8
+ ```
9
+
10
+ [Fix issues → correctover.com](https://correctover.com)
@@ -0,0 +1,2 @@
1
+ """Correctover Self-Healing Test Agent v0.1.0"""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,222 @@
1
+ """
2
+ correctover-test CLI — self-healing test agent entry point.
3
+
4
+ Usage:
5
+ correctover-test --quick # 5-second smoke test
6
+ correctover-test --framework crewai # Single framework
7
+ correctover-test --all --html # Full audit, HTML report
8
+ correctover-test --quick --cloud # Upload results to Correctover Cloud
9
+ """
10
+
11
+ import json
12
+ import sys
13
+ import urllib.request
14
+ from typing import Optional
15
+
16
+ import click
17
+
18
+ from .core.engine import TestEngine
19
+ from .core.reporter import Reporter
20
+ from .core.license import get_validator, LicenseExceededError
21
+ from .core.adapters import CrewAIAdapter, SmolagentsAdapter, LlamaIndexAdapter
22
+ from .core.scenarios import MCPSelfHealScenario, EnvProtectionScenario, ContractBreakScenario
23
+
24
+
25
+ ADAPTER_REGISTRY = {
26
+ "crewai": CrewAIAdapter,
27
+ "smolagents": SmolagentsAdapter,
28
+ "llamaindex": LlamaIndexAdapter,
29
+ }
30
+
31
+ SCENARIO_REGISTRY = {
32
+ "mcp_self_heal": MCPSelfHealScenario,
33
+ "env_protection": EnvProtectionScenario,
34
+ "contract_break": ContractBreakScenario,
35
+ }
36
+
37
+ VERSION = "0.2.0"
38
+
39
+
40
+ @click.command()
41
+ @click.option(
42
+ "--framework", "-f",
43
+ default="all",
44
+ help="Target framework: crewai, smolagents, llamaindex, or all",
45
+ )
46
+ @click.option(
47
+ "--scenario", "-s",
48
+ default="all",
49
+ help="Test scenario: mcp_self_heal, env_protection, contract_break, or all",
50
+ )
51
+ @click.option(
52
+ "--output", "-o",
53
+ default=None,
54
+ help="Output file path (.json / .md / .html based on extension)",
55
+ )
56
+ @click.option(
57
+ "--quick", "-q",
58
+ is_flag=True,
59
+ help="5-second smoke test: env_protection on all frameworks",
60
+ )
61
+ @click.option(
62
+ "--all", "run_all",
63
+ is_flag=True,
64
+ help="Full audit: all scenarios on all frameworks",
65
+ )
66
+ @click.option(
67
+ "--format", "output_format",
68
+ type=click.Choice(["terminal", "json", "markdown", "html"]),
69
+ default="terminal",
70
+ help="Output format (default: terminal)",
71
+ )
72
+ @click.option(
73
+ "--cloud",
74
+ default=None,
75
+ help="Correctover Cloud endpoint URL (e.g. https://cloud.correctover.com/api/v1/audit)",
76
+ )
77
+ @click.option(
78
+ "--no-cta",
79
+ is_flag=True,
80
+ help="Suppress the 'Fix this' CTA in output",
81
+ )
82
+ @click.version_option(version=VERSION, prog_name="correctover-test")
83
+ def main(
84
+ framework: str,
85
+ scenario: str,
86
+ output: Optional[str],
87
+ quick: bool,
88
+ run_all: bool,
89
+ output_format: str,
90
+ cloud: Optional[str],
91
+ no_cta: bool,
92
+ ):
93
+ """🔒 Correctover Agent Security Audit
94
+
95
+ Tests your AI agents for guardrail gaps, command injection,
96
+ environment variable leaks, and contract violations.
97
+
98
+ \b
99
+ Quick start:
100
+ correctover-test --quick # 5-second audit
101
+ correctover-test --all --html # Full report
102
+
103
+ \b
104
+ Correctover Cloud:
105
+ correctover-test --quick --cloud https://cloud.correctover.com/api/v1/audit
106
+ """
107
+
108
+ # Resolve frameworks
109
+ if quick:
110
+ framework = "all"
111
+ scenario = "env_protection"
112
+ elif run_all:
113
+ framework = "all"
114
+ scenario = "all"
115
+
116
+ if framework == "all":
117
+ adapters = [cls() for cls in ADAPTER_REGISTRY.values()]
118
+ else:
119
+ fws = [f.strip() for f in framework.split(",")]
120
+ adapters = []
121
+ for fw in fws:
122
+ if fw not in ADAPTER_REGISTRY:
123
+ click.echo(
124
+ f"❌ Unknown framework: {fw}. Available: {list(ADAPTER_REGISTRY.keys())}",
125
+ err=True,
126
+ )
127
+ sys.exit(1)
128
+ adapters.append(ADAPTER_REGISTRY[fw]())
129
+
130
+ # Resolve scenarios
131
+ if scenario == "all":
132
+ scenarios = [cls() for cls in SCENARIO_REGISTRY.values()]
133
+ else:
134
+ scs = [s.strip() for s in scenario.split(",")]
135
+ scenarios = []
136
+ for sc in scs:
137
+ if sc not in SCENARIO_REGISTRY:
138
+ click.echo(
139
+ f"❌ Unknown scenario: {sc}. Available: {list(SCENARIO_REGISTRY.keys())}",
140
+ err=True,
141
+ )
142
+ sys.exit(1)
143
+ scenarios.append(SCENARIO_REGISTRY[sc]())
144
+
145
+ # License check — download-to-pay enforcement
146
+ try:
147
+ from .core.license import LicenseValidator
148
+ license_key = LicenseValidator.get_license_from_env()
149
+ validator = get_validator()
150
+ if license_key:
151
+ validator.set_license_key(license_key)
152
+ status = validator.record_call()
153
+ if status["tier"] == "free":
154
+ click.echo(
155
+ f"📊 Free tier: {status['calls_remaining']} audits remaining today "
156
+ f"({status['calls_today']}/{status['limit']} used)",
157
+ err=True,
158
+ )
159
+ except LicenseExceededError as e:
160
+ click.echo(str(e), err=True)
161
+ sys.exit(1)
162
+
163
+ # Run
164
+ click.echo(
165
+ f"🔍 Auditing {len(adapters)} framework(s) × {len(scenarios)} scenario(s)...",
166
+ err=True,
167
+ )
168
+ engine = TestEngine(adapters)
169
+ results = engine.run_all(scenarios=scenarios)
170
+
171
+ # Upload to cloud
172
+ if cloud:
173
+ try:
174
+ payload = json.dumps({
175
+ "source": "correctover-test",
176
+ "version": VERSION,
177
+ "results": [r.to_dict() for r in results],
178
+ }).encode("utf-8")
179
+ req = urllib.request.Request(
180
+ cloud,
181
+ data=payload,
182
+ headers={"Content-Type": "application/json"},
183
+ method="POST",
184
+ )
185
+ with urllib.request.urlopen(req, timeout=10) as resp:
186
+ cloud_resp = json.loads(resp.read())
187
+ click.echo(
188
+ f"☁️ Uploaded to Correctover Cloud: {cloud_resp.get('dashboard_url', cloud)}",
189
+ err=True,
190
+ )
191
+ except Exception as e:
192
+ click.echo(f"⚠️ Cloud upload failed: {e}", err=True)
193
+
194
+ # Output
195
+ if output:
196
+ if output.endswith(".md"):
197
+ content = Reporter.to_markdown(results)
198
+ elif output.endswith(".html"):
199
+ content = Reporter.to_html(results)
200
+ else:
201
+ content = Reporter.to_json(results)
202
+ with open(output, "w") as f:
203
+ f.write(content)
204
+ click.echo(f"📄 Report written to {output}", err=True)
205
+ else:
206
+ if output_format == "json":
207
+ click.echo(Reporter.to_json(results))
208
+ elif output_format == "markdown":
209
+ click.echo(Reporter.to_markdown(results))
210
+ elif output_format == "html":
211
+ click.echo(Reporter.to_html(results))
212
+ else:
213
+ Reporter.to_terminal(results, show_cta=not no_cta)
214
+
215
+ # Exit code
216
+ total = len(results)
217
+ failed = sum(1 for r in results if r.verdict.value == "FAIL")
218
+ sys.exit(0 if failed == 0 else 1)
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()
@@ -0,0 +1,6 @@
1
+ from .base import FrameworkAdapter
2
+ from .crewai_adapter import CrewAIAdapter
3
+ from .smolagents_adapter import SmolagentsAdapter
4
+ from .llamaindex_adapter import LlamaIndexAdapter
5
+
6
+ __all__ = ["FrameworkAdapter", "CrewAIAdapter", "SmolagentsAdapter", "LlamaIndexAdapter"]
@@ -0,0 +1,206 @@
1
+ """
2
+ FrameworkAdapter ABC and shared data models.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass, field
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional
9
+ import time
10
+
11
+
12
+ class FaultType(str, Enum):
13
+ COMMAND_INJECTION = "command_injection"
14
+ ENV_EXPOSURE = "env_exposure"
15
+ CONTRACT_BREAK = "contract_break"
16
+
17
+
18
+ class Verdict(str, Enum):
19
+ PASS = "PASS"
20
+ FAIL = "FAIL"
21
+ SKIP = "SKIP"
22
+
23
+
24
+ @dataclass
25
+ class MetricSnapshot:
26
+ """6-dimensional verification metrics per test run."""
27
+ dimension: str # integrity | authorization | auditability | latency | coverage | recovery
28
+ value: float
29
+ threshold: float # pass threshold
30
+ passed: bool
31
+ detail: str = ""
32
+
33
+ def to_dict(self) -> Dict[str, Any]:
34
+ return {
35
+ "dimension": self.dimension,
36
+ "value": self.value,
37
+ "threshold": self.threshold,
38
+ "passed": self.passed,
39
+ "detail": self.detail,
40
+ }
41
+
42
+
43
+ @dataclass
44
+ class TestResult:
45
+ """Single scenario run result."""
46
+ framework: str
47
+ scenario: str
48
+ verdict: Verdict
49
+ metrics: List[MetricSnapshot] = field(default_factory=list)
50
+ decisions_count: int = 0
51
+ envelopes_count: int = 0
52
+ integrity_verified: bool = False
53
+ duration_ms: float = 0.0
54
+ errors: List[str] = field(default_factory=list)
55
+ details: Dict[str, Any] = field(default_factory=dict)
56
+
57
+ @property
58
+ def self_heal_rate(self) -> float:
59
+ """Fraction of metrics that passed."""
60
+ if not self.metrics:
61
+ return 0.0
62
+ return sum(1 for m in self.metrics if m.passed) / len(self.metrics)
63
+
64
+ def to_dict(self) -> Dict[str, Any]:
65
+ return {
66
+ "framework": self.framework,
67
+ "scenario": self.scenario,
68
+ "verdict": self.verdict.value,
69
+ "self_heal_rate": round(self.self_heal_rate, 4),
70
+ "metrics": [m.to_dict() for m in self.metrics],
71
+ "decisions_count": self.decisions_count,
72
+ "envelopes_count": self.envelopes_count,
73
+ "integrity_verified": self.integrity_verified,
74
+ "duration_ms": round(self.duration_ms, 2),
75
+ "errors": self.errors,
76
+ "details": self.details,
77
+ }
78
+
79
+
80
+ class FrameworkAdapter(ABC):
81
+ """Abstract adapter for hooking correctover into an Agent framework."""
82
+
83
+ name: str
84
+
85
+ @abstractmethod
86
+ def create_agent(self, tools: List[Any], config: Optional[Dict] = None) -> Any:
87
+ """Create a native agent instance for this framework."""
88
+ ...
89
+
90
+ @abstractmethod
91
+ def register_guardrail(self, agent: Any, provider: Any) -> Any:
92
+ """
93
+ Inject a correctover GuardrailProvider into the agent.
94
+ Returns the modified agent or a wrapper.
95
+ """
96
+ ...
97
+
98
+ @abstractmethod
99
+ def run_task(self, agent: Any, task: str) -> Dict[str, Any]:
100
+ """
101
+ Execute a task through the agent.
102
+ Returns {"output": str, "duration_ms": float, "error": Optional[str]}.
103
+ """
104
+ ...
105
+
106
+ @abstractmethod
107
+ def detect_missing_guardrail(self, agent: Any) -> List[Dict]:
108
+ """
109
+ Scan the agent for missing guardrail hooks.
110
+ Returns list of findings, empty list = no issues.
111
+ """
112
+ ...
113
+
114
+ @abstractmethod
115
+ def inject_fault(self, agent: Any, fault_type: FaultType) -> Any:
116
+ """
117
+ Inject a fault into the agent's tool call pipeline.
118
+ Returns the modified agent/context.
119
+ """
120
+ ...
121
+
122
+
123
+ class TestScenario(ABC):
124
+ """Abstract test scenario that runs against an adapter+agent pair."""
125
+
126
+ name: str
127
+ description: str
128
+ fault_type: FaultType
129
+
130
+ @abstractmethod
131
+ def setup(self, adapter: FrameworkAdapter, agent: Any) -> Any:
132
+ """Prepare the agent for this scenario. Return modified agent/context."""
133
+ ...
134
+
135
+ @abstractmethod
136
+ def execute(self, adapter: FrameworkAdapter, agent: Any) -> Dict[str, Any]:
137
+ """Run the scenario. Return raw results dict."""
138
+ ...
139
+
140
+ @abstractmethod
141
+ def verify(
142
+ self,
143
+ adapter: FrameworkAdapter,
144
+ agent: Any,
145
+ raw_result: Dict[str, Any],
146
+ trail: Any, # correctover.AuditTrail
147
+ ) -> List[MetricSnapshot]:
148
+ """Verify the 6-dimension metrics from AuditTrail."""
149
+ ...
150
+
151
+ def run(
152
+ self,
153
+ adapter: FrameworkAdapter,
154
+ agent: Any,
155
+ trail: Any,
156
+ ) -> TestResult:
157
+ """Orchestrate full scenario lifecycle."""
158
+ t0 = time.time()
159
+ errors = []
160
+
161
+ try:
162
+ agent = self.setup(adapter, agent)
163
+ except Exception as e:
164
+ errors.append(f"setup: {e}")
165
+ return TestResult(
166
+ framework=adapter.name,
167
+ scenario=self.name,
168
+ verdict=Verdict.FAIL,
169
+ errors=errors,
170
+ details={"phase": "setup"},
171
+ )
172
+
173
+ try:
174
+ raw = self.execute(adapter, agent)
175
+ except Exception as e:
176
+ errors.append(f"execute: {e}")
177
+ return TestResult(
178
+ framework=adapter.name,
179
+ scenario=self.name,
180
+ verdict=Verdict.FAIL,
181
+ errors=errors,
182
+ details={"phase": "execute"},
183
+ )
184
+
185
+ try:
186
+ metrics = self.verify(adapter, agent, raw, trail)
187
+ except Exception as e:
188
+ errors.append(f"verify: {e}")
189
+ metrics = []
190
+
191
+ duration_ms = (time.time() - t0) * 1000
192
+ all_pass = all(m.passed for m in metrics) if metrics else False
193
+ integrity_ok = trail.verify_all() if trail else False
194
+
195
+ return TestResult(
196
+ framework=adapter.name,
197
+ scenario=self.name,
198
+ verdict=Verdict.PASS if all_pass and not errors else Verdict.FAIL,
199
+ metrics=metrics,
200
+ decisions_count=trail.decision_count if trail else 0,
201
+ envelopes_count=trail.envelope_count if trail else 0,
202
+ integrity_verified=integrity_ok,
203
+ duration_ms=duration_ms,
204
+ errors=errors,
205
+ details={"raw_output": raw.get("output", "")[:500]},
206
+ )