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
@@ -42,8 +42,16 @@ def get_ist_now() -> datetime.datetime:
42
42
 
43
43
  # ─── UI Formatter Bridge ──────────────────────────────────────────────────────
44
44
  scripts_dir = Path(__file__).resolve().parent
45
+ tg_root = Path(__file__).resolve().parent.parent
46
+ project_root = Path(__file__).resolve().parent.parent.parent
45
47
  if str(scripts_dir) not in sys.path:
46
48
  sys.path.insert(0, str(scripts_dir))
49
+ if str(tg_root) not in sys.path:
50
+ sys.path.insert(0, str(tg_root))
51
+ if str(project_root) not in sys.path:
52
+ sys.path.insert(0, str(project_root))
53
+
54
+
47
55
 
48
56
  try:
49
57
  import term_ui as tui
@@ -1240,9 +1248,10 @@ def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
1240
1248
  return findings
1241
1249
 
1242
1250
 
1243
- def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path) -> Tuple[List[Dict[str, Any]], Dict[str, List[Dict[str, Any]]]]:
1251
+ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path, taint_paths: Optional[List[Any]] = None) -> Tuple[List[Dict[str, Any]], Dict[str, List[Dict[str, Any]]]]:
1244
1252
  """
1245
- Score each finding using finding_scorer.py and persistent memory patterns.
1253
+ Score each finding using finding_scorer.py, persistent memory patterns,
1254
+ and taint dataflow analysis.
1246
1255
  Returns (scored_findings, clusters_map).
1247
1256
  """
1248
1257
  try:
@@ -1253,6 +1262,15 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1253
1262
  scored = []
1254
1263
  clusters: Dict[str, List[Dict[str, Any]]] = {}
1255
1264
 
1265
+ # Map taint paths by (file_path, line_number)
1266
+ taint_by_loc: Dict[Tuple[str, int], Any] = {}
1267
+ if taint_paths:
1268
+ for tp in taint_paths:
1269
+ sink_node = getattr(tp, "sink", None)
1270
+ if sink_node:
1271
+ norm_p = getattr(sink_node, "file_path", "").replace("\\", "/")
1272
+ taint_by_loc[(norm_p, getattr(sink_node, "line_number", 0))] = tp
1273
+
1256
1274
  # Phase 1d: Pre-compute cross-file corroboration bonus
1257
1275
  # If multiple rule families flag the same file, each finding gets +5 confidence
1258
1276
  file_rule_families: Dict[str, set] = {}
@@ -1267,6 +1285,14 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1267
1285
  band = "High Confidence"
1268
1286
  factors = {}
1269
1287
 
1288
+ # Check for correlated taint path
1289
+ matching_tp = taint_by_loc.get((f["file_path"], f["line_number"]))
1290
+ is_taint_confirmed = matching_tp is not None
1291
+ taint_depth = getattr(matching_tp, "depth", None) if matching_tp else None
1292
+ is_sanitized = getattr(matching_tp, "is_sanitized", False) if matching_tp else False
1293
+ if matching_tp and hasattr(matching_tp, "to_dict"):
1294
+ f["taint_path"] = matching_tp.to_dict()
1295
+
1270
1296
  if finding_scorer:
1271
1297
  try:
1272
1298
  # Phase 1d: Dynamic evidence quality based on rule precision
@@ -1291,7 +1317,11 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1291
1317
  manual_review_status=mr,
1292
1318
  rule_id=f["rule_id"],
1293
1319
  file_path=f["file_path"],
1294
- root_dir=target_root
1320
+ root_dir=target_root,
1321
+ taint_path_confirmed=is_taint_confirmed,
1322
+ taint_depth=taint_depth,
1323
+ sanitizer_present=is_sanitized,
1324
+ rule_severity=f.get("severity", "High")
1295
1325
  )
1296
1326
  score = s
1297
1327
  band = b
@@ -1315,6 +1345,7 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1315
1345
  return scored, clusters
1316
1346
 
1317
1347
 
1348
+
1318
1349
  def emit_run_artifacts(run_folder: Path, scored_findings: List[Dict[str, Any]], clusters: Dict[str, List[Dict[str, Any]]], target_root: Path) -> None:
1319
1350
  """Generate findings.json, findings.md, and summary.md into run folder."""
1320
1351
  now_ist = get_ist_now()
@@ -1501,7 +1532,14 @@ def run_watch_mode(target_root: Path, severity_floor: str = "medium", json_outpu
1501
1532
  print(f"\n {YELLOW}🛑 Watch mode stopped.{RESET}\n")
1502
1533
 
1503
1534
 
1504
- def execute_audit(target_root: Path, severity_floor: str = "medium", json_output: bool = False, include_tests: bool = False) -> Dict[str, Any]:
1535
+ def execute_audit(
1536
+ target_root: Path,
1537
+ severity_floor: str = "medium",
1538
+ json_output: bool = False,
1539
+ include_tests: bool = False,
1540
+ incremental: bool = False,
1541
+ use_taint: bool = True
1542
+ ) -> Dict[str, Any]:
1505
1543
  """Execute the full TorusGuard static security audit."""
1506
1544
  start_time = time.perf_counter()
1507
1545
  target_root = target_root.resolve()
@@ -1522,14 +1560,64 @@ def execute_audit(target_root: Path, severity_floor: str = "medium", json_output
1522
1560
  except Exception:
1523
1561
  pass
1524
1562
 
1525
- # 2. Collect files & scan
1563
+ # 2. Collect files to scan
1526
1564
  files = find_files_to_scan(target_root, include_tests=include_tests)
1527
1565
  all_findings = []
1528
- for f in files:
1529
- all_findings.extend(scan_file(f, target_root))
1530
1566
 
1531
- # 3. Score & Cluster
1532
- scored_findings, clusters = score_and_cluster_findings(all_findings, target_root)
1567
+ inc_scanner = None
1568
+ files_to_scan = files
1569
+ unchanged_files = []
1570
+
1571
+ if incremental:
1572
+ try:
1573
+ from core.incremental import IncrementalScanner
1574
+ inc_scanner = IncrementalScanner(target_root)
1575
+ files_to_scan, unchanged_files = inc_scanner.get_changed_files(files)
1576
+ # Rehydrate findings for unchanged files
1577
+ for uf in unchanged_files:
1578
+ all_findings.extend(inc_scanner.get_cached_findings(uf))
1579
+ except Exception:
1580
+ files_to_scan = files
1581
+ unchanged_files = []
1582
+
1583
+ # Parallel or sequential scan on files_to_scan
1584
+ new_findings = []
1585
+ try:
1586
+ from core.parallel import ParallelAuditExecutor
1587
+ executor = ParallelAuditExecutor()
1588
+ new_findings = executor.scan_files_parallel(files_to_scan, lambda f: scan_file(f, target_root))
1589
+ except Exception:
1590
+ for f in files_to_scan:
1591
+ new_findings.extend(scan_file(f, target_root))
1592
+
1593
+ all_findings.extend(new_findings)
1594
+
1595
+ # Update cache if incremental scanner is active
1596
+ if inc_scanner:
1597
+ # Group new findings by file
1598
+ file_to_findings: Dict[Path, List[Dict[str, Any]]] = {f: [] for f in files_to_scan}
1599
+ for nf in new_findings:
1600
+ raw_fp = nf.get("file_path", "")
1601
+ target_f = target_root / raw_fp
1602
+ if target_f in file_to_findings:
1603
+ file_to_findings[target_f].append(nf)
1604
+ for target_f, f_list in file_to_findings.items():
1605
+ inc_scanner.update_file_cache(target_f, f_list)
1606
+ inc_scanner.save_cache()
1607
+
1608
+ # 2.5 Taint Dataflow Analysis (if enabled)
1609
+ taint_paths = []
1610
+ if use_taint:
1611
+ try:
1612
+ from core.cross_file_taint import CrossFileTaintAnalyzer
1613
+ analyzer = CrossFileTaintAnalyzer(target_root)
1614
+ # Analyze target files (capped to 200 files for high responsiveness)
1615
+ taint_paths = analyzer.analyze_project(files[:200])
1616
+ except Exception:
1617
+ taint_paths = []
1618
+
1619
+ # 3. Score & Cluster with Taint Evidence
1620
+ scored_findings, clusters = score_and_cluster_findings(all_findings, target_root, taint_paths=taint_paths)
1533
1621
 
1534
1622
  # 4. Allocate run folder
1535
1623
  runs_dir = target_root / ".torusguard" / "runs"
@@ -1595,6 +1683,8 @@ def main():
1595
1683
  parser.add_argument("--scope", "-s", help="Alternative path to target project")
1596
1684
  parser.add_argument("--severity", choices=["critical", "high", "medium", "low"], default="medium", help="Severity floor")
1597
1685
  parser.add_argument("--watch", "-w", action="store_true", help="Continuous watch mode: re-scan on file save")
1686
+ parser.add_argument("--incremental", "-i", action="store_true", help="Incremental mode: only scan modified files")
1687
+ parser.add_argument("--no-taint", action="store_true", help="Disable taint-aware dataflow analysis")
1598
1688
  parser.add_argument("--sarif", action="store_true", help="Automatically export findings to OASIS SARIF v2.1.0")
1599
1689
  parser.add_argument("--sarif-out", help="Output file path for SARIF export")
1600
1690
  parser.add_argument("--json", action="store_true", help="Output raw JSON")
@@ -1607,10 +1697,18 @@ def main():
1607
1697
  run_watch_mode(target, severity_floor=args.severity, json_output=args.json, include_tests=args.include_tests, sarif=args.sarif, sarif_out=args.sarif_out)
1608
1698
  sys.exit(0)
1609
1699
 
1610
- res = execute_audit(target, severity_floor=args.severity, json_output=args.json, include_tests=args.include_tests)
1700
+ res = execute_audit(
1701
+ target,
1702
+ severity_floor=args.severity,
1703
+ json_output=args.json,
1704
+ include_tests=args.include_tests,
1705
+ incremental=args.incremental,
1706
+ use_taint=(not args.no_taint)
1707
+ )
1611
1708
  if args.sarif:
1612
1709
  export_sarif(target, res.get("run_folder"), args.sarif_out)
1613
1710
 
1711
+
1614
1712
  sys.exit(0 if res["critical_count"] == 0 else 1)
1615
1713
 
1616
1714
 
@@ -11,9 +11,14 @@ import argparse
11
11
  from pathlib import Path
12
12
  from typing import Dict, Any, Tuple, Optional
13
13
 
14
+ # Ensure .torusguard directory is in sys.path for core imports
15
+ _TG_DIR = Path(__file__).resolve().parent.parent
16
+ if str(_TG_DIR) not in sys.path:
17
+ sys.path.insert(0, str(_TG_DIR))
18
+
14
19
 
15
20
  def compute_memory_boost(
16
- rule_id: str,
21
+ rule_id: Optional[str] = None,
17
22
  file_path: Optional[str] = None,
18
23
  root_dir: Optional[Path] = None
19
24
  ) -> int:
@@ -112,21 +117,35 @@ def compute_confidence_score(
112
117
  memory_boost: int = 0,
113
118
  rule_id: Optional[str] = None,
114
119
  file_path: Optional[str] = None,
115
- root_dir: Optional[Path] = None
120
+ root_dir: Optional[Path] = None,
121
+ taint_path_confirmed: bool = False,
122
+ taint_depth: Optional[int] = None,
123
+ sanitizer_present: bool = False,
124
+ rule_severity: str = "High",
125
+ use_evidence_chain: bool = False,
126
+ **kwargs
116
127
  ) -> Tuple[int, str, Dict[str, Any]]:
117
128
  """
118
129
  Computes total score and assigns confidence band.
119
- Max points:
120
- - evidence_quality: 35
121
- - reproduction_success: 25
122
- - independent_confirmations: 15
123
- - environmental_clarity: 15
124
- - manual_review_status: 10
125
- - memory_boost: -30 to +20 (modifier from persistent memory)
126
- - test_deduction: -30 if file is located in a test/mock path
127
- - doc_deduction: -25 if file is located in documentation
128
- Total is clamped to [0, 100].
130
+ Supports classical factor evaluation and multi-signal evidence-chain calibration.
129
131
  """
132
+ if use_evidence_chain:
133
+ try:
134
+ from core.confidence import ConfidenceCalibrator, EvidenceSignals
135
+ signals = EvidenceSignals(
136
+ rule_severity=rule_severity,
137
+ taint_path_confirmed=taint_path_confirmed,
138
+ taint_depth=taint_depth,
139
+ sanitizer_present=sanitizer_present,
140
+ framework_context_match=True,
141
+ has_multiline_evidence=(evidence_quality >= 30),
142
+ is_test_or_mock=is_test_path(file_path),
143
+ memory_boost=memory_boost or (compute_memory_boost(rule_id, file_path=file_path, root_dir=root_dir) if rule_id else 0)
144
+ )
145
+ return ConfidenceCalibrator.calculate_score(signals)
146
+ except Exception:
147
+ pass
148
+
130
149
  eq = min(max(evidence_quality, 0), 35)
131
150
  rs = min(max(reproduction_success, 0), 25)
132
151
  ic = min(max(independent_confirmations, 0), 15)
@@ -138,6 +157,13 @@ def compute_confidence_score(
138
157
  if rule_id and eff_mem_boost == 0:
139
158
  eff_mem_boost = compute_memory_boost(rule_id, file_path=file_path, root_dir=root_dir)
140
159
 
160
+ # Taint path adjustments
161
+ taint_mod = 0
162
+ if taint_path_confirmed:
163
+ taint_mod += 15
164
+ if sanitizer_present:
165
+ taint_mod -= 35
166
+
141
167
  # Test and Doc path noise suppression
142
168
  is_test = is_test_path(file_path)
143
169
  test_deduction = -30 if is_test else 0
@@ -145,7 +171,7 @@ def compute_confidence_score(
145
171
  is_doc = is_doc_path(file_path)
146
172
  doc_deduction = -25 if is_doc else 0
147
173
 
148
- raw_total = eq + rs + ic + ec + mr + eff_mem_boost + test_deduction + doc_deduction
174
+ raw_total = eq + rs + ic + ec + mr + eff_mem_boost + taint_mod + test_deduction + doc_deduction
149
175
  total = min(max(raw_total, 0), 100)
150
176
 
151
177
  if total >= 90:
@@ -164,6 +190,9 @@ def compute_confidence_score(
164
190
  "environmental_clarity": ec,
165
191
  "manual_review_status": mr,
166
192
  "memory_boost": eff_mem_boost,
193
+ "taint_path_confirmed": taint_path_confirmed,
194
+ "taint_depth": taint_depth,
195
+ "sanitizer_present": sanitizer_present,
167
196
  "test_exemption": is_test,
168
197
  "test_deduction": test_deduction,
169
198
  "total_score": total,
@@ -172,6 +201,7 @@ def compute_confidence_score(
172
201
  return total, band, factors
173
202
 
174
203
 
204
+
175
205
  def main():
176
206
  parser = argparse.ArgumentParser(description="TorusGuard Confidence Scorer")
177
207
  parser.add_argument("--dir", type=str, help="Target project root directory to scan and score")
@@ -39,7 +39,7 @@ def scan_workspace_files(base_dir):
39
39
  dirs.remove("__pycache__")
40
40
 
41
41
  for f in sorted(files):
42
- if f in [".manifest.json", ".gitkeep"] or f.endswith(".pyc"):
42
+ if f in [".manifest.json", ".gitkeep", "auth.json"] or f.endswith(".pyc"):
43
43
  continue
44
44
  full_path = Path(root) / f
45
45
  rel_path = full_path.relative_to(base_dir).as_posix()
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: torusguard
3
- description: Universal autonomous security engine: 74 canonical rules across 18 families, polyglot stack detection across 16+ languages, Ponytail remediation bounds (<=35 add, <=25 del), standardized 75-column terminal UI, SARIF v2.1.0 exports, and persistent security memory context.
4
- version: 1.3.6
3
+ description: Universal autonomous security engine: 88 canonical rules across 22 families, polyglot stack detection across 16+ languages, Ponytail remediation bounds (<=35 add, <=25 del), standardized 75-column terminal UI, SARIF v2.1.0 exports, and persistent security memory context.
4
+ version: 2.1.1
5
5
  ---
6
6
 
7
7
  # TorusGuard Master Security Engine & Command Router
@@ -10,38 +10,54 @@ version: 1.3.6
10
10
 
11
11
  ---
12
12
 
13
- ## Command Catalog: Terminal CLI & AI Chat Parity
13
+ ## Tri-Mode Execution & Command Catalog
14
14
 
15
- TorusGuard operates with 100% feature parity across both the terminal CLI and AI chat slash commands:
15
+ TorusGuard operates with 100% feature parity across the compiled terminal CLI, AI chat slash commands, and Model Context Protocol (MCP) tools:
16
16
 
17
- | Capability | Terminal CLI Command | AI Chat Slash Command | Specialist Skill | Purpose |
17
+ | Capability | Terminal CLI Command | AI Chat Slash Command | Specialist Skill | Governed Purpose |
18
18
  | :--- | :--- | :--- | :--- | :--- |
19
- | **Init** | `npx torusguard init` | `/torusguard init` | `skills/torusguard-init` | Stack discovery, rule activation, workspace scaffolding |
20
- | **Status** | `npx torusguard status` | `/torusguard status` | `skills/torusguard-status` | Posture score, active rules, run history, scope check |
21
- | **Audit** | `npx torusguard audit` | `/torusguard audit` | `skills/torusguard-audit` | Static AST scan, invariant fingerprinting, confidence scoring |
22
- | **Verify** | `npx torusguard verify` | `/torusguard verify` | `skills/torusguard-verify` | Evidence sufficiency audit & line match calibration |
23
- | **Harden** | `npx torusguard harden` | `/torusguard harden` | `skills/torusguard-harden` | Ponytail patch formulation ($\le 35$ add, $\le 25$ del) |
24
- | **Apply** | `npx torusguard apply [--yes]` | `/torusguard apply` | `skills/torusguard-apply` | Human Gate review, `.bak` snapshots, patch application |
25
- | **Rollback** | `npx torusguard rollback` | `/torusguard rollback` | `skills/torusguard-apply` | Instant 1-step rollback from pre-apply snapshots |
26
- | **Recheck** | `npx torusguard recheck` | `/torusguard recheck` | `skills/torusguard-recheck` | Targeted differential AST scan, fix closure verification |
27
- | **Recipes** | `npx torusguard recipes` | `/torusguard recipes` | `skills/torusguard-harden` | Explore verified Golden Fix Recipes from persistent memory |
28
- | **Report** | `npx torusguard report --html` | `/torusguard report` | `skills/torusguard-report` | Executive posture reporting, SARIF v2.1.0 & visual HTML |
29
- | **Authorize** | `npx torusguard authorize` | `/torusguard authorize` | `skills/torusguard-authorize`| Legal scope definition & safety boundaries |
30
- | **Validate** | `npx torusguard web-validate` | `/torusguard web-validate` | `skills/torusguard-web-validate`| Authorized non-destructive HTTP probing |
31
- | **Exploit** | `npx torusguard exploit-check` | `/torusguard exploit-check`| `skills/torusguard-exploit-check`| Bounded single-step exploitability confirmation |
32
- | **Full** | `npx torusguard full` | `/torusguard full` | `skills/torusguard-full` | End-to-end 7-stage closed-loop execution |
33
- | **Sync** | `npx torusguard rules sync` | `/torusguard rules sync` | `skills/torusguard-init` | Synchronize rules across AI editors (Cursor, Claude, Antigravity) |
19
+ | **Init** | `torusguard init` | `/torusguard init` | `.torusguard/skills/torusguard-init` | Stack discovery, rule activation, workspace scaffolding |
20
+ | **Status** | `torusguard status` | `/torusguard status` | `.torusguard/skills/torusguard-status` | Posture score, active rules, run history, scope check |
21
+ | **Audit** | `torusguard audit` | `/torusguard audit` | `.torusguard/skills/torusguard-audit` | Static AST scan, invariant fingerprinting, confidence scoring |
22
+ | **OCR Vision**| `torusguard ocr-scan <path>` | `/torusguard ocr-scan` | `.torusguard/skills/torusguard-ocr-scan` | Tesseract OCR secret scan on images/diagrams |
23
+ | **Verify** | `torusguard verify` | `/torusguard verify` | `.torusguard/skills/torusguard-verify` | Evidence sufficiency audit & line match calibration |
24
+ | **Harden** | `torusguard harden <patch>` | `/torusguard harden` | `.torusguard/skills/torusguard-harden` | Ponytail patch formulation ($\le 35$ add, $\le 25$ del) & reflection validation |
25
+ | **Apply** | `torusguard apply [--yes]` | `/torusguard apply` | `.torusguard/skills/torusguard-apply` | Human Gate review, `.bak` snapshots, patch application |
26
+ | **Rollback** | `torusguard rollback` | `/torusguard rollback` | `.torusguard/skills/torusguard-apply` | Instant 1-step rollback from pre-apply snapshots |
27
+ | **Recheck** | `torusguard recheck` | `/torusguard recheck` | `.torusguard/skills/torusguard-recheck` | Targeted differential AST scan, fix closure verification |
28
+ | **Recipes** | `torusguard recipes` | `/torusguard recipes` | `.torusguard/skills/torusguard-harden` | Explore verified Golden Fix Recipes from persistent memory |
29
+ | **Report** | `torusguard report --html` | `/torusguard report` | `.torusguard/skills/torusguard-report` | Executive posture reporting, SARIF v2.1.0 & visual HTML |
30
+ | **Authorize** | `torusguard authorize` | `/torusguard authorize` | `.torusguard/skills/torusguard-authorize`| Legal scope definition & safety boundaries |
31
+ | **Validate** | `torusguard web-validate` | `/torusguard web-validate` | `.torusguard/skills/torusguard-web-validate`| Authorized non-destructive HTTP probing |
32
+ | **Exploit** | `torusguard exploit-check` | `/torusguard exploit-check`| `.torusguard/skills/torusguard-exploit-check`| Bounded single-step exploitability confirmation |
33
+ | **Container** | `torusguard container` | `/torusguard container` | `.torusguard/skills/torusguard-container` | Audits Dockerfile & Compose for root users, sockets, privileged mode |
34
+ | **Git Mine** | `torusguard git-mine` | `/torusguard git-mine` | `.torusguard/skills/torusguard-git-mine` | Mines git commit history & config for leaked credentials & tokens |
35
+ | **ReDoS** | `torusguard redos` | `/torusguard redos` | `.torusguard/skills/torusguard-redos` | Analyzes regex patterns for catastrophic exponential backtracking |
36
+ | **AI Guard** | `torusguard ai-guard` | `/torusguard ai-guard` | `.torusguard/skills/torusguard-ai-guard` | Audits AI agents & RAG pipelines for prompt injection & tenant leaks |
37
+ | **Full** | `torusguard full` | `/torusguard full` | `.torusguard/skills/torusguard-full` | End-to-end 7-stage closed-loop execution |
38
+ | **MCP Server**| `torusguard mcp` | — | Native Stdio JSON-RPC 2.0 | Serves native Model Context Protocol tools to AI coding agents |
39
+ | **Update** | `torusguard update` | `/torusguard update` | `.torusguard/skills/torusguard-init` | Self-update TorusGuard engine binary |
40
+ | **Help** | `torusguard help` | `/torusguard help` | — | Interactive command guide |
34
41
 
35
42
  ---
36
43
 
37
44
  ## Non-Negotiable Invariants
38
45
 
39
- 1. **Browser-Code Truth:** If the client receives it, it is public. Zero hardcoded secrets (`SUPABASE_SERVICE_ROLE_KEY`, private tokens, live API keys) in frontend code.
40
- 2. **Multi-Tenant Isolation:** All database lookups must be scoped by organization or user ownership (`tenant_id`, `organization_id`, `where: { tenantId }`).
46
+ 1. **Browser-Code Truth:** If the client receives it, it is public. Zero hardcoded secrets (`SUPABASE_SERVICE_ROLE_KEY`, private tokens, live API keys) in frontend code (`TG-CLIENT-001`).
47
+ 2. **Multi-Tenant Isolation:** All database lookups must be scoped by organization or user ownership (`tenant_id`, `organization_id`, `where: { tenantId }`) (`TG-DB-001`).
41
48
  3. **Ponytail Churn Bounds:** Patches must be minimal and surgical ($\le 35$ additions, $\le 25$ deletions). Never perform full-file rewrites.
42
49
  4. **Standardized 75-Column Terminal:** All CLI terminal output is strictly normalized to 75 visual columns with Unicode emoji width calculation, ANSI escape handling, and visual truncation with ellipsis (`...`).
43
- 5. **Zero Security Bypasses:** Never insert `# nosec`, `verify=False`, `InsecureSkipVerify: true`, `[AllowAnonymous]`, or `csrf().disable()`.
50
+ 5. **Zero Security Bypasses:** Never insert `# nosec`, `verify=False`, `InsecureSkipVerify: true`, `[AllowAnonymous]`, or `csrf().disable()` (`TG-DIFF-001`).
44
51
  6. **Snapshots Before Edits:** Every code modification must capture a byte-for-byte pre-apply backup in `.torusguard/snapshots/<run_id>/` before touching disk code.
52
+ 7. **Living Report Ground Truth:** All findings synchronize directly with `security_report.md` at workspace root.
53
+
54
+ ---
55
+
56
+ ## 🏛️ Alibaba OpenCodeReview Hybrid Architecture Integration
57
+ TorusGuard employs the dual-track hybrid architecture battle-tested at Alibaba Group scale:
58
+ 1. **Deterministic Engineering Pipeline (Go CLI Engine):** Mechanical tasks (file traversal, whitespace tolerance, exact AST matching, Ponytail churn bounds calculation, pre-apply snapshot capture, and OCR extraction) are strictly executed by the compiled Go binary.
59
+ 2. **Context Minimization (1/9th Token Strategy):** Agents never dump full files into prompt context. Bounded AST windows ($\pm 3$ lines) are extracted via `ExtractContext` to keep review tokens hyper-dense.
60
+ 3. **Line-Level Reflection:** In remediation, the agent provides semantic intent (`find_snippet` + `replace_snippet`). The Go CLI reflection module pins and verifies line bounds deterministically, preventing line-number drift.
45
61
 
46
62
  ---
47
63
 
@@ -67,9 +83,42 @@ TorusGuard operates with 100% feature parity across both the terminal CLI and AI
67
83
  │ ├── report.html # Single-file visual dark-mode HTML report
68
84
  │ ├── results.sarif # OASIS SARIF v2.1.0 log
69
85
  │ └── bundles/ # Formulated Ponytail remediation bundles
86
+ ├── skills/ # Canonical distribution skills for end-user AI agents
87
+ ├── workflows/ # Operational step-by-step workflow guides
70
88
  └── snapshots/
71
89
  └── run-YYYYMMDD-HHMMSS-audit/ # Pre-apply .bak files for instant rollback
72
90
  ```
73
91
 
74
- ## Living Report Ground Truth
75
- - Read `security_report.md` in the workspace root before taking any action. Update the relevant finding card after completing remediation.
92
+ ---
93
+
94
+ ## 🚨 LLM Trap Table
95
+
96
+ | Pattern | What AI Does Wrong | What Is Actually Correct |
97
+ | :--- | :--- | :--- |
98
+ | **Line-Number Diff Hallucination** | Guesses line numbers in unified diff headers (`@@ -42,5 +42,7 @@`) causing patch rejection. | Use the Line-Level Reflection Module (`SemanticPatch` with `find_snippet` and `replace_snippet`) to let Go resolve exact lines. |
99
+ | **Context Window Bloating** | Dumps entire 1,000-line source files into conversation when diagnosing a single finding. | Use bounded AST context extraction ($\pm 3$ lines) matching OpenCodeReview's 1/9th token efficiency. |
100
+ | **Full-File Rewrites** | Rewrites the entire file or surrounding business logic when fixing a vulnerability. | Strictly conform to Ponytail bounds ($\le 35$ additions, $\le 25$ deletions per bundle). |
101
+ | **Security Bypass Insertion** | Introduces `# nosec`, `verify=False`, or `[AllowAnonymous]` to make tests pass. | Strictly forbidden (`TG-DIFF-001`). Rejections are enforced fail-closed by the Go engine. |
102
+ | **Missing Multi-Tenant Scope** | Modifies queries to filter only by record `id`. | Always scope database lookups by tenant or user ownership (`tenantId: user.tenantId`). |
103
+
104
+ ---
105
+
106
+ ## ✅ Pre-Flight Self-Audit
107
+
108
+ Before producing any security remediation or analysis, verify:
109
+ - [ ] Did I read `security_report.md` at workspace root before proposing changes?
110
+ - [ ] Did I inspect only the bounded AST context window ($\pm 3$ lines) rather than the entire file?
111
+ - [ ] Does my proposed remediation stay strictly within Ponytail bounds ($\le 35$ additions, $\le 25$ deletions)?
112
+ - [ ] Is the fix free of any `# nosec`, `verify=False`, or bypass flags?
113
+ - [ ] Did I verify multi-tenant isolation on all database and model queries?
114
+ - [ ] Can this patch be applied via deterministic reflection (`find_snippet` -> `replace_snippet`)?
115
+
116
+ ---
117
+
118
+ ## 🔁 VBC Protocol (Verify → Build → Confirm)
119
+
120
+ ```
121
+ VERIFY: Inspect living security_report.md and bounded AST context snippet.
122
+ BUILD: Formulate surgical SemanticPatch (find_snippet + replace_snippet within <=35 add / <=25 del).
123
+ CONFIRM: Validate via torusguard harden, apply snapshot, and execute differential torusguard recheck.
124
+ ```
@@ -63,14 +63,15 @@ import unicodedata
63
63
  ANSI_REGEX = re.compile(r'\033\[[0-9;]*m')
64
64
 
65
65
  def get_visual_width(text: str) -> int:
66
- """Calculate the printable display width of a string (ANSI and emoji aware)."""
66
+ """Calculate the printable display width of a string (ANSI, emoji and variation selector aware)."""
67
67
  clean = ANSI_REGEX.sub('', text)
68
68
  width = 0
69
69
  for ch in clean:
70
+ cp = ord(ch)
71
+ if (0xFE00 <= cp <= 0xFE0F) or (0xE0100 <= cp <= 0xE01EF) or cp in (0x200B, 0x200C, 0x200D, 0x00AD):
72
+ continue
70
73
  ea = unicodedata.east_asian_width(ch)
71
- if ea in ('W', 'F'):
72
- width += 2
73
- elif ord(ch) >= 0x1F300:
74
+ if ea in ('W', 'F') or cp >= 0x1F300:
74
75
  width += 2
75
76
  else:
76
77
  width += 1
@@ -82,7 +83,7 @@ def truncate_visual(text: str, max_w: int = 67) -> str:
82
83
  return text
83
84
  out, curr_w, in_ansi, ansi_buf = [], 0, False, ''
84
85
  for ch in text:
85
- if ch == '\033':
86
+ if ch in ('\033', '\x1b'):
86
87
  in_ansi = True
87
88
  ansi_buf = ch
88
89
  continue
@@ -92,13 +93,19 @@ def truncate_visual(text: str, max_w: int = 67) -> str:
92
93
  in_ansi = False
93
94
  out.append(ansi_buf)
94
95
  continue
96
+ cp = ord(ch)
97
+ if (0xFE00 <= cp <= 0xFE0F) or (0xE0100 <= cp <= 0xE01EF) or cp in (0x200B, 0x200C, 0x200D, 0x00AD):
98
+ out.append(ch)
99
+ continue
95
100
  ea = unicodedata.east_asian_width(ch)
96
- cw = 2 if ea in ('W', 'F') or ord(ch) >= 0x1F300 else 1
101
+ cw = 2 if (ea in ('W', 'F') or cp >= 0x1F300) else 1
97
102
  if curr_w + cw > max_w - 3:
98
- out.append('...')
103
+ out.append(RESET + '...')
104
+ curr_w += 3
99
105
  break
100
106
  out.append(ch)
101
107
  curr_w += cw
108
+ out.append(RESET)
102
109
  return ''.join(out)
103
110
 
104
111
  def card_line(content: str, max_w: int = 67, border: str = "│", border_color: str = CYAN) -> str:
@@ -109,37 +116,56 @@ def card_line(content: str, max_w: int = 67, border: str = "│", border_color:
109
116
  return f" {border_color}{border}{RESET} {trunc}{pad} {border_color}{border}{RESET}"
110
117
 
111
118
  def card_border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
119
+ """Generate 75-column top border (single ┌ or double ╔) with optional title."""
112
120
  left = "╔" if double else "┌"
113
121
  right = "╗" if double else "┐"
114
122
  h = "═" if double else "─"
115
123
  if title:
116
124
  vis = get_visual_width(title)
117
- rem = max(0, 71 - 3 - vis - 1)
125
+ rem = max(0, 68 - vis)
118
126
  return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
119
127
  return f" {border_color}{left}{h * 71}{right}{RESET}"
120
128
 
121
129
  def card_border_bottom(border_color: str = CYAN, double: bool = False) -> str:
130
+ """Generate 75-column bottom border (single └ or double ╚)."""
122
131
  left = "╚" if double else "└"
123
132
  right = "╝" if double else "┘"
124
133
  h = "═" if double else "─"
125
134
  return f" {border_color}{left}{h * 71}{right}{RESET}"
126
135
 
127
- def card_divider(border_color: str = CYAN, double: bool = False) -> str:
136
+ def card_divider(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
137
+ """Generate 75-column divider (single ├ or double ╠) with optional title."""
128
138
  left = "╠" if double else "├"
129
139
  right = "╣" if double else "┤"
130
140
  h = "═" if double else "─"
141
+ if title:
142
+ vis = get_visual_width(title)
143
+ rem = max(0, 68 - vis)
144
+ return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
131
145
  return f" {border_color}{left}{h * 71}{right}{RESET}"
132
146
 
147
+ def card_header(title: str, subtitle: str = "", version: str = "v2.1.1", border_color: str = CYAN) -> str:
148
+ """Generate standardized 75-column curved header box."""
149
+ top = f" {border_color}╭{'─' * 71}╮{RESET}"
150
+ bottom = f" {border_color}╰{'─' * 71}╯{RESET}"
151
+ empty = f" {border_color}│{' ' * 71}│{RESET}"
152
+
153
+ title_vis = get_visual_width(title)
154
+ ver_vis = get_visual_width(version)
155
+ space_count = max(1, 67 - title_vis - ver_vis)
156
+ title_str = f"{BOLD}{WHITE}{title}{RESET}{' ' * space_count}{GRAY}{version}{RESET}"
157
+
158
+ lines = [top, empty, card_line(title_str, max_w=67, border_color=border_color)]
159
+ if subtitle:
160
+ lines.append(card_line(f"{DIM}{subtitle}{RESET}", max_w=67, border_color=border_color))
161
+ lines.extend([empty, bottom])
162
+ return "\n".join(lines)
163
+
133
164
  def print_header():
134
165
  """Print the branded TorusGuard header card."""
135
- print(f"""
136
- {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}
137
- {CYAN}│{RESET} {CYAN}│{RESET}
138
- {CYAN}│{RESET} {BOLD}{WHITE}🛡️ T O R U S G U A R D{RESET} {GRAY}v1.3.0{RESET} {CYAN}│{RESET}
139
- {CYAN}│{RESET} {DIM}Autonomous Security Engine for AI-Built Applications{RESET} {CYAN}│{RESET}
140
- {CYAN}│{RESET} {CYAN}│{RESET}
141
- {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}
142
- """)
166
+ print()
167
+ print(card_header("🛡️ T O R U S G U A R D", "Autonomous Security Engine for AI-Built Applications", version="v2.1.1"))
168
+ print()
143
169
 
144
170
 
145
171
  def print_step_1_assets(file_count):
@@ -213,14 +239,15 @@ def print_success_card():
213
239
 
214
240
  def print_already_initialized(target_root, cfg):
215
241
  """Print the already-initialized status card."""
242
+ print()
243
+ top = f" {CYAN}╭{'─' * 71}╮{RESET}"
244
+ bottom = f" {CYAN}╰{'─' * 71}╯{RESET}"
245
+ title_line = card_line(f"{BOLD}{WHITE}🛡️ TORUSGUARD WORKSPACE{RESET}{' ' * 28}{GREEN}[Active]{RESET}")
246
+ print(f"{top}\n{title_line}\n{bottom}")
216
247
  print(f"""
217
- {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}
218
- {CYAN}│{RESET} {BOLD}{WHITE}🛡️ TORUSGUARD WORKSPACE{RESET} {GREEN}[Active]{RESET} {CYAN}│{RESET}
219
- {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}
220
-
221
248
  {BOLD}▸ Project Root:{RESET} {GREEN}{target_root}{RESET}
222
249
  {BOLD}▸ Workspace:{RESET} {GREEN}.torusguard/{RESET} {DIM}(Already Initialized){RESET}
223
- {BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '1.3.0')}{RESET}
250
+ {BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '2.1.1')}{RESET}
224
251
  {BOLD}▸ Severity Floor:{RESET} {YELLOW}{cfg.get('severity_threshold', 'medium')}{RESET}
225
252
 
226
253
  {DIM}To refresh templates or re-scaffold, run:{RESET}
@@ -228,7 +255,7 @@ def print_already_initialized(target_root, cfg):
228
255
  """)
229
256
 
230
257
 
231
- def scaffold_workspace(target_root=None, force=False, full_commands=False):
258
+ def scaffold_workspace(target_root=None, force=False, full_commands=False, template=None, **kwargs):
232
259
  """Scaffold the .torusguard workspace into the target project root."""
233
260
  target_root = Path(target_root or find_project_root()).resolve()
234
261
  torusguard_target = target_root / ".torusguard"
@@ -379,7 +406,13 @@ def scaffold_workspace(target_root=None, force=False, full_commands=False):
379
406
  try:
380
407
  with open(config_file, "r", encoding="utf-8") as f:
381
408
  cfg = json.load(f)
382
- if detected_stack and detected_stack.get("framework") != "None":
409
+ if template and template.lower() in ("golang", "go"):
410
+ cfg["detected_stack"] = {
411
+ "language": "Go",
412
+ "framework": "Gin",
413
+ "data_layer": "None"
414
+ }
415
+ elif detected_stack and detected_stack.get("framework") != "None":
383
416
  cfg["detected_stack"] = {
384
417
  "language": detected_stack.get("language"),
385
418
  "framework": detected_stack.get("framework"),