torusguard 1.3.4 → 1.3.6

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 (319) hide show
  1. package/.torusguard/.manifest.json +66 -63
  2. package/.torusguard/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
  3. package/.torusguard/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
  4. package/.torusguard/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
  5. package/.torusguard/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
  6. package/.torusguard/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
  7. package/.torusguard/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
  8. package/.torusguard/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
  9. package/.torusguard/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
  10. package/.torusguard/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
  11. package/.torusguard/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
  12. package/.torusguard/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
  13. package/.torusguard/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
  14. package/.torusguard/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
  15. package/.torusguard/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
  16. package/.torusguard/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
  17. package/.torusguard/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
  18. package/.torusguard/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
  19. package/.torusguard/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
  20. package/.torusguard/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
  21. package/.torusguard/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
  22. package/.torusguard/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
  23. package/.torusguard/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
  24. package/.torusguard/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
  25. package/.torusguard/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
  26. package/.torusguard/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
  27. package/.torusguard/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
  28. package/.torusguard/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
  29. package/.torusguard/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
  30. package/.torusguard/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
  31. package/.torusguard/rules_catalog.json +520 -0
  32. package/.torusguard/runs/report-latest.html +2700 -232
  33. package/.torusguard/runs/results-latest.sarif +20 -0
  34. package/.torusguard/runs/run-20260912-214110-audit/findings.json +1256 -0
  35. package/.torusguard/runs/run-20260912-214110-audit/findings.md +534 -0
  36. package/.torusguard/runs/run-20260912-214110-audit/manifest.json +12 -0
  37. package/.torusguard/runs/run-20260912-214110-audit/summary.md +5 -0
  38. package/.torusguard/runs/run-20260912-214813-audit/findings.json +992 -0
  39. package/.torusguard/runs/run-20260912-214813-audit/findings.md +422 -0
  40. package/.torusguard/runs/run-20260912-214813-audit/manifest.json +12 -0
  41. package/.torusguard/runs/run-20260912-214813-audit/summary.md +5 -0
  42. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/metadata.json +17 -0
  43. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/minimal_patch_plan.md +21 -0
  44. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/patch.diff +9 -0
  45. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  46. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  47. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  48. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  49. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  50. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  51. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  52. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  53. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  54. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  55. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  56. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  57. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  58. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  59. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  60. package/.torusguard/runs/run-20260912-215555-audit/findings.json +530 -0
  61. package/.torusguard/runs/run-20260912-215555-audit/findings.md +232 -0
  62. package/.torusguard/runs/run-20260912-215555-audit/manifest.json +13 -0
  63. package/.torusguard/runs/run-20260912-215555-audit/remediation.md +102 -0
  64. package/.torusguard/runs/run-20260912-215555-audit/summary.md +5 -0
  65. package/.torusguard/runs/run-20260912-215731-audit/apply_plan.md +6 -0
  66. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/metadata.json +17 -0
  67. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/minimal_patch_plan.md +21 -0
  68. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/patch.diff +9 -0
  69. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/metadata.json +17 -0
  70. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/minimal_patch_plan.md +21 -0
  71. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/patch.diff +9 -0
  72. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/metadata.json +17 -0
  73. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/minimal_patch_plan.md +22 -0
  74. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/patch.diff +10 -0
  75. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  76. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  77. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  78. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  79. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  80. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  81. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/metadata.json +17 -0
  82. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/minimal_patch_plan.md +23 -0
  83. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/patch.diff +11 -0
  84. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/metadata.json +17 -0
  85. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/minimal_patch_plan.md +23 -0
  86. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/patch.diff +11 -0
  87. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  88. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  89. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  90. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/metadata.json +17 -0
  91. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/minimal_patch_plan.md +21 -0
  92. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/patch.diff +9 -0
  93. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/metadata.json +17 -0
  94. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/minimal_patch_plan.md +21 -0
  95. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/patch.diff +9 -0
  96. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  97. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  98. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  99. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/metadata.json +17 -0
  100. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/minimal_patch_plan.md +21 -0
  101. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/patch.diff +9 -0
  102. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/metadata.json +17 -0
  103. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/minimal_patch_plan.md +21 -0
  104. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/patch.diff +9 -0
  105. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  106. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  107. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  108. package/.torusguard/runs/run-20260912-215731-audit/diff_summary.md +186 -0
  109. package/.torusguard/runs/run-20260912-215731-audit/findings.json +497 -0
  110. package/.torusguard/runs/run-20260912-215731-audit/findings.md +218 -0
  111. package/.torusguard/runs/run-20260912-215731-audit/manifest.json +14 -0
  112. package/.torusguard/runs/run-20260912-215731-audit/recheck.md +76 -0
  113. package/.torusguard/runs/run-20260912-215731-audit/remediation.md +234 -0
  114. package/.torusguard/runs/run-20260912-215731-audit/summary.md +5 -0
  115. package/.torusguard/runs/run-20260912-220134-audit/findings.json +68 -0
  116. package/.torusguard/runs/run-20260912-220134-audit/findings.md +36 -0
  117. package/.torusguard/runs/run-20260912-220134-audit/manifest.json +12 -0
  118. package/.torusguard/runs/run-20260912-220134-audit/summary.md +5 -0
  119. package/.torusguard/runs/run-20260912-220200-audit/findings.json +1 -0
  120. package/.torusguard/runs/run-20260912-220200-audit/findings.md +8 -0
  121. package/.torusguard/runs/run-20260912-220200-audit/manifest.json +12 -0
  122. package/.torusguard/runs/run-20260912-220200-audit/recheck.md +6 -0
  123. package/.torusguard/runs/run-20260912-220200-audit/summary.md +5 -0
  124. package/.torusguard/runs/run-20260912-220227-audit/findings.json +1 -0
  125. package/.torusguard/runs/run-20260912-220227-audit/findings.md +8 -0
  126. package/.torusguard/runs/run-20260912-220227-audit/manifest.json +12 -0
  127. package/.torusguard/runs/run-20260912-220227-audit/recheck.md +6 -0
  128. package/.torusguard/runs/run-20260912-220227-audit/summary.md +5 -0
  129. package/.torusguard/runs/run-20260918-104221-audit/findings.json +1 -0
  130. package/.torusguard/runs/run-20260918-104221-audit/findings.md +8 -0
  131. package/.torusguard/runs/run-20260918-104221-audit/manifest.json +12 -0
  132. package/.torusguard/runs/run-20260918-104221-audit/summary.md +5 -0
  133. package/.torusguard/runs/run-20260918-104236-audit/findings.json +2314 -0
  134. package/.torusguard/runs/run-20260918-104236-audit/findings.md +960 -0
  135. package/.torusguard/runs/run-20260918-104236-audit/manifest.json +12 -0
  136. package/.torusguard/runs/run-20260918-104236-audit/summary.md +5 -0
  137. package/.torusguard/runs/run-20260918-105723-audit/findings.json +2382 -0
  138. package/.torusguard/runs/run-20260918-105723-audit/findings.md +960 -0
  139. package/.torusguard/runs/run-20260918-105723-audit/manifest.json +12 -0
  140. package/.torusguard/runs/run-20260918-105723-audit/summary.md +5 -0
  141. package/.torusguard/runs/run-20260918-105731-audit/findings.json +1 -0
  142. package/.torusguard/runs/run-20260918-105731-audit/findings.md +8 -0
  143. package/.torusguard/runs/run-20260918-105731-audit/manifest.json +12 -0
  144. package/.torusguard/runs/run-20260918-105731-audit/summary.md +5 -0
  145. package/.torusguard/runs/run-20260918-105940-audit/findings.json +1 -0
  146. package/.torusguard/runs/run-20260918-105940-audit/findings.md +8 -0
  147. package/.torusguard/runs/run-20260918-105940-audit/manifest.json +12 -0
  148. package/.torusguard/runs/run-20260918-105940-audit/summary.md +5 -0
  149. package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  150. package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  151. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  152. package/.torusguard/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
  153. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  154. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  155. package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  156. package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  157. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  158. package/.torusguard/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
  159. package/.torusguard/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
  160. package/.torusguard/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
  161. package/.torusguard/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  162. package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  163. package/.torusguard/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
  164. package/.torusguard/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  165. package/.torusguard/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
  166. package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  167. package/.torusguard/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  168. package/.torusguard/scripts/apply_runner.py +389 -377
  169. package/.torusguard/scripts/audit_runner.py +1513 -653
  170. package/.torusguard/scripts/finding_scorer.py +14 -2
  171. package/.torusguard/scripts/harden_runner.py +628 -455
  172. package/.torusguard/scripts/html_reporter.py +2668 -648
  173. package/.torusguard/scripts/recheck_runner.py +248 -238
  174. package/.torusguard/scripts/recipes_runner.py +137 -137
  175. package/.torusguard/scripts/report_sync.py +719 -0
  176. package/.torusguard/scripts/sarif_exporter.py +156 -116
  177. package/.torusguard/scripts/stack_detect.py +44 -2
  178. package/.torusguard/scripts/term_ui.py +162 -162
  179. package/.torusguard/skills/torusguard/SKILL.md +75 -72
  180. package/.torusguard/skills/torusguard-apply/SKILL.md +84 -80
  181. package/.torusguard/skills/torusguard-audit/SKILL.md +87 -83
  182. package/.torusguard/skills/torusguard-authorize/SKILL.md +7 -2
  183. package/.torusguard/skills/torusguard-exploit-check/SKILL.md +7 -2
  184. package/.torusguard/skills/torusguard-full/SKILL.md +7 -2
  185. package/.torusguard/skills/torusguard-harden/SKILL.md +98 -94
  186. package/.torusguard/skills/torusguard-init/SKILL.md +68 -64
  187. package/.torusguard/skills/torusguard-recheck/SKILL.md +81 -77
  188. package/.torusguard/skills/torusguard-report/SKILL.md +93 -72
  189. package/.torusguard/skills/torusguard-status/SKILL.md +62 -58
  190. package/.torusguard/skills/torusguard-verify/SKILL.md +7 -2
  191. package/.torusguard/skills/torusguard-web-validate/SKILL.md +7 -2
  192. package/.torusguard/snapshots/run-20260912-215731-audit/demo/playground/vulnerable_nextjs/actions.ts.bak +24 -0
  193. package/.torusguard/snapshots/run-20260912-215731-audit/examples/hardened-react-express/server/index.js.bak +121 -0
  194. package/.torusguard/snapshots/run-20260912-215731-audit/examples/python/fastapi-vuln/main.py.bak +20 -0
  195. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/src/App.jsx.bak +63 -0
  196. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/vite.config.js.bak +12 -0
  197. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/server/index.js.bak +82 -0
  198. package/.torusguard/workflows/apply.md +11 -4
  199. package/.torusguard/workflows/audit.md +7 -1
  200. package/.torusguard/workflows/authorize.md +2 -1
  201. package/.torusguard/workflows/exploit-check.md +2 -1
  202. package/.torusguard/workflows/harden.md +7 -1
  203. package/.torusguard/workflows/init.md +6 -1
  204. package/.torusguard/workflows/memory.md +2 -1
  205. package/.torusguard/workflows/recheck.md +7 -1
  206. package/.torusguard/workflows/report.md +7 -1
  207. package/.torusguard/workflows/status.md +6 -1
  208. package/.torusguard/workflows/verify.md +6 -1
  209. package/.torusguard/workflows/web-validate.md +2 -1
  210. package/README.md +476 -345
  211. package/bin/torusguard.js +586 -545
  212. package/package.json +1 -1
  213. package/skills/torusguard/SKILL.md +73 -72
  214. package/skills/torusguard/bootstrap.py +7 -2
  215. package/skills/torusguard/payload/.manifest.json +66 -63
  216. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
  217. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
  218. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
  219. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
  220. package/skills/torusguard/payload/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
  221. package/skills/torusguard/payload/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
  222. package/skills/torusguard/payload/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
  223. package/skills/torusguard/payload/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
  224. package/skills/torusguard/payload/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
  225. package/skills/torusguard/payload/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
  226. package/skills/torusguard/payload/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
  227. package/skills/torusguard/payload/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
  228. package/skills/torusguard/payload/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
  229. package/skills/torusguard/payload/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
  230. package/skills/torusguard/payload/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
  231. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
  232. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
  233. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
  234. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
  235. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
  236. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
  237. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
  238. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
  239. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
  240. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
  241. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
  242. package/skills/torusguard/payload/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
  243. package/skills/torusguard/payload/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
  244. package/skills/torusguard/payload/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
  245. package/skills/torusguard/payload/rules_catalog.json +520 -0
  246. package/skills/torusguard/payload/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  247. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  248. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  249. package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
  250. package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
  251. package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  252. package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  253. package/skills/torusguard/payload/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  254. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-311.pyc +0 -0
  255. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  256. package/skills/torusguard/payload/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  257. package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-311.pyc +0 -0
  258. package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
  259. package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-311.pyc +0 -0
  260. package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
  261. package/skills/torusguard/payload/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
  262. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  263. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  264. package/skills/torusguard/payload/scripts/__pycache__/rules_sync.cpython-311.pyc +0 -0
  265. package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-311.pyc +0 -0
  266. package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
  267. package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  268. package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-311.pyc +0 -0
  269. package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
  270. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  271. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  272. package/skills/torusguard/payload/scripts/apply_runner.py +389 -377
  273. package/skills/torusguard/payload/scripts/audit_runner.py +1513 -653
  274. package/skills/torusguard/payload/scripts/finding_scorer.py +14 -2
  275. package/skills/torusguard/payload/scripts/harden_runner.py +628 -455
  276. package/skills/torusguard/payload/scripts/html_reporter.py +2668 -648
  277. package/skills/torusguard/payload/scripts/recheck_runner.py +248 -238
  278. package/skills/torusguard/payload/scripts/recipes_runner.py +137 -137
  279. package/skills/torusguard/payload/scripts/report_sync.py +719 -0
  280. package/skills/torusguard/payload/scripts/sarif_exporter.py +156 -116
  281. package/skills/torusguard/payload/scripts/stack_detect.py +44 -2
  282. package/skills/torusguard/payload/scripts/term_ui.py +162 -162
  283. package/skills/torusguard/payload/skills/torusguard/SKILL.md +75 -72
  284. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +84 -80
  285. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +87 -83
  286. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +7 -2
  287. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +7 -2
  288. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +7 -2
  289. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +98 -94
  290. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +68 -64
  291. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +81 -77
  292. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +93 -72
  293. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +62 -58
  294. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +7 -2
  295. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +7 -2
  296. package/skills/torusguard/payload/workflows/apply.md +11 -4
  297. package/skills/torusguard/payload/workflows/audit.md +7 -1
  298. package/skills/torusguard/payload/workflows/authorize.md +2 -1
  299. package/skills/torusguard/payload/workflows/exploit-check.md +2 -1
  300. package/skills/torusguard/payload/workflows/harden.md +7 -1
  301. package/skills/torusguard/payload/workflows/init.md +6 -1
  302. package/skills/torusguard/payload/workflows/memory.md +2 -1
  303. package/skills/torusguard/payload/workflows/recheck.md +7 -1
  304. package/skills/torusguard/payload/workflows/report.md +7 -1
  305. package/skills/torusguard/payload/workflows/status.md +6 -1
  306. package/skills/torusguard/payload/workflows/verify.md +6 -1
  307. package/skills/torusguard/payload/workflows/web-validate.md +2 -1
  308. package/skills/torusguard-apply/SKILL.md +84 -80
  309. package/skills/torusguard-audit/SKILL.md +87 -83
  310. package/skills/torusguard-authorize/SKILL.md +7 -2
  311. package/skills/torusguard-exploit-check/SKILL.md +7 -2
  312. package/skills/torusguard-full/SKILL.md +7 -2
  313. package/skills/torusguard-harden/SKILL.md +98 -94
  314. package/skills/torusguard-init/SKILL.md +68 -64
  315. package/skills/torusguard-recheck/SKILL.md +81 -77
  316. package/skills/torusguard-report/SKILL.md +93 -72
  317. package/skills/torusguard-status/SKILL.md +62 -58
  318. package/skills/torusguard-verify/SKILL.md +7 -2
  319. package/skills/torusguard-web-validate/SKILL.md +7 -2
@@ -1,377 +1,389 @@
1
- #!/usr/bin/env python3
2
- """
3
- TorusGuard Governed Patch Application & Snapshot Engine (v1.3.3)
4
- Interactive Human Gate authorization, pre-apply .bak rollback snapshots,
5
- atomic patch application, and Golden Fix Recipe distillation into persistent memory.
6
- Strict 75-column terminal UI formatting.
7
-
8
- Pure Python 3.10+ standard library (zero external dependencies).
9
- """
10
-
11
- import sys
12
- import os
13
- import re
14
- import json
15
- import shutil
16
- import hashlib
17
- import argparse
18
- import datetime
19
- from pathlib import Path
20
- from typing import Dict, List, Any, Optional
21
-
22
- # Ensure scripts dir is in sys.path for term_ui import
23
- scripts_dir = Path(__file__).resolve().parent
24
- if str(scripts_dir) not in sys.path:
25
- sys.path.insert(0, str(scripts_dir))
26
-
27
- try:
28
- import term_ui
29
- except ImportError:
30
- term_ui = None
31
-
32
- # Windows console UTF-8 support
33
- if sys.stdout and hasattr(sys.stdout, "reconfigure"):
34
- try:
35
- getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
36
- except Exception:
37
- pass
38
-
39
- # ─── Timezone Configuration: Indian Standard Time (IST, UTC+05:30) ───────────
40
- IST = datetime.timezone(datetime.timedelta(hours=5, minutes=30), name="IST")
41
-
42
- def get_ist_now() -> datetime.datetime:
43
- return datetime.datetime.now(IST)
44
-
45
- # ─── Formatting Helpers ────────────────────────────────────────────────────────
46
- BOLD = "\033[1m"
47
- DIM = "\033[2m"
48
- RESET = "\033[0m"
49
- CYAN = "\033[36m"
50
- GREEN = "\033[32m"
51
- YELLOW = "\033[33m"
52
- WHITE = "\033[97m"
53
- GRAY = "\033[90m"
54
- RED = "\033[31m"
55
-
56
- def box_line(content: str, width: int = 67, border: str = "│", border_color: str = CYAN) -> str:
57
- if term_ui:
58
- return term_ui.format_box_line(content, width=width, border=border, border_color=border_color)
59
- return f" {border_color}{border}{RESET} {content}"
60
-
61
- def box_header(title: str, subtitle: str = "", version: str = "v1.3.3", border_color: str = CYAN) -> str:
62
- if term_ui:
63
- return term_ui.card_header(title, subtitle, version, border_color)
64
- return f"=== {title} ({version}) ==="
65
-
66
- def border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
67
- if term_ui:
68
- return term_ui.card_border_top(title, border_color=border_color, double=double)
69
- return "┌" + "─" * 71 + "┐"
70
-
71
- def border_bottom(border_color: str = CYAN, double: bool = False) -> str:
72
- if term_ui:
73
- return term_ui.card_border_bottom(border_color=border_color, double=double)
74
- return "└" + "─" * 71 + "┘"
75
-
76
-
77
- def find_latest_audit_run(runs_dir: Path) -> Optional[Path]:
78
- if not runs_dir.is_dir():
79
- return None
80
- runs = sorted(runs_dir.glob("run-*-audit"), key=lambda p: p.stat().st_mtime, reverse=True)
81
- return runs[0] if runs else None
82
-
83
-
84
- def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> None:
85
- """Restore all target files from the pre-apply backup snapshots."""
86
- snapshots_base = target_root / ".torusguard" / "snapshots"
87
- if not snapshots_base.is_dir():
88
- print(f"\n {YELLOW}ℹ No snapshots directory found at: {snapshots_base}{RESET}\n")
89
- sys.exit(0)
90
-
91
- if run_id_arg:
92
- snapshot_dir = snapshots_base / run_id_arg
93
- else:
94
- snapshots = sorted([d for d in snapshots_base.iterdir() if d.is_dir()], key=lambda p: p.stat().st_mtime, reverse=True)
95
- snapshot_dir = snapshots[0] if snapshots else None
96
-
97
- if not snapshot_dir or not snapshot_dir.is_dir():
98
- print(f"\n {RED}✖ No rollback snapshot found for run: {run_id_arg or 'latest'}{RESET}\n")
99
- sys.exit(1)
100
-
101
- restored_files = []
102
- for bak_file in snapshot_dir.rglob("*.bak"):
103
- rel_path = bak_file.relative_to(snapshot_dir)
104
- orig_rel = str(rel_path)[:-4] # Strip .bak
105
- dest_file = target_root / orig_rel
106
-
107
- dest_file.parent.mkdir(parents=True, exist_ok=True)
108
- shutil.copy2(bak_file, dest_file)
109
- restored_files.append(orig_rel)
110
-
111
- print()
112
- print(box_header("🛡️ TORUSGUARD ROLLBACK RESTORATION", "Restored original files from pre-apply snapshot", "v1.3.3"))
113
- print()
114
-
115
- print(border_top("Rollback Snapshot Restored"))
116
- print(box_line(f"Snapshot Run: {WHITE}{snapshot_dir.name}{RESET}"))
117
- print(box_line(f"Files Restored: {GREEN}{len(restored_files)} file(s) cleanly reverted{RESET}"))
118
- for rf in restored_files:
119
- print(box_line(f" ✔ {rf}"))
120
- print(border_bottom())
121
- print()
122
- sys.exit(0)
123
-
124
-
125
- def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_approve: bool = False) -> Dict[str, Any]:
126
- runs_dir = target_root / ".torusguard" / "runs"
127
- if run_id_arg:
128
- run_folder = runs_dir / run_id_arg
129
- else:
130
- run_folder = find_latest_audit_run(runs_dir)
131
-
132
- if not run_folder or not run_folder.is_dir():
133
- print(f"\n {RED}✖ No audit runs found.{RESET}")
134
- print(f" Run {CYAN}npx torusguard audit{RESET} followed by {CYAN}npx torusguard harden{RESET} first.\n")
135
- sys.exit(1)
136
-
137
- bundles_dir = run_folder / "bundles"
138
- bundle_meta_files = list(bundles_dir.glob("*/metadata.json")) if bundles_dir.is_dir() else []
139
- if not bundle_meta_files:
140
- print()
141
- print(box_header("🛡️ TORUSGUARD GOVERNED PATCH APPLIER", "Human-Gate Authorization, Snapshots & Golden Recipe Distillation", "v1.3.3"))
142
- print()
143
- print(f" {YELLOW}ℹ Zero candidate remediation bundles found in run: {run_folder.name}{RESET}")
144
- print(f" {GRAY}The findings in this run require manual review or AI-assisted remediation.{RESET}\n")
145
- print(border_top("Recommended Next Steps"))
146
- print(box_line("1. In AI Chat: Run /torusguard-harden to synthesize custom fixes"))
147
- print(box_line(f"2. Inspect findings: .torusguard/runs/{run_folder.name}/findings.md"))
148
- print(box_line("3. View visual posture report: npx torusguard report --html"))
149
- print(border_bottom())
150
- print()
151
- sys.exit(0)
152
-
153
- bundles = []
154
- for mf in bundle_meta_files:
155
- try:
156
- bundles.append(json.loads(mf.read_text(encoding="utf-8")))
157
- except Exception:
158
- pass
159
-
160
- # Snapshot directory
161
- snapshot_dir = target_root / ".torusguard" / "snapshots" / run_folder.name
162
- snapshot_dir.mkdir(parents=True, exist_ok=True)
163
-
164
- # Header Card
165
- print()
166
- print(box_header("🛡️ TORUSGUARD GOVERNED PATCH APPLIER", "Human-Gate Authorization, Snapshots & Golden Recipe Distillation", "v1.3.3"))
167
- print()
168
-
169
- print(border_top("Candidate Bundles Ready for Application"))
170
- print(box_line(f"Active Run: {WHITE}{run_folder.name}{RESET}"))
171
- print(box_line(f"Total Bundles: {GREEN}{len(bundles)} candidate surgical patches formulated{RESET}"))
172
- print(box_line(f"Rollback Safety:Automatic pre-apply snapshot to .torusguard/snapshots/"))
173
- print(border_bottom())
174
- print()
175
-
176
- applied_count = 0
177
- skipped_count = 0
178
- all_approved = auto_approve
179
- diff_summary_lines = [f"# TorusGuard Applied Patch Summary: {run_folder.name}\n"]
180
- applied_bundles = []
181
-
182
- # Import memory_engine for Golden Recipe distillation
183
- try:
184
- import memory_engine
185
- except ImportError:
186
- memory_engine = None
187
-
188
- for idx, b in enumerate(bundles, 1):
189
- rule_id = b.get("rule_id", "TG-SEC")
190
- rel_path = b.get("target_file", "")
191
- line_no = b.get("line_number", 1)
192
- diff = b.get("proposed_diff", "")
193
- desc = b.get("what_should_change", "")
194
- patched_content = b.get("patched_content")
195
-
196
- print(f" {YELLOW}─── [{idx}/{len(bundles)}] Candidate Patch: [{rule_id}] on {rel_path}:{line_no} ───{RESET}")
197
- print(f" Strategy: {WHITE}{desc}{RESET}")
198
- print(f" Ponytail: {GREEN}+{b.get('additions', 0)} / -{b.get('deletions', 0)}{RESET}\n")
199
-
200
- # Display syntax-highlighted diff
201
- for dline in diff.splitlines()[:12]:
202
- if dline.startswith("+") and not dline.startswith("+++"):
203
- print(f" {GREEN}{dline}{RESET}")
204
- elif dline.startswith("-") and not dline.startswith("---"):
205
- print(f" {RED}{dline}{RESET}")
206
- elif dline.startswith("@"):
207
- print(f" {CYAN}{dline}{RESET}")
208
- else:
209
- print(f" {DIM}{dline}{RESET}")
210
- print()
211
-
212
- if not all_approved:
213
- try:
214
- choice = input(f" {BOLD}Apply this patch?{RESET} ([y]es / [n]o / [a]ll / [q]uit): ").strip().lower()
215
- except (EOFError, KeyboardInterrupt):
216
- print(f"\n {YELLOW}Aborted by user.{RESET}\n")
217
- break
218
-
219
- if choice in ("q", "quit"):
220
- print(f"\n {YELLOW}Exited review.{RESET}\n")
221
- break
222
- elif choice in ("a", "all"):
223
- all_approved = True
224
- elif choice not in ("y", "yes", ""):
225
- print(f" {GRAY}Skipped.{RESET}\n")
226
- skipped_count += 1
227
- continue
228
-
229
- # Execute Application
230
- target_file = target_root / rel_path
231
- if not target_file.is_file():
232
- print(f" {RED}✖ Target file not found: {target_file}{RESET}\n")
233
- continue
234
-
235
- # 1. Pre-apply Snapshot (.bak) if not already created
236
- bak_dest = snapshot_dir / (rel_path + ".bak")
237
- if not bak_dest.is_file():
238
- bak_dest.parent.mkdir(parents=True, exist_ok=True)
239
- shutil.copy2(target_file, bak_dest)
240
-
241
- # 2. Write Patched Content (Sequential Line-Aware or Full Patch)
242
- target_line = b.get("target_line")
243
- replacement_line = b.get("replacement_line")
244
- curr_text = target_file.read_text(encoding="utf-8", errors="replace")
245
- curr_lines = curr_text.splitlines()
246
-
247
- if target_line and replacement_line is not None:
248
- applied_line = False
249
- # Try expected line index
250
- if 0 <= line_no - 1 < len(curr_lines) and curr_lines[line_no - 1] == target_line:
251
- curr_lines[line_no - 1] = replacement_line
252
- applied_line = True
253
- else:
254
- # Search across whole file for the target line
255
- for i, cl in enumerate(curr_lines):
256
- if cl == target_line:
257
- curr_lines[i] = replacement_line
258
- applied_line = True
259
- break
260
- if applied_line:
261
- target_file.write_text("\n".join(curr_lines) + "\n", encoding="utf-8")
262
- elif patched_content:
263
- target_file.write_text(patched_content, encoding="utf-8")
264
- elif patched_content:
265
- target_file.write_text(patched_content, encoding="utf-8")
266
- else:
267
- continue
268
-
269
- # 3. Distill into Golden Fix Recipe in Memory Engine
270
- if memory_engine:
271
- try:
272
- fn = getattr(memory_engine, "record_golden_recipe", getattr(memory_engine, "store_golden_recipe", None))
273
- if fn:
274
- before_lines = target_file.read_text(encoding="utf-8").splitlines()
275
- before_snip = before_lines[max(0, line_no - 1)] if before_lines else ""
276
- fn(
277
- rule_id=rule_id,
278
- before_snippet=before_snip,
279
- after_snippet=desc,
280
- diff_snippet=diff,
281
- file_type=target_file.suffix,
282
- description=desc,
283
- root_dir=target_root
284
- )
285
- # Record event
286
- record_ev = getattr(memory_engine, "record_event", None)
287
- if record_ev:
288
- record_ev("fix_applied", {
289
- "rule_id": rule_id,
290
- "file_path": rel_path,
291
- "line_number": line_no,
292
- "bundle_id": b.get("bundle_id")
293
- }, root_dir=target_root)
294
- except Exception:
295
- pass
296
-
297
- applied_count += 1
298
- applied_bundles.append(b)
299
- diff_summary_lines.append(f"## Applied: `[{rule_id}]` on `{rel_path}:{line_no}`")
300
- diff_summary_lines.append("```diff")
301
- diff_summary_lines.append(diff.strip())
302
- diff_summary_lines.append("```\n")
303
- try:
304
- rel_snapshot = bak_dest.relative_to(target_root)
305
- except Exception:
306
- rel_snapshot = bak_dest.name
307
- print(f" {GREEN}✔ Applied successfully.{RESET} (Snapshot saved: {rel_snapshot})\n")
308
-
309
- # Write apply artifacts in run folder
310
- (run_folder / "diff_summary.md").write_text("\n".join(diff_summary_lines), encoding="utf-8")
311
- apply_plan_lines = [
312
- f"# TorusGuard Governed Application Plan: {run_folder.name}",
313
- f"- **Total Candidate Patches:** {len(bundles)}",
314
- f"- **Successfully Applied:** {applied_count}",
315
- f"- **Skipped by Human Gate:** {skipped_count}",
316
- f"- **Execution Timestamp:** {get_ist_now().strftime('%Y-%m-%d %H:%M:%S IST')}",
317
- f"- **Rollback Directory:** `.torusguard/snapshots/{run_folder.name}`"
318
- ]
319
- (run_folder / "apply_plan.md").write_text("\n".join(apply_plan_lines), encoding="utf-8")
320
-
321
- # Update manifest
322
- manifest_file = run_folder / "manifest.json"
323
- if manifest_file.is_file():
324
- try:
325
- mdata = json.loads(manifest_file.read_text(encoding="utf-8"))
326
- mdata["status"] = "patches_applied"
327
- mdata["remediated"] = applied_count
328
- manifest_file.write_text(json.dumps(mdata, indent=2), encoding="utf-8")
329
- except Exception:
330
- pass
331
-
332
- # Summary Box
333
- print(border_top("Application Summary"))
334
- print(box_line(f"Applied Patches:{GREEN}{applied_count} applied cleanly{RESET}"))
335
- print(box_line(f"Skipped / Pass: {GRAY}{skipped_count} skipped{RESET}"))
336
- print(box_line(f"Golden Recipes: {GREEN}{applied_count} distilled into persistent memory{RESET}"))
337
- print(box_line(f"Snapshot Safety:.torusguard/snapshots/{run_folder.name}"))
338
- print(border_bottom())
339
- print()
340
-
341
- print(border_top("Next Governed Action", border_color=GREEN, double=True))
342
- print(box_line(f"Differential Recheck: {BOLD}{WHITE}npx torusguard recheck{RESET} (Verify fix closure)", border="║", border_color=GREEN))
343
- print(box_line(f"View Golden Recipes: {CYAN}npx torusguard recipes{RESET} (Inspect memory)", border="║", border_color=GREEN))
344
- print(box_line(f"Emergency Rollback: {YELLOW}npx torusguard rollback{RESET} (Instant revert)", border="║", border_color=GREEN))
345
- print(border_bottom(border_color=GREEN, double=True))
346
- print()
347
-
348
- return {
349
- "applied_count": applied_count,
350
- "skipped_count": skipped_count,
351
- "applied_bundles": applied_bundles,
352
- "snapshot_dir": str(snapshot_dir)
353
- }
354
-
355
-
356
- def main():
357
- parser = argparse.ArgumentParser(description="TorusGuard Governed Patch Application Engine")
358
- parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
359
- parser.add_argument("--run", "-r", help="Explicit run ID to apply")
360
- parser.add_argument("--yes", "-y", action="store_true", help="Auto-approve all patches (non-interactive)")
361
- parser.add_argument("--rollback", action="store_true", help="Rollback all applied patches using snapshots")
362
- parser.add_argument("--json", action="store_true", help="Output raw JSON")
363
- args = parser.parse_args()
364
-
365
- target = Path(args.path).resolve()
366
-
367
- if args.rollback:
368
- execute_rollback(target, run_id_arg=args.run)
369
- return
370
-
371
- result = execute_apply(target, run_id_arg=args.run, auto_approve=args.yes)
372
- if args.json:
373
- print(json.dumps(result, indent=2))
374
-
375
-
376
- if __name__ == "__main__":
377
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ TorusGuard Governed Patch Application & Snapshot Engine (v1.3.6)
4
+ Interactive Human Gate authorization, pre-apply .bak rollback snapshots,
5
+ atomic patch application, and Golden Fix Recipe distillation into persistent memory.
6
+ Strict 75-column terminal UI formatting.
7
+
8
+ Pure Python 3.10+ standard library (zero external dependencies).
9
+ """
10
+
11
+ import sys
12
+ import os
13
+ import re
14
+ import json
15
+ import shutil
16
+ import hashlib
17
+ import argparse
18
+ import datetime
19
+ from pathlib import Path
20
+ from typing import Dict, List, Any, Optional
21
+
22
+ # Ensure scripts dir is in sys.path for term_ui import
23
+ scripts_dir = Path(__file__).resolve().parent
24
+ if str(scripts_dir) not in sys.path:
25
+ sys.path.insert(0, str(scripts_dir))
26
+
27
+ try:
28
+ import term_ui
29
+ except ImportError:
30
+ term_ui = None
31
+
32
+ # Windows console UTF-8 support
33
+ if sys.stdout and hasattr(sys.stdout, "reconfigure"):
34
+ try:
35
+ getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
36
+ except Exception:
37
+ pass
38
+
39
+ # ─── Timezone Configuration: Indian Standard Time (IST, UTC+05:30) ───────────
40
+ IST = datetime.timezone(datetime.timedelta(hours=5, minutes=30), name="IST")
41
+
42
+ def get_ist_now() -> datetime.datetime:
43
+ return datetime.datetime.now(IST)
44
+
45
+ # ─── Formatting Helpers ────────────────────────────────────────────────────────
46
+ BOLD = "\033[1m"
47
+ DIM = "\033[2m"
48
+ RESET = "\033[0m"
49
+ CYAN = "\033[36m"
50
+ GREEN = "\033[32m"
51
+ YELLOW = "\033[33m"
52
+ WHITE = "\033[97m"
53
+ GRAY = "\033[90m"
54
+ RED = "\033[31m"
55
+
56
+ def box_line(content: str, width: int = 67, border: str = "│", border_color: str = CYAN) -> str:
57
+ if term_ui:
58
+ return term_ui.format_box_line(content, width=width, border=border, border_color=border_color)
59
+ return f" {border_color}{border}{RESET} {content}"
60
+
61
+ def box_header(title: str, subtitle: str = "", version: str = "v1.3.6", border_color: str = CYAN) -> str:
62
+ if term_ui:
63
+ return term_ui.card_header(title, subtitle, version, border_color)
64
+ return f"=== {title} ({version}) ==="
65
+
66
+ def border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
67
+ if term_ui:
68
+ return term_ui.card_border_top(title, border_color=border_color, double=double)
69
+ return "┌" + "─" * 71 + "┐"
70
+
71
+ def border_bottom(border_color: str = CYAN, double: bool = False) -> str:
72
+ if term_ui:
73
+ return term_ui.card_border_bottom(border_color=border_color, double=double)
74
+ return "└" + "─" * 71 + "┘"
75
+
76
+
77
+ def find_latest_audit_run(runs_dir: Path) -> Optional[Path]:
78
+ if not runs_dir.is_dir():
79
+ return None
80
+ runs = sorted(runs_dir.glob("run-*-audit"), key=lambda p: p.stat().st_mtime, reverse=True)
81
+ return runs[0] if runs else None
82
+
83
+
84
+ def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> None:
85
+ """Restore all target files from the pre-apply backup snapshots."""
86
+ snapshots_base = target_root / ".torusguard" / "snapshots"
87
+ if not snapshots_base.is_dir():
88
+ print(f"\n {YELLOW}ℹ No snapshots directory found at: {snapshots_base}{RESET}\n")
89
+ sys.exit(0)
90
+
91
+ if run_id_arg:
92
+ snapshot_dir = snapshots_base / run_id_arg
93
+ else:
94
+ snapshots = sorted([d for d in snapshots_base.iterdir() if d.is_dir()], key=lambda p: p.stat().st_mtime, reverse=True)
95
+ snapshot_dir = snapshots[0] if snapshots else None
96
+
97
+ if not snapshot_dir or not snapshot_dir.is_dir():
98
+ print(f"\n {RED}✖ No rollback snapshot found for run: {run_id_arg or 'latest'}{RESET}\n")
99
+ sys.exit(1)
100
+
101
+ restored_files = []
102
+ for bak_file in snapshot_dir.rglob("*.bak"):
103
+ rel_path = bak_file.relative_to(snapshot_dir)
104
+ orig_rel = str(rel_path)[:-4] # Strip .bak
105
+ dest_file = target_root / orig_rel
106
+
107
+ dest_file.parent.mkdir(parents=True, exist_ok=True)
108
+ shutil.copy2(bak_file, dest_file)
109
+ restored_files.append(orig_rel)
110
+
111
+ try:
112
+ import report_sync
113
+ report_sync.record_rollback_results(target_root, restored_files, snapshot_dir.name)
114
+ except Exception:
115
+ pass
116
+
117
+ print()
118
+ print(box_header("🛡️ TORUSGUARD ROLLBACK RESTORATION", "Restored original files from pre-apply snapshot", "v1.3.6"))
119
+ print()
120
+
121
+ print(border_top("Rollback Snapshot Restored"))
122
+ print(box_line(f"Snapshot Run: {WHITE}{snapshot_dir.name}{RESET}"))
123
+ print(box_line(f"Files Restored: {GREEN}{len(restored_files)} file(s) cleanly reverted{RESET}"))
124
+ for rf in restored_files:
125
+ print(box_line(f" ✔ {rf}"))
126
+ print(border_bottom())
127
+ print()
128
+ sys.exit(0)
129
+
130
+
131
+ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_approve: bool = False) -> Dict[str, Any]:
132
+ runs_dir = target_root / ".torusguard" / "runs"
133
+ if run_id_arg:
134
+ run_folder = runs_dir / run_id_arg
135
+ else:
136
+ run_folder = find_latest_audit_run(runs_dir)
137
+
138
+ if not run_folder or not run_folder.is_dir():
139
+ print(f"\n {RED}✖ No audit runs found.{RESET}")
140
+ print(f" Run {CYAN}npx torusguard audit{RESET} followed by {CYAN}npx torusguard harden{RESET} first.\n")
141
+ sys.exit(1)
142
+
143
+ bundles_dir = run_folder / "bundles"
144
+ bundle_meta_files = list(bundles_dir.glob("*/metadata.json")) if bundles_dir.is_dir() else []
145
+ if not bundle_meta_files:
146
+ print()
147
+ print(box_header("🛡️ TORUSGUARD GOVERNED PATCH APPLIER", "Human-Gate Authorization, Snapshots & Golden Recipe Distillation", "v1.3.6"))
148
+ print()
149
+ print(f" {YELLOW}ℹ Zero candidate remediation bundles found in run: {run_folder.name}{RESET}")
150
+ print(f" {GRAY}The findings in this run require manual review or AI-assisted remediation.{RESET}\n")
151
+ print(border_top("Recommended Next Steps"))
152
+ print(box_line("1. In AI Chat: Run /torusguard-harden to synthesize custom fixes"))
153
+ print(box_line(f"2. Inspect findings: .torusguard/runs/{run_folder.name}/findings.md"))
154
+ print(box_line("3. View visual posture report: npx torusguard report --html"))
155
+ print(border_bottom())
156
+ print()
157
+ sys.exit(0)
158
+
159
+ bundles = []
160
+ for mf in bundle_meta_files:
161
+ try:
162
+ bundles.append(json.loads(mf.read_text(encoding="utf-8")))
163
+ except Exception:
164
+ pass
165
+
166
+ # Snapshot directory
167
+ snapshot_dir = target_root / ".torusguard" / "snapshots" / run_folder.name
168
+ snapshot_dir.mkdir(parents=True, exist_ok=True)
169
+
170
+ # Header Card
171
+ print()
172
+ print(box_header("🛡️ TORUSGUARD GOVERNED PATCH APPLIER", "Human-Gate Authorization, Snapshots & Golden Recipe Distillation", "v1.3.6"))
173
+ print()
174
+
175
+ print(border_top("Candidate Bundles Ready for Application"))
176
+ print(box_line(f"Active Run: {WHITE}{run_folder.name}{RESET}"))
177
+ print(box_line(f"Total Bundles: {GREEN}{len(bundles)} candidate surgical patches formulated{RESET}"))
178
+ print(box_line(f"Rollback Safety:Automatic pre-apply snapshot to .torusguard/snapshots/"))
179
+ print(border_bottom())
180
+ print()
181
+
182
+ applied_count = 0
183
+ skipped_count = 0
184
+ all_approved = auto_approve
185
+ diff_summary_lines = [f"# TorusGuard Applied Patch Summary: {run_folder.name}\n"]
186
+ applied_bundles = []
187
+
188
+ # Import memory_engine for Golden Recipe distillation
189
+ try:
190
+ import memory_engine
191
+ except ImportError:
192
+ memory_engine = None
193
+
194
+ for idx, b in enumerate(bundles, 1):
195
+ rule_id = b.get("rule_id", "TG-SEC")
196
+ rel_path = b.get("target_file", "")
197
+ line_no = b.get("line_number", 1)
198
+ diff = b.get("proposed_diff", "")
199
+ desc = b.get("what_should_change", "")
200
+ patched_content = b.get("patched_content")
201
+
202
+ print(f" {YELLOW}─── [{idx}/{len(bundles)}] Candidate Patch: [{rule_id}] on {rel_path}:{line_no} ───{RESET}")
203
+ print(f" Strategy: {WHITE}{desc}{RESET}")
204
+ print(f" Ponytail: {GREEN}+{b.get('additions', 0)} / -{b.get('deletions', 0)}{RESET}\n")
205
+
206
+ # Display syntax-highlighted diff
207
+ for dline in diff.splitlines()[:12]:
208
+ if dline.startswith("+") and not dline.startswith("+++"):
209
+ print(f" {GREEN}{dline}{RESET}")
210
+ elif dline.startswith("-") and not dline.startswith("---"):
211
+ print(f" {RED}{dline}{RESET}")
212
+ elif dline.startswith("@"):
213
+ print(f" {CYAN}{dline}{RESET}")
214
+ else:
215
+ print(f" {DIM}{dline}{RESET}")
216
+ print()
217
+
218
+ if not all_approved:
219
+ try:
220
+ choice = input(f" {BOLD}Apply this patch?{RESET} ([y]es / [n]o / [a]ll / [q]uit): ").strip().lower()
221
+ except (EOFError, KeyboardInterrupt):
222
+ print(f"\n {YELLOW}Aborted by user.{RESET}\n")
223
+ break
224
+
225
+ if choice in ("q", "quit"):
226
+ print(f"\n {YELLOW}Exited review.{RESET}\n")
227
+ break
228
+ elif choice in ("a", "all"):
229
+ all_approved = True
230
+ elif choice not in ("y", "yes", ""):
231
+ print(f" {GRAY}Skipped.{RESET}\n")
232
+ skipped_count += 1
233
+ continue
234
+
235
+ # Execute Application
236
+ target_file = target_root / rel_path
237
+ if not target_file.is_file():
238
+ print(f" {RED}✖ Target file not found: {target_file}{RESET}\n")
239
+ continue
240
+
241
+ # 1. Pre-apply Snapshot (.bak) if not already created
242
+ bak_dest = snapshot_dir / (rel_path + ".bak")
243
+ if not bak_dest.is_file():
244
+ bak_dest.parent.mkdir(parents=True, exist_ok=True)
245
+ shutil.copy2(target_file, bak_dest)
246
+
247
+ # 2. Write Patched Content (Sequential Line-Aware or Full Patch)
248
+ target_line = b.get("target_line")
249
+ replacement_line = b.get("replacement_line")
250
+ curr_text = target_file.read_text(encoding="utf-8", errors="replace")
251
+ curr_lines = curr_text.splitlines()
252
+
253
+ if target_line and replacement_line is not None:
254
+ applied_line = False
255
+ # Try expected line index
256
+ if 0 <= line_no - 1 < len(curr_lines) and curr_lines[line_no - 1] == target_line:
257
+ curr_lines[line_no - 1] = replacement_line
258
+ applied_line = True
259
+ else:
260
+ # Search across whole file for the target line
261
+ for i, cl in enumerate(curr_lines):
262
+ if cl == target_line:
263
+ curr_lines[i] = replacement_line
264
+ applied_line = True
265
+ break
266
+ if applied_line:
267
+ target_file.write_text("\n".join(curr_lines) + "\n", encoding="utf-8")
268
+ elif patched_content:
269
+ target_file.write_text(patched_content, encoding="utf-8")
270
+ elif patched_content:
271
+ target_file.write_text(patched_content, encoding="utf-8")
272
+ else:
273
+ continue
274
+
275
+ # 3. Distill into Golden Fix Recipe in Memory Engine
276
+ if memory_engine:
277
+ try:
278
+ fn = getattr(memory_engine, "record_golden_recipe", getattr(memory_engine, "store_golden_recipe", None))
279
+ if fn:
280
+ before_lines = target_file.read_text(encoding="utf-8").splitlines()
281
+ before_snip = before_lines[max(0, line_no - 1)] if before_lines else ""
282
+ fn(
283
+ rule_id=rule_id,
284
+ before_snippet=before_snip,
285
+ after_snippet=desc,
286
+ diff_snippet=diff,
287
+ file_type=target_file.suffix,
288
+ description=desc,
289
+ root_dir=target_root
290
+ )
291
+ # Record event
292
+ record_ev = getattr(memory_engine, "record_event", None)
293
+ if record_ev:
294
+ record_ev("fix_applied", {
295
+ "rule_id": rule_id,
296
+ "file_path": rel_path,
297
+ "line_number": line_no,
298
+ "bundle_id": b.get("bundle_id")
299
+ }, root_dir=target_root)
300
+ except Exception:
301
+ pass
302
+
303
+ applied_count += 1
304
+ applied_bundles.append(b)
305
+ diff_summary_lines.append(f"## Applied: `[{rule_id}]` on `{rel_path}:{line_no}`")
306
+ diff_summary_lines.append("```diff")
307
+ diff_summary_lines.append(diff.strip())
308
+ diff_summary_lines.append("```\n")
309
+ try:
310
+ rel_snapshot = bak_dest.relative_to(target_root)
311
+ except Exception:
312
+ rel_snapshot = bak_dest.name
313
+ print(f" {GREEN}✔ Applied successfully.{RESET} (Snapshot saved: {rel_snapshot})\n")
314
+
315
+ # Write apply artifacts in run folder
316
+ (run_folder / "diff_summary.md").write_text("\n".join(diff_summary_lines), encoding="utf-8")
317
+ apply_plan_lines = [
318
+ f"# TorusGuard Governed Application Plan: {run_folder.name}",
319
+ f"- **Total Candidate Patches:** {len(bundles)}",
320
+ f"- **Successfully Applied:** {applied_count}",
321
+ f"- **Skipped by Human Gate:** {skipped_count}",
322
+ f"- **Execution Timestamp:** {get_ist_now().strftime('%Y-%m-%d %H:%M:%S IST')}",
323
+ f"- **Rollback Directory:** `.torusguard/snapshots/{run_folder.name}`"
324
+ ]
325
+ (run_folder / "apply_plan.md").write_text("\n".join(apply_plan_lines), encoding="utf-8")
326
+ try:
327
+ import report_sync
328
+ report_sync.record_applied_patches(target_root, applied_bundles, snapshot_dir, run_folder.name)
329
+ except Exception:
330
+ pass
331
+
332
+ # Update manifest
333
+ manifest_file = run_folder / "manifest.json"
334
+ if manifest_file.is_file():
335
+ try:
336
+ mdata = json.loads(manifest_file.read_text(encoding="utf-8"))
337
+ mdata["status"] = "patches_applied"
338
+ mdata["remediated"] = applied_count
339
+ manifest_file.write_text(json.dumps(mdata, indent=2), encoding="utf-8")
340
+ except Exception:
341
+ pass
342
+
343
+ # Summary Box
344
+ print(border_top("Application Summary"))
345
+ print(box_line(f"Applied Patches:{GREEN}{applied_count} applied cleanly{RESET}"))
346
+ print(box_line(f"Skipped / Pass: {GRAY}{skipped_count} skipped{RESET}"))
347
+ print(box_line(f"Golden Recipes: {GREEN}{applied_count} distilled into persistent memory{RESET}"))
348
+ print(box_line(f"Snapshot Safety:.torusguard/snapshots/{run_folder.name}"))
349
+ print(border_bottom())
350
+ print()
351
+
352
+ print(border_top("Next Governed Action", border_color=GREEN, double=True))
353
+ print(box_line(f"Living Report: {CYAN}security_report.md (applied state recorded){RESET}", border="║", border_color=GREEN))
354
+ print(box_line(f"Differential Recheck: {BOLD}{WHITE}npx torusguard recheck{RESET} (Verify fix closure)", border="║", border_color=GREEN))
355
+ print(box_line(f"View Golden Recipes: {CYAN}npx torusguard recipes{RESET} (Inspect memory)", border="║", border_color=GREEN))
356
+ print(box_line(f"Emergency Rollback: {YELLOW}npx torusguard rollback{RESET} (Instant revert)", border="║", border_color=GREEN))
357
+ print(border_bottom(border_color=GREEN, double=True))
358
+ print()
359
+
360
+ return {
361
+ "applied_count": applied_count,
362
+ "skipped_count": skipped_count,
363
+ "applied_bundles": applied_bundles,
364
+ "snapshot_dir": str(snapshot_dir)
365
+ }
366
+
367
+
368
+ def main():
369
+ parser = argparse.ArgumentParser(description="TorusGuard Governed Patch Application Engine")
370
+ parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
371
+ parser.add_argument("--run", "-r", help="Explicit run ID to apply")
372
+ parser.add_argument("--yes", "-y", action="store_true", help="Auto-approve all patches (non-interactive)")
373
+ parser.add_argument("--rollback", action="store_true", help="Rollback all applied patches using snapshots")
374
+ parser.add_argument("--json", action="store_true", help="Output raw JSON")
375
+ args = parser.parse_args()
376
+
377
+ target = Path(args.path).resolve()
378
+
379
+ if args.rollback:
380
+ execute_rollback(target, run_id_arg=args.run)
381
+ return
382
+
383
+ result = execute_apply(target, run_id_arg=args.run, auto_approve=args.yes)
384
+ if args.json:
385
+ print(json.dumps(result, indent=2))
386
+
387
+
388
+ if __name__ == "__main__":
389
+ main()