torusguard 2.1.0 → 2.1.2
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.
- package/.torusguard/.manifest.json +55 -5
- package/.torusguard/core/__init__.py +146 -0
- package/.torusguard/core/agent_roles.py +104 -0
- package/.torusguard/core/ast_walker.py +283 -0
- package/.torusguard/core/authorization.py +218 -0
- package/.torusguard/core/browser_verifier.py +128 -0
- package/.torusguard/core/bundle.py +141 -0
- package/.torusguard/core/call_graph.py +184 -0
- package/.torusguard/core/clustering.py +275 -0
- package/.torusguard/core/confidence.py +120 -0
- package/.torusguard/core/cross_file_taint.py +101 -0
- package/.torusguard/core/exploit_checker.py +317 -0
- package/.torusguard/core/formatter.py +351 -0
- package/.torusguard/core/governance.py +210 -0
- package/.torusguard/core/identity.py +104 -0
- package/.torusguard/core/import_resolver.py +91 -0
- package/.torusguard/core/incremental.py +102 -0
- package/.torusguard/core/lifecycle.py +137 -0
- package/.torusguard/core/models.py +425 -0
- package/.torusguard/core/parallel.py +56 -0
- package/.torusguard/core/parser.py +202 -0
- package/.torusguard/core/rechecker.py +107 -0
- package/.torusguard/core/replay_trace.py +178 -0
- package/.torusguard/core/rules_registry.py +131 -0
- package/.torusguard/core/run_folder.py +60 -0
- package/.torusguard/core/run_manager.py +163 -0
- package/.torusguard/core/runtime_evidence.py +175 -0
- package/.torusguard/core/runtime_validator.py +246 -0
- package/.torusguard/core/safety_gate.py +139 -0
- package/.torusguard/core/sarif.py +189 -0
- package/.torusguard/core/stack_profiler.py +184 -0
- package/.torusguard/core/symbol_table.py +91 -0
- package/.torusguard/core/taint.py +133 -0
- package/.torusguard/core/taint_graph.py +235 -0
- package/.torusguard/core/taint_rules.py +268 -0
- package/.torusguard/core/v070_reporter.py +102 -0
- package/.torusguard/core/v070_workflow.py +339 -0
- package/.torusguard/core/v6_reporter.py +180 -0
- package/.torusguard/core/v6_workflow.py +221 -0
- package/.torusguard/core/watcher.py +58 -0
- package/.torusguard/custom_rules/README.md +24 -0
- package/.torusguard/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
- package/.torusguard/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
- package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/rules_sync.cpython-314.pyc +0 -0
- package/.torusguard/scripts/audit_runner.py +108 -10
- package/.torusguard/scripts/deliberation_tournament.py +136 -0
- package/.torusguard/scripts/finding_scorer.py +43 -13
- package/.torusguard/scripts/reachability_analyzer.py +165 -0
- package/.torusguard/scripts/skill_profiler.py +26 -0
- package/.torusguard/scripts/stride_generator.py +198 -0
- package/.torusguard/skills/torusguard/SKILL.md +6 -2
- package/.torusguard/skills/torusguard-audit/SKILL.md +109 -84
- package/.torusguard/skills/torusguard-review/SKILL.md +27 -0
- package/.torusguard/skills/torusguard-threatmodel/SKILL.md +27 -0
- package/.torusguard/workflows/audit.md +21 -17
- package/.torusguard/workflows/review.md +11 -0
- package/.torusguard/workflows/threatmodel.md +9 -0
- package/README.md +149 -48
- package/package.json +10 -2
- package/skills/torusguard/SKILL.md +6 -2
- package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/skills/torusguard/bootstrap.py +3 -3
- package/skills/torusguard/payload/.manifest.json +56 -7
- package/skills/torusguard/payload/core/__init__.py +146 -0
- package/skills/torusguard/payload/core/agent_roles.py +104 -0
- package/skills/torusguard/payload/core/ast_walker.py +283 -0
- package/skills/torusguard/payload/core/authorization.py +218 -0
- package/skills/torusguard/payload/core/browser_verifier.py +128 -0
- package/skills/torusguard/payload/core/bundle.py +141 -0
- package/skills/torusguard/payload/core/call_graph.py +184 -0
- package/skills/torusguard/payload/core/clustering.py +275 -0
- package/skills/torusguard/payload/core/confidence.py +120 -0
- package/skills/torusguard/payload/core/cross_file_taint.py +101 -0
- package/skills/torusguard/payload/core/exploit_checker.py +317 -0
- package/skills/torusguard/payload/core/formatter.py +351 -0
- package/skills/torusguard/payload/core/governance.py +210 -0
- package/skills/torusguard/payload/core/identity.py +104 -0
- package/skills/torusguard/payload/core/import_resolver.py +91 -0
- package/skills/torusguard/payload/core/incremental.py +102 -0
- package/skills/torusguard/payload/core/lifecycle.py +137 -0
- package/skills/torusguard/payload/core/models.py +425 -0
- package/skills/torusguard/payload/core/parallel.py +56 -0
- package/skills/torusguard/payload/core/parser.py +202 -0
- package/skills/torusguard/payload/core/rechecker.py +107 -0
- package/skills/torusguard/payload/core/replay_trace.py +178 -0
- package/skills/torusguard/payload/core/rules_registry.py +131 -0
- package/skills/torusguard/payload/core/run_folder.py +60 -0
- package/skills/torusguard/payload/core/run_manager.py +163 -0
- package/skills/torusguard/payload/core/runtime_evidence.py +175 -0
- package/skills/torusguard/payload/core/runtime_validator.py +246 -0
- package/skills/torusguard/payload/core/safety_gate.py +139 -0
- package/skills/torusguard/payload/core/sarif.py +189 -0
- package/skills/torusguard/payload/core/stack_profiler.py +184 -0
- package/skills/torusguard/payload/core/symbol_table.py +91 -0
- package/skills/torusguard/payload/core/taint.py +133 -0
- package/skills/torusguard/payload/core/taint_graph.py +235 -0
- package/skills/torusguard/payload/core/taint_rules.py +268 -0
- package/skills/torusguard/payload/core/v070_reporter.py +102 -0
- package/skills/torusguard/payload/core/v070_workflow.py +339 -0
- package/skills/torusguard/payload/core/v6_reporter.py +180 -0
- package/skills/torusguard/payload/core/v6_workflow.py +221 -0
- package/skills/torusguard/payload/core/watcher.py +58 -0
- package/skills/torusguard/payload/custom_rules/README.md +15 -0
- package/skills/torusguard/payload/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
- package/skills/torusguard/payload/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
- package/skills/torusguard/payload/rules/container/TG-CONT-001-root-user-execution.md +50 -50
- package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -47
- package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -53
- package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -43
- package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -44
- package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -41
- package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -40
- package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -72
- package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -51
- package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -51
- package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -46
- package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -43
- package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
- package/skills/torusguard/payload/scripts/deliberation_tournament.py +127 -0
- package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
- package/skills/torusguard/payload/scripts/reachability_analyzer.py +164 -0
- package/skills/torusguard/payload/scripts/stride_generator.py +196 -0
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +6 -2
- package/skills/torusguard/payload/skills/torusguard/bootstrap.py +3 -3
- package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -95
- package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +109 -84
- package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -94
- package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -92
- package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -94
- package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -91
- package/skills/torusguard/payload/skills/torusguard-review/SKILL.md +27 -0
- package/skills/torusguard/payload/skills/torusguard-threatmodel/SKILL.md +27 -0
- package/skills/torusguard/payload/workflows/ai-guard.md +31 -31
- package/skills/torusguard/payload/workflows/audit.md +21 -17
- package/skills/torusguard/payload/workflows/container.md +29 -29
- package/skills/torusguard/payload/workflows/git-mine.md +25 -25
- package/skills/torusguard/payload/workflows/ocr-scan.md +25 -25
- package/skills/torusguard/payload/workflows/redos.md +27 -27
- package/skills/torusguard/payload/workflows/review.md +11 -0
- package/skills/torusguard/payload/workflows/threatmodel.md +9 -0
- package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
- package/skills/torusguard/references/csharp-security.md +41 -41
- package/skills/torusguard/references/go-security.md +41 -41
- package/skills/torusguard/references/java-security.md +40 -40
- package/skills/torusguard/references/polyglot-security-matrix.md +25 -25
- package/skills/torusguard/references/rust-security.md +40 -40
- package/skills/torusguard-audit/SKILL.md +107 -83
- package/skills/torusguard-review/SKILL.md +27 -0
- package/skills/torusguard-threatmodel/SKILL.md +27 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v0.7.0 Unified Workflow Controller
|
|
3
|
+
Coordinates the complete v0.7.0 lifecycle:
|
|
4
|
+
1. Authorization & Scope Enforcement
|
|
5
|
+
2. Stack Profiling & Route Discovery (Profiler Role)
|
|
6
|
+
3. Static Audit & Root-Cause Clustering
|
|
7
|
+
4. Authorized Runtime Web Probing (Validator Role)
|
|
8
|
+
5. Bounded Exploitability Confirmation
|
|
9
|
+
6. Remediation Bundle Packaging with Runtime Insights (Remediator Role)
|
|
10
|
+
7. Multi-Agent Governance & Policy Review (Reviewer Role)
|
|
11
|
+
8. Replayable Validation Trace Serialization
|
|
12
|
+
9. Unified Reporting & Multi-Analysis SARIF Export
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
from core.authorization import AuthorizationRecord, AuthorizationManager, TargetScope, AuthorizationError
|
|
19
|
+
from core.safety_gate import SafetyGate
|
|
20
|
+
from core.runtime_evidence import EvidenceCollector
|
|
21
|
+
from core.runtime_validator import WebValidator
|
|
22
|
+
from core.exploit_checker import ExploitChecker, ExploitCheckResult, ExploitabilityStatus
|
|
23
|
+
from core.browser_verifier import BrowserVerifier
|
|
24
|
+
from core.agent_roles import RoleOrchestrator, AgentRole
|
|
25
|
+
from core.replay_trace import ReplayManager
|
|
26
|
+
from core.run_manager import RunManager
|
|
27
|
+
from core.v6_workflow import V6Workflow
|
|
28
|
+
from core.v070_reporter import V070Reporter
|
|
29
|
+
from core.sarif import SarifExporter
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class V070Workflow:
|
|
33
|
+
"""
|
|
34
|
+
Unified controller for TorusGuard v0.7.0 authorized runtime validation and governed remediation.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, target_root: Optional[Path] = None, output_base: Optional[Path] = None):
|
|
38
|
+
self.target_root = target_root or Path(".")
|
|
39
|
+
self.output_base = output_base or Path(".torusguard/runs")
|
|
40
|
+
self.v6_engine = V6Workflow(target_root=self.target_root, output_base=self.output_base)
|
|
41
|
+
|
|
42
|
+
def execute_runtime_validation(
|
|
43
|
+
self,
|
|
44
|
+
target_name: str,
|
|
45
|
+
auth_record: AuthorizationRecord,
|
|
46
|
+
static_findings: List[Dict[str, Any]],
|
|
47
|
+
runtime_probes: List[Dict[str, Any]],
|
|
48
|
+
run_id: Optional[str] = None,
|
|
49
|
+
export_sarif: bool = True
|
|
50
|
+
) -> Dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
Executes the end-to-end v0.7.0 authorized runtime validation workflow.
|
|
53
|
+
"""
|
|
54
|
+
# 1. Initialize Run Folder
|
|
55
|
+
run_mgr = RunManager(
|
|
56
|
+
base_dir=self.output_base,
|
|
57
|
+
target_name=target_name,
|
|
58
|
+
command="runtime-validate",
|
|
59
|
+
run_id=run_id
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 2. Store Authorization Artifacts
|
|
63
|
+
auth_scope_file, auth_doc_file = AuthorizationManager.write_artifacts(run_mgr.run_path, auth_record)
|
|
64
|
+
|
|
65
|
+
# 3. Initialize Agent Role Orchestrator & Evidence Collector
|
|
66
|
+
orchestrator = RoleOrchestrator(run_id=run_mgr.run_id)
|
|
67
|
+
evidence_collector = EvidenceCollector()
|
|
68
|
+
web_validator = WebValidator(
|
|
69
|
+
auth_record=auth_record,
|
|
70
|
+
evidence_collector=evidence_collector,
|
|
71
|
+
max_requests=auth_record.scope.max_requests
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# 4. Agent Role: Profiler -> Validator Handoff
|
|
75
|
+
orchestrator.record_handoff(
|
|
76
|
+
from_role=AgentRole.PROFILER,
|
|
77
|
+
to_role=AgentRole.VALIDATOR,
|
|
78
|
+
contract_goal="Deliver detected application endpoints and static findings for authorized runtime validation.",
|
|
79
|
+
inputs={"target_name": target_name, "static_finding_count": len(static_findings)},
|
|
80
|
+
outputs={"authorized_hosts": auth_record.scope.target_hosts, "allowed_prefixes": auth_record.scope.allowed_path_prefixes}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# 5. Static Scan Enrichment & Clustering (v6 base)
|
|
84
|
+
enriched_findings, clusters = self.v6_engine.execute_audit(
|
|
85
|
+
static_findings,
|
|
86
|
+
target_name=target_name,
|
|
87
|
+
run_id=run_mgr.run_id,
|
|
88
|
+
export_sarif=False
|
|
89
|
+
), []
|
|
90
|
+
|
|
91
|
+
# Re-derive clusters
|
|
92
|
+
from core.clustering import ClusteringEngine
|
|
93
|
+
clusters = ClusteringEngine.cluster_findings(static_findings)
|
|
94
|
+
|
|
95
|
+
# 6. Execute Bounded Exploitability Probes (Validator Role)
|
|
96
|
+
exploit_results: List[ExploitCheckResult] = []
|
|
97
|
+
replay_managers: List[ReplayManager] = []
|
|
98
|
+
|
|
99
|
+
for probe in runtime_probes:
|
|
100
|
+
res, replay_mgr = self._dispatch_single_probe(probe, web_validator, auth_record)
|
|
101
|
+
exploit_results.append(res)
|
|
102
|
+
replay_managers.append(replay_mgr)
|
|
103
|
+
|
|
104
|
+
# 7. Agent Role: Validator -> Remediator Handoff
|
|
105
|
+
orchestrator.record_handoff(
|
|
106
|
+
from_role=AgentRole.VALIDATOR,
|
|
107
|
+
to_role=AgentRole.REMEDIATOR,
|
|
108
|
+
contract_goal="Deliver confirmed exploitability evidence to prioritize minimal safe remediation bundles.",
|
|
109
|
+
inputs={"probes_executed": len(exploit_results)},
|
|
110
|
+
outputs={"confirmed_exploitable": sum(1 for r in exploit_results if r.status == ExploitabilityStatus.RUNTIME_CONFIRMED.value)}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# 8. Enrich static findings with runtime status
|
|
114
|
+
rt_lookup = {r.finding_id: r for r in exploit_results}
|
|
115
|
+
for f in static_findings:
|
|
116
|
+
f_id = f.get("finding_id")
|
|
117
|
+
if f_id in rt_lookup:
|
|
118
|
+
f["runtime_exploitability"] = rt_lookup[f_id].status
|
|
119
|
+
|
|
120
|
+
# 9. Remediation Bundles formulation (v6 base)
|
|
121
|
+
bundles = self.v6_engine.execute_harden(run_mgr, static_findings)
|
|
122
|
+
|
|
123
|
+
# 10. Agent Role: Remediator -> Reviewer Handoff
|
|
124
|
+
manual_review_items = [
|
|
125
|
+
{"finding_id": r.finding_id, "reason": r.proof_summary}
|
|
126
|
+
for r in exploit_results
|
|
127
|
+
if r.status in [ExploitabilityStatus.NEEDS_MANUAL_REVIEW.value, ExploitabilityStatus.BLOCKED_BY_CONTROLS.value]
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
orchestrator.record_handoff(
|
|
131
|
+
from_role=AgentRole.REMEDIATOR,
|
|
132
|
+
to_role=AgentRole.REVIEWER,
|
|
133
|
+
contract_goal="Review runtime evidence sufficiency, verify safety gate compliance, and sign off on final report.",
|
|
134
|
+
inputs={"bundles_packaged": len(bundles), "manual_review_items": len(manual_review_items)},
|
|
135
|
+
outputs={"verdict": "Signed Off", "status": "Ready for emission"}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# 11-16. Write All Workflow Artifacts
|
|
139
|
+
web_artifacts = self._write_runtime_artifacts(
|
|
140
|
+
run_mgr=run_mgr,
|
|
141
|
+
target_name=target_name,
|
|
142
|
+
auth_record=auth_record,
|
|
143
|
+
static_findings=static_findings,
|
|
144
|
+
exploit_results=exploit_results,
|
|
145
|
+
replay_managers=replay_managers,
|
|
146
|
+
web_validator=web_validator,
|
|
147
|
+
orchestrator=orchestrator,
|
|
148
|
+
clusters=clusters,
|
|
149
|
+
manual_review_items=manual_review_items,
|
|
150
|
+
bundles=bundles,
|
|
151
|
+
export_sarif=export_sarif
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"run_manager": run_mgr,
|
|
156
|
+
"authorization_artifacts": (auth_scope_file, auth_doc_file),
|
|
157
|
+
"exploit_results": exploit_results,
|
|
158
|
+
"web_artifacts": web_artifacts,
|
|
159
|
+
"summary_file": run_mgr.summary_file,
|
|
160
|
+
"sarif_file": run_mgr.sarif_file,
|
|
161
|
+
"manifest_file": run_mgr.manifest_file
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def _dispatch_single_probe(
|
|
166
|
+
cls,
|
|
167
|
+
probe: Dict[str, Any],
|
|
168
|
+
web_validator: WebValidator,
|
|
169
|
+
auth_record: AuthorizationRecord
|
|
170
|
+
) -> Tuple[ExploitCheckResult, ReplayManager]:
|
|
171
|
+
"""Dispatches a single bounded probe and configures its replay manager."""
|
|
172
|
+
finding_id = probe.get("finding_id", "fnd-unknown")
|
|
173
|
+
cluster_id = probe.get("cluster_id", "cluster-general")
|
|
174
|
+
check_type = probe.get("check_type")
|
|
175
|
+
target_url = probe.get("target_url")
|
|
176
|
+
|
|
177
|
+
base_url = f"{auth_record.scope.target_hosts[0]}"
|
|
178
|
+
if not base_url.startswith("http"):
|
|
179
|
+
base_url = f"http://{base_url}"
|
|
180
|
+
replay_mgr = ReplayManager(
|
|
181
|
+
finding_id=finding_id, target_base_url=base_url, description=f"Verification trace for {check_type}"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if check_type == "auth_bypass":
|
|
185
|
+
res = ExploitChecker.check_auth_bypass(
|
|
186
|
+
validator=web_validator,
|
|
187
|
+
finding_id=finding_id,
|
|
188
|
+
cluster_id=cluster_id,
|
|
189
|
+
endpoint_url=target_url,
|
|
190
|
+
expected_sensitive_marker=probe.get("expected_sensitive_marker")
|
|
191
|
+
)
|
|
192
|
+
replay_mgr.add_step(
|
|
193
|
+
action_type="http_request",
|
|
194
|
+
target=target_url,
|
|
195
|
+
method="GET",
|
|
196
|
+
expected_status=200,
|
|
197
|
+
expected_pattern=probe.get("expected_sensitive_marker")
|
|
198
|
+
)
|
|
199
|
+
elif check_type == "tenant_isolation":
|
|
200
|
+
res = ExploitChecker.check_tenant_isolation(
|
|
201
|
+
validator=web_validator,
|
|
202
|
+
finding_id=finding_id,
|
|
203
|
+
cluster_id=cluster_id,
|
|
204
|
+
tenant_a_resource_url=target_url,
|
|
205
|
+
tenant_b_auth_headers=probe.get("tenant_b_auth_headers", {}),
|
|
206
|
+
tenant_a_data_marker=probe.get("tenant_a_data_marker", "")
|
|
207
|
+
)
|
|
208
|
+
replay_mgr.add_step(
|
|
209
|
+
action_type="http_request",
|
|
210
|
+
target=target_url,
|
|
211
|
+
method="GET",
|
|
212
|
+
headers=probe.get("tenant_b_auth_headers", {}),
|
|
213
|
+
expected_status=200,
|
|
214
|
+
expected_pattern=probe.get("tenant_a_data_marker")
|
|
215
|
+
)
|
|
216
|
+
elif check_type == "header_trust":
|
|
217
|
+
res = ExploitChecker.check_header_trust(
|
|
218
|
+
validator=web_validator,
|
|
219
|
+
finding_id=finding_id,
|
|
220
|
+
cluster_id=cluster_id,
|
|
221
|
+
endpoint_url=target_url,
|
|
222
|
+
spoofed_headers=probe.get("spoofed_headers", {}),
|
|
223
|
+
expected_reflection_marker=probe.get("expected_reflection_marker", "")
|
|
224
|
+
)
|
|
225
|
+
replay_mgr.add_step(
|
|
226
|
+
action_type="http_request",
|
|
227
|
+
target=target_url,
|
|
228
|
+
method="GET",
|
|
229
|
+
headers=probe.get("spoofed_headers", {}),
|
|
230
|
+
expected_status=200,
|
|
231
|
+
expected_pattern=probe.get("expected_reflection_marker")
|
|
232
|
+
)
|
|
233
|
+
elif check_type == "debug_exposure":
|
|
234
|
+
res = ExploitChecker.check_debug_exposure(
|
|
235
|
+
validator=web_validator,
|
|
236
|
+
finding_id=finding_id,
|
|
237
|
+
cluster_id=cluster_id,
|
|
238
|
+
debug_url=target_url,
|
|
239
|
+
debug_marker=probe.get("debug_marker", "")
|
|
240
|
+
)
|
|
241
|
+
replay_mgr.add_step(
|
|
242
|
+
action_type="http_request",
|
|
243
|
+
target=target_url,
|
|
244
|
+
method="GET",
|
|
245
|
+
expected_status=200,
|
|
246
|
+
expected_pattern=probe.get("debug_marker")
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
status, headers, body, decision = web_validator.execute_probe(
|
|
250
|
+
finding_id=finding_id,
|
|
251
|
+
cluster_id=cluster_id,
|
|
252
|
+
method=probe.get("method", "GET"),
|
|
253
|
+
target_url=target_url
|
|
254
|
+
)
|
|
255
|
+
res = ExploitCheckResult(
|
|
256
|
+
finding_id=finding_id,
|
|
257
|
+
issue_class=check_type or "general",
|
|
258
|
+
status=ExploitabilityStatus.RUNTIME_LIKELY.value if status == 200 else ExploitabilityStatus.NOT_REPRODUCIBLE_IN_SCOPE.value,
|
|
259
|
+
confidence_score=70 if status == 200 else 20,
|
|
260
|
+
probe_url=target_url,
|
|
261
|
+
http_status_observed=status,
|
|
262
|
+
proof_summary=f"HTTP {status} observed.",
|
|
263
|
+
reproducible=True,
|
|
264
|
+
remediation_advice="Review endpoint configuration."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return res, replay_mgr
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def _write_runtime_artifacts(
|
|
271
|
+
cls,
|
|
272
|
+
run_mgr: RunManager,
|
|
273
|
+
target_name: str,
|
|
274
|
+
auth_record: AuthorizationRecord,
|
|
275
|
+
static_findings: List[Dict[str, Any]],
|
|
276
|
+
exploit_results: List[ExploitCheckResult],
|
|
277
|
+
replay_managers: List[ReplayManager],
|
|
278
|
+
web_validator: WebValidator,
|
|
279
|
+
orchestrator: RoleOrchestrator,
|
|
280
|
+
clusters: List[Any],
|
|
281
|
+
manual_review_items: List[Dict[str, Any]],
|
|
282
|
+
bundles: List[Any],
|
|
283
|
+
export_sarif: bool
|
|
284
|
+
) -> Dict[str, Any]:
|
|
285
|
+
"""Emits replay traces, web validation reports, role audits, summary markdown, SARIF, and manifest."""
|
|
286
|
+
# 11. Write Replay Traces
|
|
287
|
+
for rm in replay_managers:
|
|
288
|
+
rm.write_artifacts(run_mgr.run_path)
|
|
289
|
+
|
|
290
|
+
# 12. Write Web Validation and Session Notes
|
|
291
|
+
web_artifacts = web_validator.write_report_artifacts(run_mgr.run_path)
|
|
292
|
+
|
|
293
|
+
# 13. Write Agent Role Audit Artifacts
|
|
294
|
+
orchestrator.write_artifacts(run_mgr.run_path)
|
|
295
|
+
|
|
296
|
+
# 14. Write Combined Summary Report
|
|
297
|
+
summary_md = V070Reporter.render_combined_summary(
|
|
298
|
+
target_name=target_name,
|
|
299
|
+
run_id=run_mgr.run_id,
|
|
300
|
+
auth_id=auth_record.authorization_id,
|
|
301
|
+
static_findings=static_findings,
|
|
302
|
+
runtime_results=[r.to_dict() for r in exploit_results],
|
|
303
|
+
clusters=clusters,
|
|
304
|
+
manual_review_items=manual_review_items
|
|
305
|
+
)
|
|
306
|
+
with open(run_mgr.summary_file, "w", encoding="utf-8") as f:
|
|
307
|
+
f.write(summary_md)
|
|
308
|
+
|
|
309
|
+
# 15. Export Multi-Analysis Partitioned SARIF v2.1.0
|
|
310
|
+
if export_sarif:
|
|
311
|
+
sarif_data = SarifExporter.generate_sarif(
|
|
312
|
+
findings=static_findings,
|
|
313
|
+
tool_version="0.7.0",
|
|
314
|
+
analysis_category="torusguard/runtime"
|
|
315
|
+
)
|
|
316
|
+
with open(run_mgr.sarif_file, "w", encoding="utf-8") as f:
|
|
317
|
+
import json
|
|
318
|
+
json.dump(sarif_data, f, indent=2)
|
|
319
|
+
|
|
320
|
+
# 16. Write Manifest
|
|
321
|
+
run_mgr.write_manifest(
|
|
322
|
+
status_counts={
|
|
323
|
+
"total_findings": len(static_findings),
|
|
324
|
+
"runtime_confirmed": sum(1 for r in exploit_results if r.status == ExploitabilityStatus.RUNTIME_CONFIRMED.value),
|
|
325
|
+
"runtime_likely": sum(1 for r in exploit_results if r.status == ExploitabilityStatus.RUNTIME_LIKELY.value),
|
|
326
|
+
"not_reproducible": sum(1 for r in exploit_results if r.status == ExploitabilityStatus.NOT_REPRODUCIBLE_IN_SCOPE.value),
|
|
327
|
+
"needs_manual_review": len(manual_review_items),
|
|
328
|
+
"remediated": len(bundles),
|
|
329
|
+
"verified_fixed": 0,
|
|
330
|
+
"regressed": 0
|
|
331
|
+
},
|
|
332
|
+
extra_meta={
|
|
333
|
+
"authorization_id": auth_record.authorization_id,
|
|
334
|
+
"version": "v0.7.0",
|
|
335
|
+
"analysis_category": "torusguard/runtime"
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return web_artifacts
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v6 Report Generator
|
|
3
|
+
Generates clean, traceable, Human-First Markdown reports for the v6 workflow:
|
|
4
|
+
- summary.md
|
|
5
|
+
- findings.md
|
|
6
|
+
- remediation.md
|
|
7
|
+
- apply-plan.md
|
|
8
|
+
- recheck.md
|
|
9
|
+
- diff-summary.md
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
13
|
+
from core.clustering import RootCauseCluster
|
|
14
|
+
from core.bundle import RemediationBundle
|
|
15
|
+
from core.governance import PatchPolicyDecision
|
|
16
|
+
from core.rechecker import TargetedRecheckResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class V6Reporter:
|
|
20
|
+
"""
|
|
21
|
+
Renders standard Markdown artifacts for TorusGuard v6 run folders.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def render_summary(
|
|
26
|
+
target_name: str,
|
|
27
|
+
run_id: str,
|
|
28
|
+
findings: List[Dict[str, Any]],
|
|
29
|
+
clusters: List[RootCauseCluster],
|
|
30
|
+
recheck_results: Optional[List[TargetedRecheckResult]] = None,
|
|
31
|
+
) -> str:
|
|
32
|
+
total = len(findings)
|
|
33
|
+
confirmed = sum(1 for f in findings if f.get("confidence_band") == "Confirmed")
|
|
34
|
+
needs_review = sum(1 for f in findings if f.get("confidence_band") == "Needs Review")
|
|
35
|
+
high_conf = sum(1 for f in findings if f.get("confidence_band") == "High Confidence")
|
|
36
|
+
|
|
37
|
+
out = []
|
|
38
|
+
out.append(f"# TorusGuard v6 Execution Summary — `{target_name}`\n")
|
|
39
|
+
out.append(f"- **Run ID:** `{run_id}`")
|
|
40
|
+
out.append(f"- **Total Findings Modeled:** {total}")
|
|
41
|
+
out.append(f"- **Confirmed:** {confirmed} | **High Confidence:** {high_conf} | **Needs Review:** {needs_review}")
|
|
42
|
+
out.append(f"- **Root-Cause Clusters:** {len(clusters)}\n")
|
|
43
|
+
|
|
44
|
+
out.append("## Root-Cause Clustering Breakdown\n")
|
|
45
|
+
out.append("| Cluster ID | Root-Cause Title | Primary Rule | Findings | Files | Hotspot Module | Severity |")
|
|
46
|
+
out.append("|---|---|---|---:|---:|---|---|")
|
|
47
|
+
for c in clusters:
|
|
48
|
+
hotspot = f"`{c.hotspot_module}`" if c.hotspot_module else "root"
|
|
49
|
+
out.append(
|
|
50
|
+
f"| `{c.cluster_id}` | {c.title} | `{c.primary_rule}` | {len(c.finding_ids)} | {len(c.affected_files)} | {hotspot} | {c.risk_severity} |"
|
|
51
|
+
)
|
|
52
|
+
out.append("\n")
|
|
53
|
+
|
|
54
|
+
if recheck_results:
|
|
55
|
+
fixed = sum(1 for r in recheck_results if r.outcome.value == "Confirmed Fixed")
|
|
56
|
+
regressed = sum(1 for r in recheck_results if r.outcome.value == "Regressed")
|
|
57
|
+
out.append("## Targeted Recheck Status\n")
|
|
58
|
+
out.append(f"- **Verified Fixed:** {fixed}/{len(recheck_results)}")
|
|
59
|
+
out.append(f"- **Regressions:** {regressed}\n")
|
|
60
|
+
|
|
61
|
+
out.append("## Next Actions\n")
|
|
62
|
+
out.append("1. Run `/torusguard harden` to inspect structured remediation bundles.")
|
|
63
|
+
out.append("2. Run `/torusguard apply` to execute Ponytail-governed minimal code modifications.")
|
|
64
|
+
out.append("3. Run `/torusguard recheck` to verify impacted trust boundaries.\n")
|
|
65
|
+
|
|
66
|
+
return "\n".join(out)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def render_findings(findings: List[Dict[str, Any]]) -> str:
|
|
70
|
+
out = ["# TorusGuard v6 Detailed Findings\n"]
|
|
71
|
+
total = len(findings)
|
|
72
|
+
|
|
73
|
+
# High scale handling: if more than 25 findings, show top 15 directly and collapse the rest
|
|
74
|
+
collapse_threshold = 25
|
|
75
|
+
top_findings = findings[:15] if total > collapse_threshold else findings
|
|
76
|
+
overflow_findings = findings[15:] if total > collapse_threshold else []
|
|
77
|
+
|
|
78
|
+
for f in top_findings:
|
|
79
|
+
f_id = f.get("finding_id", "fnd-01")
|
|
80
|
+
rule_id = f.get("rule_id", "TG-GENERIC")
|
|
81
|
+
title = f.get("title", "Security Flaw")
|
|
82
|
+
severity = f.get("severity", "High")
|
|
83
|
+
conf_score = f.get("confidence_score", 85)
|
|
84
|
+
conf_band = f.get("confidence_band", "High Confidence")
|
|
85
|
+
cluster_id = f.get("cluster_id", "cluster-general")
|
|
86
|
+
priority = f.get("priority", "Near-Term (P1)")
|
|
87
|
+
|
|
88
|
+
target = f.get("target", {})
|
|
89
|
+
file_path = target.get("file_path", "unknown")
|
|
90
|
+
start_line = target.get("line_start", 1)
|
|
91
|
+
end_line = target.get("line_end", start_line)
|
|
92
|
+
|
|
93
|
+
evidence = f.get("evidence", {}).get("code_snippet", "# code")
|
|
94
|
+
|
|
95
|
+
out.append(f"### 🚨 [{rule_id}] {title}\n")
|
|
96
|
+
out.append(f"- **Stable Finding ID:** `{f_id}`")
|
|
97
|
+
out.append(f"- **Root-Cause Cluster:** `{cluster_id}`")
|
|
98
|
+
out.append(f"- **Severity:** {severity} | **Priority:** {priority}")
|
|
99
|
+
out.append(f"- **Confidence:** {conf_score}/100 ({conf_band})")
|
|
100
|
+
out.append(f"- **Location:** `{file_path}:{start_line}-{end_line}`\n")
|
|
101
|
+
out.append("#### Evidence")
|
|
102
|
+
out.append("```python")
|
|
103
|
+
out.append(evidence.strip())
|
|
104
|
+
out.append("```\n")
|
|
105
|
+
|
|
106
|
+
out.append("<details><summary><b>🎫 Ticket Payload</b></summary>\n")
|
|
107
|
+
out.append(f"**Issue:** [{rule_id}] {title} in `{file_path}`\n")
|
|
108
|
+
out.append(f"**Severity:** {severity} | **Priority:** {priority}\n")
|
|
109
|
+
out.append(f"**Finding ID:** `{f_id}`\n")
|
|
110
|
+
out.append("</details>\n")
|
|
111
|
+
out.append("---\n")
|
|
112
|
+
|
|
113
|
+
if overflow_findings:
|
|
114
|
+
out.append(f"\n## 📦 Collapsed High-Density Findings ({len(overflow_findings)} additional items)\n")
|
|
115
|
+
out.append("<details><summary><b>Click to expand remaining findings table</b></summary>\n\n")
|
|
116
|
+
out.append("| Finding ID | Rule ID | Title | File Path | Confidence | Cluster |\n")
|
|
117
|
+
out.append("|---|---|---|---|---|---|\n")
|
|
118
|
+
for of in overflow_findings:
|
|
119
|
+
of_id = of.get("finding_id", "")
|
|
120
|
+
of_rule = of.get("rule_id", "")
|
|
121
|
+
of_title = of.get("title", "")
|
|
122
|
+
of_path = of.get("target", {}).get("file_path", "")
|
|
123
|
+
of_conf = f"{of.get('confidence_score', 80)}/100"
|
|
124
|
+
of_cluster = of.get("cluster_id", "")
|
|
125
|
+
out.append(f"| `{of_id}` | `{of_rule}` | {of_title} | `{of_path}` | {of_conf} | `{of_cluster}` |\n")
|
|
126
|
+
out.append("\n</details>\n\n---\n")
|
|
127
|
+
|
|
128
|
+
return "\n".join(out)
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def render_remediation(bundles: List[RemediationBundle]) -> str:
|
|
132
|
+
out = ["# TorusGuard v6 Remediation Bundles\n"]
|
|
133
|
+
for b in bundles:
|
|
134
|
+
out.append(f"## 🛠️ Bundle: `{b.bundle_id}` — {b.title}\n")
|
|
135
|
+
out.append(f"- **Target Finding:** `{b.finding_id}` (`{b.rule_id}`)")
|
|
136
|
+
if b.cluster_id:
|
|
137
|
+
out.append(f"- **Cluster:** `{b.cluster_id}`")
|
|
138
|
+
out.append(f"- **Target Files:** {', '.join(f'`{tf}`' for tf in b.target_files)}\n")
|
|
139
|
+
out.append("### What Is Wrong")
|
|
140
|
+
out.append(f"{b.what_is_wrong}\n")
|
|
141
|
+
out.append("### What Should Change")
|
|
142
|
+
out.append(f"{b.what_should_change}\n")
|
|
143
|
+
out.append("### Proposed Minimal Diff")
|
|
144
|
+
out.append("```diff")
|
|
145
|
+
out.append(b.proposed_diff.strip())
|
|
146
|
+
out.append("```\n")
|
|
147
|
+
out.append("### Verification After Change")
|
|
148
|
+
out.append(f"{b.verification_steps}\n")
|
|
149
|
+
out.append("---\n")
|
|
150
|
+
|
|
151
|
+
return "\n".join(out)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def render_apply_plan(decisions: List[Tuple[str, PatchPolicyDecision]]) -> str:
|
|
155
|
+
out = ["# TorusGuard v6 Minimal Patch Governance & Apply Plan\n"]
|
|
156
|
+
out.append("| Finding / Patch | Files | Added | Deleted | Escalation | Auto-Apply Allowed | Policy Notes |")
|
|
157
|
+
out.append("|---|---:|---:|---:|:---:|:---:|---|")
|
|
158
|
+
for f_id, d in decisions:
|
|
159
|
+
escalation = "⚠️ Yes" if d.escalation_required else "No"
|
|
160
|
+
allowed = "✅ Permitted" if d.allowed_auto_apply else "❌ Blocked (Manual Review)"
|
|
161
|
+
notes = "; ".join(d.rejection_reasons or d.risk_factors or ["Compliant"])
|
|
162
|
+
out.append(
|
|
163
|
+
f"| `{f_id}` | {d.files_touched} | +{d.line_additions} | -{d.line_deletions} | {escalation} | {allowed} | {notes} |"
|
|
164
|
+
)
|
|
165
|
+
out.append("\n")
|
|
166
|
+
return "\n".join(out)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def render_recheck(results: List[TargetedRecheckResult]) -> str:
|
|
170
|
+
out = ["# TorusGuard v6 Targeted Recheck Report\n"]
|
|
171
|
+
out.append("| Finding ID | Rule ID | Target File | Outcome | Regressions | Details |")
|
|
172
|
+
out.append("|---|---|---|---|---|---|")
|
|
173
|
+
for r in results:
|
|
174
|
+
outcome_badge = f"**{r.outcome.value}**"
|
|
175
|
+
reg = "None" if not r.regressions_detected else ", ".join(r.regressions_detected)
|
|
176
|
+
out.append(
|
|
177
|
+
f"| `{r.finding_id}` | `{r.rule_id}` | `{r.target_file}` | {outcome_badge} | {reg} | {r.explanation} |"
|
|
178
|
+
)
|
|
179
|
+
out.append("\n")
|
|
180
|
+
return "\n".join(out)
|