torusguard 2.0.0-alpha → 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 (227) hide show
  1. package/.torusguard/.manifest.json +94 -30
  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/rules/container/TG-CONT-001-root-user-execution.md +50 -0
  44. package/.torusguard/rules/container/TG-CONT-002-docker-socket-mount.md +47 -0
  45. package/.torusguard/rules/container/TG-CONT-003-privileged-container-mode.md +53 -0
  46. package/.torusguard/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -0
  47. package/.torusguard/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -0
  48. package/.torusguard/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -0
  49. package/.torusguard/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -0
  50. package/.torusguard/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -0
  51. package/.torusguard/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -0
  52. package/.torusguard/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -0
  53. package/.torusguard/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -0
  54. package/.torusguard/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -0
  55. package/.torusguard/rules_catalog.json +96 -0
  56. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  57. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  58. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  59. package/.torusguard/scripts/__pycache__/rules_sync.cpython-314.pyc +0 -0
  60. package/.torusguard/scripts/audit_runner.py +108 -10
  61. package/.torusguard/scripts/finding_scorer.py +43 -13
  62. package/.torusguard/scripts/manifest_builder.py +1 -1
  63. package/.torusguard/scripts/skill_profiler.py +26 -0
  64. package/.torusguard/skills/torusguard/SKILL.md +74 -25
  65. package/.torusguard/skills/torusguard/bootstrap.py +57 -24
  66. package/.torusguard/skills/torusguard-ai-guard/SKILL.md +95 -0
  67. package/.torusguard/skills/torusguard-apply/SKILL.md +60 -34
  68. package/.torusguard/skills/torusguard-audit/SKILL.md +131 -57
  69. package/.torusguard/skills/torusguard-authorize/SKILL.md +48 -6
  70. package/.torusguard/skills/torusguard-container/SKILL.md +94 -0
  71. package/.torusguard/skills/torusguard-exploit-check/SKILL.md +50 -6
  72. package/.torusguard/skills/torusguard-full/SKILL.md +62 -19
  73. package/.torusguard/skills/torusguard-git-mine/SKILL.md +92 -0
  74. package/.torusguard/skills/torusguard-harden/SKILL.md +81 -50
  75. package/.torusguard/skills/torusguard-init/SKILL.md +61 -14
  76. package/.torusguard/skills/torusguard-ocr-scan/SKILL.md +94 -0
  77. package/.torusguard/skills/torusguard-recheck/SKILL.md +71 -18
  78. package/.torusguard/skills/torusguard-redos/SKILL.md +91 -0
  79. package/.torusguard/skills/torusguard-report/SKILL.md +50 -9
  80. package/.torusguard/skills/torusguard-status/SKILL.md +63 -10
  81. package/.torusguard/skills/torusguard-verify/SKILL.md +52 -10
  82. package/.torusguard/skills/torusguard-web-validate/SKILL.md +53 -8
  83. package/.torusguard/workflows/ai-guard.md +31 -0
  84. package/.torusguard/workflows/apply.md +32 -55
  85. package/.torusguard/workflows/audit.md +35 -49
  86. package/.torusguard/workflows/authorize.md +27 -50
  87. package/.torusguard/workflows/container.md +29 -0
  88. package/.torusguard/workflows/exploit-check.md +28 -50
  89. package/.torusguard/workflows/git-mine.md +25 -0
  90. package/.torusguard/workflows/harden.md +29 -48
  91. package/.torusguard/workflows/init.md +27 -50
  92. package/.torusguard/workflows/memory.md +18 -23
  93. package/.torusguard/workflows/ocr-scan.md +25 -0
  94. package/.torusguard/workflows/recheck.md +28 -46
  95. package/.torusguard/workflows/redos.md +27 -0
  96. package/.torusguard/workflows/report.md +33 -52
  97. package/.torusguard/workflows/status.md +31 -52
  98. package/.torusguard/workflows/verify.md +29 -49
  99. package/.torusguard/workflows/web-validate.md +22 -45
  100. package/README.md +96 -60
  101. package/package.json +7 -2
  102. package/skills/torusguard/SKILL.md +75 -24
  103. package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
  104. package/skills/torusguard/bootstrap.py +60 -71
  105. package/skills/torusguard/payload/.manifest.json +94 -31
  106. package/skills/torusguard/payload/core/__init__.py +146 -0
  107. package/skills/torusguard/payload/core/agent_roles.py +104 -0
  108. package/skills/torusguard/payload/core/ast_walker.py +283 -0
  109. package/skills/torusguard/payload/core/authorization.py +218 -0
  110. package/skills/torusguard/payload/core/browser_verifier.py +128 -0
  111. package/skills/torusguard/payload/core/bundle.py +141 -0
  112. package/skills/torusguard/payload/core/call_graph.py +184 -0
  113. package/skills/torusguard/payload/core/clustering.py +275 -0
  114. package/skills/torusguard/payload/core/confidence.py +120 -0
  115. package/skills/torusguard/payload/core/cross_file_taint.py +101 -0
  116. package/skills/torusguard/payload/core/exploit_checker.py +317 -0
  117. package/skills/torusguard/payload/core/formatter.py +351 -0
  118. package/skills/torusguard/payload/core/governance.py +210 -0
  119. package/skills/torusguard/payload/core/identity.py +104 -0
  120. package/skills/torusguard/payload/core/import_resolver.py +91 -0
  121. package/skills/torusguard/payload/core/incremental.py +102 -0
  122. package/skills/torusguard/payload/core/lifecycle.py +137 -0
  123. package/skills/torusguard/payload/core/models.py +425 -0
  124. package/skills/torusguard/payload/core/parallel.py +56 -0
  125. package/skills/torusguard/payload/core/parser.py +202 -0
  126. package/skills/torusguard/payload/core/rechecker.py +107 -0
  127. package/skills/torusguard/payload/core/replay_trace.py +178 -0
  128. package/skills/torusguard/payload/core/rules_registry.py +131 -0
  129. package/skills/torusguard/payload/core/run_folder.py +60 -0
  130. package/skills/torusguard/payload/core/run_manager.py +163 -0
  131. package/skills/torusguard/payload/core/runtime_evidence.py +175 -0
  132. package/skills/torusguard/payload/core/runtime_validator.py +246 -0
  133. package/skills/torusguard/payload/core/safety_gate.py +139 -0
  134. package/skills/torusguard/payload/core/sarif.py +189 -0
  135. package/skills/torusguard/payload/core/stack_profiler.py +184 -0
  136. package/skills/torusguard/payload/core/symbol_table.py +91 -0
  137. package/skills/torusguard/payload/core/taint.py +133 -0
  138. package/skills/torusguard/payload/core/taint_graph.py +235 -0
  139. package/skills/torusguard/payload/core/taint_rules.py +268 -0
  140. package/skills/torusguard/payload/core/v070_reporter.py +102 -0
  141. package/skills/torusguard/payload/core/v070_workflow.py +339 -0
  142. package/skills/torusguard/payload/core/v6_reporter.py +180 -0
  143. package/skills/torusguard/payload/core/v6_workflow.py +221 -0
  144. package/skills/torusguard/payload/core/watcher.py +58 -0
  145. package/skills/torusguard/payload/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
  146. package/skills/torusguard/payload/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
  147. package/skills/torusguard/payload/rules/container/TG-CONT-001-root-user-execution.md +50 -0
  148. package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -0
  149. package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -0
  150. package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -0
  151. package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -0
  152. package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -0
  153. package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -0
  154. package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -0
  155. package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -0
  156. package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -0
  157. package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -0
  158. package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -0
  159. package/skills/torusguard/payload/rules_catalog.json +338 -518
  160. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  161. package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
  162. package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
  163. package/skills/torusguard/payload/scripts/manifest_builder.py +1 -1
  164. package/skills/torusguard/payload/skills/torusguard/SKILL.md +74 -25
  165. package/skills/torusguard/payload/skills/torusguard/bootstrap.py +57 -24
  166. package/skills/torusguard/payload/skills/torusguard/references/csharp-security.md +41 -41
  167. package/skills/torusguard/payload/skills/torusguard/references/go-security.md +41 -41
  168. package/skills/torusguard/payload/skills/torusguard/references/java-security.md +40 -40
  169. package/skills/torusguard/payload/skills/torusguard/references/polyglot-security-matrix.md +25 -25
  170. package/skills/torusguard/payload/skills/torusguard/references/rust-security.md +40 -40
  171. package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -0
  172. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +60 -34
  173. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +131 -57
  174. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +48 -6
  175. package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -0
  176. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +50 -6
  177. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +62 -19
  178. package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -0
  179. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +81 -50
  180. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +61 -14
  181. package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -0
  182. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +71 -18
  183. package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -0
  184. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +50 -9
  185. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +63 -10
  186. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +52 -10
  187. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +53 -8
  188. package/skills/torusguard/payload/workflows/ai-guard.md +31 -0
  189. package/skills/torusguard/payload/workflows/apply.md +31 -62
  190. package/skills/torusguard/payload/workflows/audit.md +35 -55
  191. package/skills/torusguard/payload/workflows/authorize.md +27 -50
  192. package/skills/torusguard/payload/workflows/container.md +29 -0
  193. package/skills/torusguard/payload/workflows/exploit-check.md +28 -50
  194. package/skills/torusguard/payload/workflows/git-mine.md +25 -0
  195. package/skills/torusguard/payload/workflows/harden.md +28 -52
  196. package/skills/torusguard/payload/workflows/init.md +27 -56
  197. package/skills/torusguard/payload/workflows/memory.md +18 -23
  198. package/skills/torusguard/payload/workflows/ocr-scan.md +25 -0
  199. package/skills/torusguard/payload/workflows/recheck.md +28 -46
  200. package/skills/torusguard/payload/workflows/redos.md +27 -0
  201. package/skills/torusguard/payload/workflows/report.md +39 -62
  202. package/skills/torusguard/payload/workflows/status.md +31 -55
  203. package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
  204. package/skills/torusguard/payload/workflows/verify.md +29 -49
  205. package/skills/torusguard/payload/workflows/web-validate.md +22 -45
  206. package/skills/torusguard/references/csharp-security.md +41 -0
  207. package/skills/torusguard/references/go-security.md +41 -0
  208. package/skills/torusguard/references/java-security.md +40 -0
  209. package/skills/torusguard/references/polyglot-security-matrix.md +25 -0
  210. package/skills/torusguard/references/rust-security.md +40 -0
  211. package/skills/torusguard-ai-guard/SKILL.md +95 -0
  212. package/skills/torusguard-apply/SKILL.md +60 -34
  213. package/skills/torusguard-audit/SKILL.md +130 -57
  214. package/skills/torusguard-authorize/SKILL.md +48 -6
  215. package/skills/torusguard-container/SKILL.md +94 -0
  216. package/skills/torusguard-exploit-check/SKILL.md +50 -6
  217. package/skills/torusguard-full/SKILL.md +62 -19
  218. package/skills/torusguard-git-mine/SKILL.md +92 -0
  219. package/skills/torusguard-harden/SKILL.md +81 -50
  220. package/skills/torusguard-init/SKILL.md +61 -14
  221. package/skills/torusguard-ocr-scan/SKILL.md +94 -0
  222. package/skills/torusguard-recheck/SKILL.md +71 -18
  223. package/skills/torusguard-redos/SKILL.md +91 -0
  224. package/skills/torusguard-report/SKILL.md +50 -9
  225. package/skills/torusguard-status/SKILL.md +63 -10
  226. package/skills/torusguard-verify/SKILL.md +52 -10
  227. package/skills/torusguard-web-validate/SKILL.md +53 -8
@@ -0,0 +1,141 @@
1
+ """
2
+ TorusGuard v6 Remediation Bundle Generator
3
+ Constructs self-contained, structured remediation packages per cluster or finding containing:
4
+ - finding.md
5
+ - remediation.md
6
+ - minimal_patch_plan.md
7
+ - verify-after-change.md
8
+ - metadata.json
9
+ """
10
+
11
+ import json
12
+ from pathlib import Path
13
+ from dataclasses import dataclass, field, asdict
14
+ from typing import Dict, Any, List, Optional
15
+
16
+
17
+ @dataclass
18
+ class RemediationBundle:
19
+ bundle_id: str
20
+ finding_id: str
21
+ rule_id: str
22
+ title: str
23
+ target_files: List[str]
24
+ risk_severity: str
25
+ what_is_wrong: str
26
+ why_it_matters: str
27
+ what_should_change: str
28
+ proposed_diff: str
29
+ verification_steps: str
30
+ cluster_id: Optional[str] = None
31
+ created_at: str = ""
32
+
33
+ def write_to_directory(self, base_dir: Path) -> Path:
34
+ bundle_dir = base_dir / self.bundle_id
35
+ bundle_dir.mkdir(parents=True, exist_ok=True)
36
+
37
+ # 1. finding.md
38
+ with open(bundle_dir / "finding.md", "w", encoding="utf-8") as f:
39
+ f.write(f"# Finding: {self.title}\n\n")
40
+ f.write(f"- **Finding ID:** `{self.finding_id}`\n")
41
+ f.write(f"- **Rule ID:** `{self.rule_id}`\n")
42
+ f.write(f"- **Severity:** {self.risk_severity}\n")
43
+ if self.cluster_id:
44
+ f.write(f"- **Root-Cause Cluster:** `{self.cluster_id}`\n")
45
+ f.write(f"- **Target Files:** {', '.join(f'`{tf}`' for tf in self.target_files)}\n\n")
46
+ f.write("## What Is Wrong\n")
47
+ f.write(f"{self.what_is_wrong}\n\n")
48
+ f.write("## Why It Matters\n")
49
+ f.write(f"{self.why_it_matters}\n")
50
+
51
+ # 2. remediation.md
52
+ with open(bundle_dir / "remediation.md", "w", encoding="utf-8") as f:
53
+ f.write(f"# Remediation Guide: {self.title}\n\n")
54
+ f.write("## What Should Change\n")
55
+ f.write(f"{self.what_should_change}\n\n")
56
+ f.write("## Target Files to Modify\n")
57
+ for tf in self.target_files:
58
+ f.write(f"- `{tf}`\n")
59
+
60
+ # 3. minimal_patch_plan.md
61
+ with open(bundle_dir / "minimal_patch_plan.md", "w", encoding="utf-8") as f:
62
+ f.write(f"# Minimal Patch Plan for `{self.finding_id}`\n\n")
63
+ f.write("```diff\n")
64
+ f.write(self.proposed_diff.strip() + "\n")
65
+ f.write("```\n")
66
+
67
+ # 4. verify-after-change.md
68
+ with open(bundle_dir / "verify-after-change.md", "w", encoding="utf-8") as f:
69
+ f.write(f"# Verification Steps for `{self.finding_id}`\n\n")
70
+ f.write(f"{self.verification_steps}\n\n")
71
+ f.write("### Recheck Command\n")
72
+ f.write("```bash\n/torusguard recheck\n```\n")
73
+
74
+ # 5. metadata.json
75
+ meta = {
76
+ "bundle_id": self.bundle_id,
77
+ "finding_id": self.finding_id,
78
+ "rule_id": self.rule_id,
79
+ "title": self.title,
80
+ "cluster_id": self.cluster_id,
81
+ "target_files": self.target_files,
82
+ "risk_severity": self.risk_severity,
83
+ "created_at": self.created_at,
84
+ }
85
+ with open(bundle_dir / "metadata.json", "w", encoding="utf-8") as f:
86
+ json.dump(meta, f, indent=2)
87
+
88
+ return bundle_dir
89
+
90
+
91
+ class BundleManager:
92
+ """
93
+ Factory for generating remediation bundles from findings and clusters.
94
+ """
95
+
96
+ @staticmethod
97
+ def create_bundle(finding: Dict[str, Any], cluster_id: Optional[str] = None) -> RemediationBundle:
98
+ f_id = finding.get("finding_id", "fnd-01")
99
+ rule_id = finding.get("rule_id", "TG-GENERIC")
100
+ title = finding.get("title", "Security Finding")
101
+ target = finding.get("target", {})
102
+ file_path = target.get("file_path", "app.py")
103
+ severity = finding.get("severity", "High")
104
+
105
+ bundle_id = f"bundle-{f_id}"
106
+
107
+ what_is_wrong = finding.get(
108
+ "what_is_wrong",
109
+ f"The component in `{file_path}` violates security rule `{rule_id}`."
110
+ )
111
+ why_it_matters = finding.get(
112
+ "why_it_matters",
113
+ "This vulnerability could allow attackers to bypass authorization, access unauthorized tenant data, or inject malicious payloads."
114
+ )
115
+ what_should_change = finding.get(
116
+ "what_should_change",
117
+ f"Apply least-invasive framework-idiomatic hardening to `{file_path}`."
118
+ )
119
+ proposed_diff = finding.get(
120
+ "proposed_diff",
121
+ f"--- a/{file_path}\n+++ b/{file_path}\n@@ -1,3 +1,3 @@\n-# Unsafe pattern\n+# Hardened pattern\n"
122
+ )
123
+ verification_steps = finding.get(
124
+ "verification_steps",
125
+ f"Run `/torusguard recheck` on `{file_path}` to confirm vulnerability resolution."
126
+ )
127
+
128
+ return RemediationBundle(
129
+ bundle_id=bundle_id,
130
+ finding_id=f_id,
131
+ rule_id=rule_id,
132
+ title=title,
133
+ target_files=[file_path],
134
+ risk_severity=severity,
135
+ what_is_wrong=what_is_wrong,
136
+ why_it_matters=why_it_matters,
137
+ what_should_change=what_should_change,
138
+ proposed_diff=proposed_diff,
139
+ verification_steps=verification_steps,
140
+ cluster_id=cluster_id,
141
+ )
@@ -0,0 +1,184 @@
1
+ """
2
+ TorusGuard Project-Wide Call Graph Engine
3
+ Maps interprocedural call sites, function definitions, and caller/callee relationships
4
+ across module boundaries.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from dataclasses import dataclass, field, asdict
9
+ from typing import Dict, List, Optional, Set, Any
10
+ import re
11
+
12
+ from core.parser import PolyglotParser, ParseResult
13
+ from core.import_resolver import ImportResolver
14
+
15
+
16
+ @dataclass
17
+ class CallSite:
18
+ caller_file: str
19
+ caller_func: str
20
+ caller_line: int
21
+ callee_name: str
22
+ callee_file: Optional[str] = None
23
+ callee_line: Optional[int] = None
24
+ arguments: List[str] = field(default_factory=list)
25
+
26
+ def to_dict(self) -> Dict[str, Any]:
27
+ return asdict(self)
28
+
29
+
30
+ @dataclass
31
+ class FunctionDef:
32
+ file_path: str
33
+ func_name: str
34
+ start_line: int
35
+ end_line: int = 0
36
+ parameters: List[str] = field(default_factory=list)
37
+ return_expressions: List[str] = field(default_factory=list)
38
+
39
+ def to_dict(self) -> Dict[str, Any]:
40
+ return asdict(self)
41
+
42
+
43
+ class CallGraph:
44
+ """Project-wide interprocedural function call graph."""
45
+
46
+ def __init__(self, project_root: Path):
47
+ self.project_root = project_root.resolve()
48
+ self.import_resolver = ImportResolver(self.project_root)
49
+ # Key: (file_path, func_name) -> FunctionDef
50
+ self.functions: Dict[Tuple[str, str], FunctionDef] = {}
51
+ # Key: func_name -> list of FunctionDef (for quick symbol lookup)
52
+ self.functions_by_name: Dict[str, List[FunctionDef]] = {}
53
+ # Key: (file_path, func_name) -> list of CallSite where this func is caller
54
+ self.callees: Dict[Tuple[str, str], List[CallSite]] = {}
55
+ # Key: (file_path, func_name) -> list of CallSite where this func is callee
56
+ self.callers: Dict[Tuple[str, str], List[CallSite]] = {}
57
+
58
+ def register_function(self, fdef: FunctionDef) -> None:
59
+ key = (fdef.file_path, fdef.func_name)
60
+ self.functions[key] = fdef
61
+ if fdef.func_name not in self.functions_by_name:
62
+ self.functions_by_name[fdef.func_name] = []
63
+ self.functions_by_name[fdef.func_name].append(fdef)
64
+
65
+ def add_call(self, call: CallSite) -> None:
66
+ caller_key = (call.caller_file, call.caller_func)
67
+ if caller_key not in self.callees:
68
+ self.callees[caller_key] = []
69
+ self.callees[caller_key].append(call)
70
+
71
+ if call.callee_file:
72
+ callee_key = (call.callee_file, call.callee_name)
73
+ if callee_key not in self.callers:
74
+ self.callers[callee_key] = []
75
+ self.callers[callee_key].append(call)
76
+
77
+ def build_from_files(self, file_paths: List[Path], parser: PolyglotParser) -> None:
78
+ """Parse files and construct functions and call edges."""
79
+ parsed_results: Dict[str, ParseResult] = {}
80
+
81
+ # Pass 1: Extract all function definitions
82
+ for p in file_paths:
83
+ try:
84
+ res = parser.parse_file(p)
85
+ rel_p = str(p.resolve().relative_to(self.project_root)).replace("\\", "/")
86
+ parsed_results[rel_p] = res
87
+ self._extract_function_defs(rel_p, res.raw_content, res.language)
88
+ except Exception:
89
+ continue
90
+
91
+ # Pass 2: Extract call sites and link to callees
92
+ for rel_p, res in parsed_results.items():
93
+ abs_p = self.project_root / rel_p
94
+ # Build import map: alias/name -> resolved Path
95
+ resolved_imports: Dict[str, str] = {}
96
+ for imp in res.imports:
97
+ target_p = self.import_resolver.resolve(abs_p, imp.module)
98
+ if target_p:
99
+ rel_target = str(target_p.resolve().relative_to(self.project_root)).replace("\\", "/")
100
+ for name in imp.imported_names:
101
+ resolved_imports[name] = rel_target
102
+ for alias, orig in imp.alias_map.items():
103
+ resolved_imports[alias] = rel_target
104
+
105
+ # Process function calls
106
+ for fc in res.function_calls:
107
+ callee_name = fc.name
108
+ callee_file = None
109
+ callee_line = None
110
+
111
+ # Method call e.g. mod.func
112
+ if "." in callee_name:
113
+ prefix, actual_name = callee_name.split(".", 1)
114
+ if prefix in resolved_imports:
115
+ callee_file = resolved_imports[prefix]
116
+ callee_name = actual_name
117
+ elif callee_name in resolved_imports:
118
+ callee_file = resolved_imports[callee_name]
119
+ else:
120
+ # Check local in same file
121
+ if (rel_p, callee_name) in self.functions:
122
+ callee_file = rel_p
123
+
124
+ # Find which enclosing function contains this call
125
+ caller_func = self._find_enclosing_function(rel_p, fc.line_number)
126
+
127
+ call_site = CallSite(
128
+ caller_file=rel_p,
129
+ caller_func=caller_func,
130
+ caller_line=fc.line_number,
131
+ callee_name=callee_name,
132
+ callee_file=callee_file,
133
+ arguments=fc.arguments
134
+ )
135
+ self.add_call(call_site)
136
+
137
+ def _extract_function_defs(self, file_path: str, content: str, language: str) -> None:
138
+ lines = content.splitlines()
139
+ for idx, line in enumerate(lines):
140
+ line_num = idx + 1
141
+ stripped = line.strip()
142
+
143
+ # Python: def foo(a, b):
144
+ py_m = re.match(r"^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*?)\)", stripped)
145
+ if py_m:
146
+ fname = py_m.group(1)
147
+ params = [p.strip() for p in py_m.group(2).split(",") if p.strip()]
148
+ self.register_function(FunctionDef(file_path=file_path, func_name=fname, start_line=line_num, parameters=params))
149
+ continue
150
+
151
+ # JS/TS: function foo(a, b) or const foo = (a, b) =>
152
+ js_m = re.match(r"^(?:async\s+)?function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*?)\)", stripped)
153
+ if js_m:
154
+ fname = js_m.group(1)
155
+ params = [p.strip() for p in js_m.group(2).split(",") if p.strip()]
156
+ self.register_function(FunctionDef(file_path=file_path, func_name=fname, start_line=line_num, parameters=params))
157
+ continue
158
+
159
+ arrow_m = re.match(r"^(?:const|let|var)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(?:async\s*)?\((.*?)\)\s*=>", stripped)
160
+ if arrow_m:
161
+ fname = arrow_m.group(1)
162
+ params = [p.strip() for p in arrow_m.group(2).split(",") if p.strip()]
163
+ self.register_function(FunctionDef(file_path=file_path, func_name=fname, start_line=line_num, parameters=params))
164
+ continue
165
+
166
+ # Go: func foo(a string)
167
+ go_m = re.match(r"^func\s+(?:\(.*?\)\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*?)\)", stripped)
168
+ if go_m:
169
+ fname = go_m.group(1)
170
+ params = [p.strip() for p in go_m.group(2).split(",") if p.strip()]
171
+ self.register_function(FunctionDef(file_path=file_path, func_name=fname, start_line=line_num, parameters=params))
172
+
173
+ def _find_enclosing_function(self, file_path: str, line_num: int) -> str:
174
+ candidates = [f for (fp, fn), f in self.functions.items() if fp == file_path and f.start_line <= line_num]
175
+ if not candidates:
176
+ return "<global>"
177
+ candidates.sort(key=lambda f: f.start_line, reverse=True)
178
+ return candidates[0].func_name
179
+
180
+ def get_callers(self, file_path: str, func_name: str) -> List[CallSite]:
181
+ return self.callers.get((file_path, func_name), [])
182
+
183
+ def get_callees(self, file_path: str, func_name: str) -> List[CallSite]:
184
+ return self.callees.get((file_path, func_name), [])
@@ -0,0 +1,275 @@
1
+ """
2
+ TorusGuard v6 Root-Cause Clustering Engine
3
+ Groups individual static-analysis findings into cohesive root-cause clusters
4
+ so engineering teams can remediate systemic architectural issues rather than chasing isolated alerts.
5
+ """
6
+
7
+ from dataclasses import dataclass, field, asdict
8
+ from typing import List, Dict, Any, Optional
9
+ import hashlib
10
+
11
+
12
+ # Canonical Cluster Taxonomy Definitions
13
+ KNOWN_ROOT_CAUSES = {
14
+ "TG-DB-004": {
15
+ "cluster_id": "cluster-tenant-isolation",
16
+ "title": "Missing Multi-Tenant Query Scoping & Model Isolation",
17
+ "shared_remediation_path": "Implement tenant-aware BaseManager / default queryset filtering or tenancy middleware context.",
18
+ "shared_verification_plan": "Execute tenant cross-boundary query assertions and recheck query builders."
19
+ },
20
+ "TG-INPUT-006": {
21
+ "cluster_id": "cluster-path-traversal",
22
+ "title": "Unsafe File Upload Storage & Path Traversal Boundaries",
23
+ "shared_remediation_path": "Sanitize filenames using secure_filename() and enforce safe directory resolution with Path.resolve().",
24
+ "shared_verification_plan": "Run path traversal payload test suite and verify storage isolation."
25
+ },
26
+ "TG-INPUT-005": {
27
+ "cluster_id": "cluster-template-escaping",
28
+ "title": "Disabled Template Autoescaping & Unsafe HTML Rendering",
29
+ "shared_remediation_path": "Remove explicit mark_safe() / |safe filters and use autoescaped context variables.",
30
+ "shared_verification_plan": "Execute XSS payload injection test against rendered view outputs."
31
+ },
32
+ "TG-AUTH-008": {
33
+ "cluster_id": "cluster-header-trust",
34
+ "title": "Untrusted Client Header Trust & Role/Tenant Injection",
35
+ "shared_remediation_path": "Derive user identity and role scopes exclusively from cryptographically signed session tokens or trusted gateways.",
36
+ "shared_verification_plan": "Send spoofed client headers (X-User-Role, X-Tenant-ID) and assert rejection."
37
+ },
38
+ "TG-AUTH-007": {
39
+ "cluster_id": "cluster-idor-scoping",
40
+ "title": "Insecure Direct Object Reference (IDOR) on Primary Keys",
41
+ "shared_remediation_path": "Scope database queries with user_id or account ownership filters before returning model instances.",
42
+ "shared_verification_plan": "Run IDOR authorization matrix tests across test accounts."
43
+ },
44
+ "TG-RATE-001": {
45
+ "cluster_id": "cluster-rate-limiting",
46
+ "title": "Unbounded Resource Consumption & Missing Endpoint Throttling",
47
+ "shared_remediation_path": "Apply Redis/in-memory rate limiting middleware or DRF Throttling classes.",
48
+ "shared_verification_plan": "Execute burst traffic simulation and verify 429 Too Many Requests response."
49
+ },
50
+ "TG-SSRF-001": {
51
+ "cluster_id": "cluster-ssrf-network",
52
+ "title": "Unvalidated Outbound HTTP Requests & Network Boundary Leakage",
53
+ "shared_remediation_path": "Validate destination URLs against strict allowlists and block internal IP ranges (127.0.0.1, 169.254.169.254).",
54
+ "shared_verification_plan": "Attempt outbound requests to loopback and link-local metadata endpoints."
55
+ },
56
+ "TG-WEBHOOK-001": {
57
+ "cluster_id": "cluster-webhook-auth",
58
+ "title": "Unverified Inbound Webhook Signatures & Replay Vulnerability",
59
+ "shared_remediation_path": "Verify HMAC signatures using timing-safe comparisons and enforce timestamp freshness bounds.",
60
+ "shared_verification_plan": "Send unsigned and replay webhook payloads and confirm 401/403 rejection."
61
+ },
62
+ "TG-SEC-001": {
63
+ "cluster_id": "cluster-secrets",
64
+ "title": "Hardcoded Secrets & Sensitive Environment Configuration Exposure",
65
+ "shared_remediation_path": "Extract secrets into environment variables (.env / secrets manager) and exclude from version control.",
66
+ "shared_verification_plan": "Audit git history and scan source files for credential patterns."
67
+ },
68
+ "TG-AGENT-001": {
69
+ "cluster_id": "cluster-prompt-injection",
70
+ "title": "AI Agent User Prompt Concatenation & System Prompt Override",
71
+ "shared_remediation_path": "Isolate untrusted user input within explicit inert XML delimiters or structured user-role message arrays.",
72
+ "shared_verification_plan": "Execute prompt injection canary payloads and verify refusal to override system directives."
73
+ },
74
+ "TG-CSRF-001": {
75
+ "cluster_id": "cluster-csrf-missing",
76
+ "title": "Cross-Site Request Forgery (CSRF) & Missing SameSite Cookie Protection",
77
+ "shared_remediation_path": "Enforce SameSite=Lax/Strict on session cookies and validate cryptographic anti-CSRF tokens on state mutations.",
78
+ "shared_verification_plan": "Attempt cross-origin state-changing POST requests without CSRF token."
79
+ },
80
+ "TG-GQL-001": {
81
+ "cluster_id": "cluster-graphql-abuse",
82
+ "title": "Unbounded GraphQL Query Depth & Production Introspection Exposure",
83
+ "shared_remediation_path": "Configure GraphQL query depth limiting (max depth 6) and disable schema introspection in production.",
84
+ "shared_verification_plan": "Send deeply nested recursive query and introspection requests to production endpoint."
85
+ },
86
+ "TG-SUPPLY-001": {
87
+ "cluster_id": "cluster-supply-chain",
88
+ "title": "Supply Chain Vulnerability & Unpinned CI/CD Action Hashes",
89
+ "shared_remediation_path": "Pin GitHub Actions to full immutable commit SHAs and enforce lockfile integrity verification in CI.",
90
+ "shared_verification_plan": "Scan workflow files for mutable tag references and verify lockfile checksums."
91
+ },
92
+ "TG-BIZ-001": {
93
+ "cluster_id": "cluster-business-logic",
94
+ "title": "Business Logic Invariant Violation & Negative Quantity/Discount Bypass",
95
+ "shared_remediation_path": "Assert non-negative amount invariants, enforce transactional locks, and cap discounts server-side.",
96
+ "shared_verification_plan": "Submit negative amount and coupon boundary payloads in transactional API endpoints."
97
+ },
98
+ "TG-CACHE-001": {
99
+ "cluster_id": "cluster-cache-poisoning",
100
+ "title": "Web Cache Poisoning & Missing Cache-Control on Sensitive Responses",
101
+ "shared_remediation_path": "Sanitize unkeyed HTTP request headers and apply 'Cache-Control: no-store' on private or authenticated responses.",
102
+ "shared_verification_plan": "Send unkeyed headers (X-Forwarded-Host) and inspect cached responses."
103
+ },
104
+ "TG-WS-001": {
105
+ "cluster_id": "cluster-websocket-auth",
106
+ "title": "Missing WebSocket Handshake Authentication & Origin Verification",
107
+ "shared_remediation_path": "Authenticate client credentials during WebSocket upgrade and reject connections with unwhitelisted Origin headers.",
108
+ "shared_verification_plan": "Connect from untrusted origin and assert handshake rejection with 403 Forbidden."
109
+ },
110
+ "TG-EDGE-001": {
111
+ "cluster_id": "cluster-edge-abuse",
112
+ "title": "Serverless Subrequest Fan-Out Explosion & Missing Timeout Bounds",
113
+ "shared_remediation_path": "Enforce concurrency caps on edge subrequests and configure strict execution timeouts (<= 15s).",
114
+ "shared_verification_plan": "Simulate high subrequest fan-out and assert bounded concurrency."
115
+ },
116
+ "TG-CONT-001": {
117
+ "cluster_id": "cluster-container-hardening",
118
+ "title": "Container Security Misconfiguration & Root User Execution",
119
+ "shared_remediation_path": "Define non-root USER in Dockerfile, avoid mounting /var/run/docker.sock, and disallow privileged mode.",
120
+ "shared_verification_plan": "Inspect container image metadata and assert non-root UID."
121
+ },
122
+ "TG-GIT-001": {
123
+ "cluster_id": "cluster-git-secret-mining",
124
+ "title": "Committed Credentials & Leaked Tokens in Git Commit History",
125
+ "shared_remediation_path": "Rotate exposed keys immediately, purge secret commits using git-filter-repo, and install pre-commit secret hooks.",
126
+ "shared_verification_plan": "Mine git commit logs for high-entropy tokens and regex matches."
127
+ },
128
+ "TG-REDOS-001": {
129
+ "cluster_id": "cluster-regex-backtracking",
130
+ "title": "Regular Expression Denial of Service (ReDoS) via Nested Quantifiers",
131
+ "shared_remediation_path": "Refactor regex patterns to eliminate nested quantifiers (e.g. (a+)+) or enforce execution timeouts.",
132
+ "shared_verification_plan": "Execute polynomial/exponential adversarial string payloads against regex engine."
133
+ },
134
+ "TG-RAG-001": {
135
+ "cluster_id": "cluster-rag-tenant-leak",
136
+ "title": "RAG Pipeline Vector Database Multi-Tenant Isolation Leakage",
137
+ "shared_remediation_path": "Always include tenant_id or user_id in vector similarity filter metadata (e.g. filter={'tenant_id': tid}).",
138
+ "shared_verification_plan": "Query vector database with tenant A context requesting tenant B documents."
139
+ },
140
+ "TG-INPUT-007": {
141
+ "cluster_id": "cluster-open-redirect",
142
+ "title": "Unvalidated URL Redirection (Open Redirect)",
143
+ "shared_remediation_path": "Validate destination URLs against an allowlist of trusted domains and restrict to relative paths.",
144
+ "shared_verification_plan": "Test redirect endpoints with external and protocol-relative URLs."
145
+ },
146
+ "TG-INPUT-008": {
147
+ "cluster_id": "cluster-deserialization",
148
+ "title": "Insecure Object Deserialization via Untrusted Streams",
149
+ "shared_remediation_path": "Avoid pickle/yaml.load with untrusted input; use safe serialization formats (JSON) or SafeLoader.",
150
+ "shared_verification_plan": "Submit serialized gadget payload and confirm safe parser rejection."
151
+ }
152
+ }
153
+
154
+
155
+
156
+ @dataclass
157
+ class RootCauseCluster:
158
+ cluster_id: str
159
+ title: str
160
+ primary_rule: str
161
+ affected_files: List[str] = field(default_factory=list)
162
+ affected_locations: List[str] = field(default_factory=list)
163
+ finding_ids: List[str] = field(default_factory=list)
164
+ shared_remediation_path: str = ""
165
+ shared_verification_plan: str = ""
166
+ risk_severity: str = "High"
167
+ hotspot_module: str = ""
168
+ is_high_density: bool = False
169
+
170
+ def add_finding(self, finding_id: str, file_path: str, location_str: str, severity: str = "High"):
171
+ if finding_id not in self.finding_ids:
172
+ self.finding_ids.append(finding_id)
173
+ if file_path not in self.affected_files:
174
+ self.affected_files.append(file_path)
175
+ if location_str not in self.affected_locations:
176
+ self.affected_locations.append(location_str)
177
+
178
+ # Update hotspot module (top directory)
179
+ parts = file_path.replace("\\", "/").split("/")
180
+ if len(parts) > 1:
181
+ self.hotspot_module = "/".join(parts[:2])
182
+ else:
183
+ self.hotspot_module = parts[0]
184
+
185
+ # Density threshold (> 5 findings in one cluster is high density)
186
+ if len(self.finding_ids) >= 5:
187
+ self.is_high_density = True
188
+
189
+ # Escalate cluster severity if higher
190
+ severity_order = {"Critical": 4, "High": 3, "Medium": 2, "Low": 1, "Informational": 0}
191
+ if severity_order.get(severity, 0) > severity_order.get(self.risk_severity, 0):
192
+ self.risk_severity = severity
193
+
194
+ def to_dict(self) -> Dict[str, Any]:
195
+ return asdict(self)
196
+
197
+
198
+ # Generated / Vendor File Exclusion Patterns
199
+ IGNORED_PATTERNS = [
200
+ "migrations/", "node_modules/", "dist/", "build/", "vendor/",
201
+ ".venv/", "venv/", ".min.js", ".min.css", ".pb.go", "_pb2.py",
202
+ "bundle.js", ".map"
203
+ ]
204
+
205
+
206
+ def is_generated_file(file_path: str) -> bool:
207
+ norm = file_path.replace("\\", "/").lower()
208
+ return any(p in norm for p in IGNORED_PATTERNS)
209
+
210
+
211
+ class ClusteringEngine:
212
+ """
213
+ Analyzes findings and groups them into root-cause clusters with scale and density metrics.
214
+ """
215
+
216
+ @staticmethod
217
+ def cluster_findings(
218
+ findings: List[Dict[str, Any]],
219
+ filter_generated: bool = False
220
+ ) -> List[RootCauseCluster]:
221
+ clusters: Dict[str, RootCauseCluster] = {}
222
+
223
+ for f in findings:
224
+ target = f.get("target", {})
225
+ file_path = target.get("file_path", "unknown")
226
+
227
+ if filter_generated and is_generated_file(file_path):
228
+ continue
229
+
230
+ rule_id = f.get("rule_id", "TG-GENERIC")
231
+ finding_id = f.get("finding_id", "unknown")
232
+ start_line = target.get("line_start", 0)
233
+ end_line = target.get("line_end", 0)
234
+ loc_str = f"{file_path}:{start_line}-{end_line}"
235
+ severity = f.get("severity", "High")
236
+
237
+ # Determine cluster mapping
238
+ if rule_id in KNOWN_ROOT_CAUSES:
239
+ meta = KNOWN_ROOT_CAUSES[rule_id]
240
+ cid = meta["cluster_id"]
241
+ title = meta["title"]
242
+ rem_path = meta["shared_remediation_path"]
243
+ ver_plan = meta["shared_verification_plan"]
244
+ else:
245
+ cid = f"cluster-{rule_id.lower().replace('tg-', '')}"
246
+ title = f"Systemic {f.get('title', rule_id)} Issues"
247
+ rem_path = "Apply framework-native security controls as documented in rule reference."
248
+ ver_plan = "Re-audit all affected components with /torusguard recheck."
249
+
250
+ if cid not in clusters:
251
+ clusters[cid] = RootCauseCluster(
252
+ cluster_id=cid,
253
+ title=title,
254
+ primary_rule=rule_id,
255
+ shared_remediation_path=rem_path,
256
+ shared_verification_plan=ver_plan,
257
+ risk_severity=severity,
258
+ )
259
+
260
+ clusters[cid].add_finding(
261
+ finding_id=finding_id,
262
+ file_path=file_path,
263
+ location_str=loc_str,
264
+ severity=severity,
265
+ )
266
+
267
+ # Sort clusters by severity and finding count
268
+ severity_order = {"Critical": 4, "High": 3, "Medium": 2, "Low": 1, "Informational": 0}
269
+ sorted_clusters = sorted(
270
+ clusters.values(),
271
+ key=lambda c: (severity_order.get(c.risk_severity, 0), len(c.finding_ids)),
272
+ reverse=True
273
+ )
274
+
275
+ return sorted_clusters