torusguard 2.1.0 → 2.1.1

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.
Files changed (133) hide show
  1. package/.torusguard/.manifest.json +47 -5
  2. package/.torusguard/core/__init__.py +146 -0
  3. package/.torusguard/core/agent_roles.py +104 -0
  4. package/.torusguard/core/ast_walker.py +283 -0
  5. package/.torusguard/core/authorization.py +218 -0
  6. package/.torusguard/core/browser_verifier.py +128 -0
  7. package/.torusguard/core/bundle.py +141 -0
  8. package/.torusguard/core/call_graph.py +184 -0
  9. package/.torusguard/core/clustering.py +275 -0
  10. package/.torusguard/core/confidence.py +120 -0
  11. package/.torusguard/core/cross_file_taint.py +101 -0
  12. package/.torusguard/core/exploit_checker.py +317 -0
  13. package/.torusguard/core/formatter.py +351 -0
  14. package/.torusguard/core/governance.py +210 -0
  15. package/.torusguard/core/identity.py +104 -0
  16. package/.torusguard/core/import_resolver.py +91 -0
  17. package/.torusguard/core/incremental.py +102 -0
  18. package/.torusguard/core/lifecycle.py +137 -0
  19. package/.torusguard/core/models.py +425 -0
  20. package/.torusguard/core/parallel.py +56 -0
  21. package/.torusguard/core/parser.py +202 -0
  22. package/.torusguard/core/rechecker.py +107 -0
  23. package/.torusguard/core/replay_trace.py +178 -0
  24. package/.torusguard/core/rules_registry.py +131 -0
  25. package/.torusguard/core/run_folder.py +60 -0
  26. package/.torusguard/core/run_manager.py +163 -0
  27. package/.torusguard/core/runtime_evidence.py +175 -0
  28. package/.torusguard/core/runtime_validator.py +246 -0
  29. package/.torusguard/core/safety_gate.py +139 -0
  30. package/.torusguard/core/sarif.py +189 -0
  31. package/.torusguard/core/stack_profiler.py +184 -0
  32. package/.torusguard/core/symbol_table.py +91 -0
  33. package/.torusguard/core/taint.py +133 -0
  34. package/.torusguard/core/taint_graph.py +235 -0
  35. package/.torusguard/core/taint_rules.py +268 -0
  36. package/.torusguard/core/v070_reporter.py +102 -0
  37. package/.torusguard/core/v070_workflow.py +339 -0
  38. package/.torusguard/core/v6_reporter.py +180 -0
  39. package/.torusguard/core/v6_workflow.py +221 -0
  40. package/.torusguard/core/watcher.py +58 -0
  41. package/.torusguard/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
  42. package/.torusguard/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
  43. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  44. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  45. package/.torusguard/scripts/__pycache__/rules_sync.cpython-314.pyc +0 -0
  46. package/.torusguard/scripts/audit_runner.py +108 -10
  47. package/.torusguard/scripts/finding_scorer.py +43 -13
  48. package/.torusguard/scripts/skill_profiler.py +26 -0
  49. package/.torusguard/skills/torusguard/SKILL.md +6 -2
  50. package/.torusguard/skills/torusguard-audit/SKILL.md +109 -84
  51. package/.torusguard/workflows/audit.md +21 -17
  52. package/README.md +19 -11
  53. package/package.json +7 -2
  54. package/skills/torusguard/SKILL.md +6 -2
  55. package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
  56. package/skills/torusguard/bootstrap.py +3 -3
  57. package/skills/torusguard/payload/.manifest.json +48 -7
  58. package/skills/torusguard/payload/core/__init__.py +146 -0
  59. package/skills/torusguard/payload/core/agent_roles.py +104 -0
  60. package/skills/torusguard/payload/core/ast_walker.py +283 -0
  61. package/skills/torusguard/payload/core/authorization.py +218 -0
  62. package/skills/torusguard/payload/core/browser_verifier.py +128 -0
  63. package/skills/torusguard/payload/core/bundle.py +141 -0
  64. package/skills/torusguard/payload/core/call_graph.py +184 -0
  65. package/skills/torusguard/payload/core/clustering.py +275 -0
  66. package/skills/torusguard/payload/core/confidence.py +120 -0
  67. package/skills/torusguard/payload/core/cross_file_taint.py +101 -0
  68. package/skills/torusguard/payload/core/exploit_checker.py +317 -0
  69. package/skills/torusguard/payload/core/formatter.py +351 -0
  70. package/skills/torusguard/payload/core/governance.py +210 -0
  71. package/skills/torusguard/payload/core/identity.py +104 -0
  72. package/skills/torusguard/payload/core/import_resolver.py +91 -0
  73. package/skills/torusguard/payload/core/incremental.py +102 -0
  74. package/skills/torusguard/payload/core/lifecycle.py +137 -0
  75. package/skills/torusguard/payload/core/models.py +425 -0
  76. package/skills/torusguard/payload/core/parallel.py +56 -0
  77. package/skills/torusguard/payload/core/parser.py +202 -0
  78. package/skills/torusguard/payload/core/rechecker.py +107 -0
  79. package/skills/torusguard/payload/core/replay_trace.py +178 -0
  80. package/skills/torusguard/payload/core/rules_registry.py +131 -0
  81. package/skills/torusguard/payload/core/run_folder.py +60 -0
  82. package/skills/torusguard/payload/core/run_manager.py +163 -0
  83. package/skills/torusguard/payload/core/runtime_evidence.py +175 -0
  84. package/skills/torusguard/payload/core/runtime_validator.py +246 -0
  85. package/skills/torusguard/payload/core/safety_gate.py +139 -0
  86. package/skills/torusguard/payload/core/sarif.py +189 -0
  87. package/skills/torusguard/payload/core/stack_profiler.py +184 -0
  88. package/skills/torusguard/payload/core/symbol_table.py +91 -0
  89. package/skills/torusguard/payload/core/taint.py +133 -0
  90. package/skills/torusguard/payload/core/taint_graph.py +235 -0
  91. package/skills/torusguard/payload/core/taint_rules.py +268 -0
  92. package/skills/torusguard/payload/core/v070_reporter.py +102 -0
  93. package/skills/torusguard/payload/core/v070_workflow.py +339 -0
  94. package/skills/torusguard/payload/core/v6_reporter.py +180 -0
  95. package/skills/torusguard/payload/core/v6_workflow.py +221 -0
  96. package/skills/torusguard/payload/core/watcher.py +58 -0
  97. package/skills/torusguard/payload/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
  98. package/skills/torusguard/payload/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
  99. package/skills/torusguard/payload/rules/container/TG-CONT-001-root-user-execution.md +50 -50
  100. package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -47
  101. package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -53
  102. package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -43
  103. package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -44
  104. package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -41
  105. package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -40
  106. package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -72
  107. package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -51
  108. package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -51
  109. package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -46
  110. package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -43
  111. package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
  112. package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
  113. package/skills/torusguard/payload/skills/torusguard/SKILL.md +6 -2
  114. package/skills/torusguard/payload/skills/torusguard/bootstrap.py +3 -3
  115. package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -95
  116. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +109 -84
  117. package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -94
  118. package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -92
  119. package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -94
  120. package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -91
  121. package/skills/torusguard/payload/workflows/ai-guard.md +31 -31
  122. package/skills/torusguard/payload/workflows/audit.md +21 -17
  123. package/skills/torusguard/payload/workflows/container.md +29 -29
  124. package/skills/torusguard/payload/workflows/git-mine.md +25 -25
  125. package/skills/torusguard/payload/workflows/ocr-scan.md +25 -25
  126. package/skills/torusguard/payload/workflows/redos.md +27 -27
  127. package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
  128. package/skills/torusguard/references/csharp-security.md +41 -41
  129. package/skills/torusguard/references/go-security.md +41 -41
  130. package/skills/torusguard/references/java-security.md +40 -40
  131. package/skills/torusguard/references/polyglot-security-matrix.md +25 -25
  132. package/skills/torusguard/references/rust-security.md +40 -40
  133. package/skills/torusguard-audit/SKILL.md +107 -83
@@ -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)