torusguard 1.3.4 → 1.3.5

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 (239) 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 +93 -7
  33. package/.torusguard/runs/run-20260912-214110-audit/findings.json +1256 -0
  34. package/.torusguard/runs/run-20260912-214110-audit/findings.md +534 -0
  35. package/.torusguard/runs/run-20260912-214110-audit/manifest.json +12 -0
  36. package/.torusguard/runs/run-20260912-214110-audit/summary.md +5 -0
  37. package/.torusguard/runs/run-20260912-214813-audit/findings.json +992 -0
  38. package/.torusguard/runs/run-20260912-214813-audit/findings.md +422 -0
  39. package/.torusguard/runs/run-20260912-214813-audit/manifest.json +12 -0
  40. package/.torusguard/runs/run-20260912-214813-audit/summary.md +5 -0
  41. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/metadata.json +17 -0
  42. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/minimal_patch_plan.md +21 -0
  43. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/patch.diff +9 -0
  44. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  45. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  46. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  47. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  48. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  49. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  50. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  51. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  52. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  53. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  54. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  55. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  56. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  57. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  58. package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  59. package/.torusguard/runs/run-20260912-215555-audit/findings.json +530 -0
  60. package/.torusguard/runs/run-20260912-215555-audit/findings.md +232 -0
  61. package/.torusguard/runs/run-20260912-215555-audit/manifest.json +13 -0
  62. package/.torusguard/runs/run-20260912-215555-audit/remediation.md +102 -0
  63. package/.torusguard/runs/run-20260912-215555-audit/summary.md +5 -0
  64. package/.torusguard/runs/run-20260912-215731-audit/apply_plan.md +6 -0
  65. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/metadata.json +17 -0
  66. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/minimal_patch_plan.md +21 -0
  67. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/patch.diff +9 -0
  68. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/metadata.json +17 -0
  69. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/minimal_patch_plan.md +21 -0
  70. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/patch.diff +9 -0
  71. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/metadata.json +17 -0
  72. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/minimal_patch_plan.md +22 -0
  73. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/patch.diff +10 -0
  74. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
  75. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
  76. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
  77. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
  78. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
  79. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
  80. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/metadata.json +17 -0
  81. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/minimal_patch_plan.md +23 -0
  82. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/patch.diff +11 -0
  83. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/metadata.json +17 -0
  84. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/minimal_patch_plan.md +23 -0
  85. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/patch.diff +11 -0
  86. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
  87. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
  88. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
  89. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/metadata.json +17 -0
  90. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/minimal_patch_plan.md +21 -0
  91. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/patch.diff +9 -0
  92. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/metadata.json +17 -0
  93. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/minimal_patch_plan.md +21 -0
  94. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/patch.diff +9 -0
  95. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
  96. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
  97. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
  98. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/metadata.json +17 -0
  99. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/minimal_patch_plan.md +21 -0
  100. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/patch.diff +9 -0
  101. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/metadata.json +17 -0
  102. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/minimal_patch_plan.md +21 -0
  103. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/patch.diff +9 -0
  104. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
  105. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
  106. package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
  107. package/.torusguard/runs/run-20260912-215731-audit/diff_summary.md +186 -0
  108. package/.torusguard/runs/run-20260912-215731-audit/findings.json +497 -0
  109. package/.torusguard/runs/run-20260912-215731-audit/findings.md +218 -0
  110. package/.torusguard/runs/run-20260912-215731-audit/manifest.json +14 -0
  111. package/.torusguard/runs/run-20260912-215731-audit/recheck.md +76 -0
  112. package/.torusguard/runs/run-20260912-215731-audit/remediation.md +234 -0
  113. package/.torusguard/runs/run-20260912-215731-audit/summary.md +5 -0
  114. package/.torusguard/runs/run-20260912-220134-audit/findings.json +68 -0
  115. package/.torusguard/runs/run-20260912-220134-audit/findings.md +36 -0
  116. package/.torusguard/runs/run-20260912-220134-audit/manifest.json +12 -0
  117. package/.torusguard/runs/run-20260912-220134-audit/summary.md +5 -0
  118. package/.torusguard/runs/run-20260912-220200-audit/findings.json +1 -0
  119. package/.torusguard/runs/run-20260912-220200-audit/findings.md +8 -0
  120. package/.torusguard/runs/run-20260912-220200-audit/manifest.json +12 -0
  121. package/.torusguard/runs/run-20260912-220200-audit/recheck.md +6 -0
  122. package/.torusguard/runs/run-20260912-220200-audit/summary.md +5 -0
  123. package/.torusguard/runs/run-20260912-220227-audit/findings.json +1 -0
  124. package/.torusguard/runs/run-20260912-220227-audit/findings.md +8 -0
  125. package/.torusguard/runs/run-20260912-220227-audit/manifest.json +12 -0
  126. package/.torusguard/runs/run-20260912-220227-audit/recheck.md +6 -0
  127. package/.torusguard/runs/run-20260912-220227-audit/summary.md +5 -0
  128. package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  129. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  130. package/.torusguard/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  131. package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  132. package/.torusguard/scripts/apply_runner.py +383 -377
  133. package/.torusguard/scripts/audit_runner.py +1306 -653
  134. package/.torusguard/scripts/finding_scorer.py +14 -2
  135. package/.torusguard/scripts/harden_runner.py +628 -455
  136. package/.torusguard/scripts/recheck_runner.py +248 -238
  137. package/.torusguard/scripts/recipes_runner.py +137 -137
  138. package/.torusguard/scripts/report_sync.py +423 -0
  139. package/.torusguard/scripts/sarif_exporter.py +116 -116
  140. package/.torusguard/scripts/term_ui.py +162 -162
  141. package/.torusguard/snapshots/run-20260912-215731-audit/demo/playground/vulnerable_nextjs/actions.ts.bak +24 -0
  142. package/.torusguard/snapshots/run-20260912-215731-audit/examples/hardened-react-express/server/index.js.bak +121 -0
  143. package/.torusguard/snapshots/run-20260912-215731-audit/examples/python/fastapi-vuln/main.py.bak +20 -0
  144. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/src/App.jsx.bak +63 -0
  145. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/vite.config.js.bak +12 -0
  146. package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/server/index.js.bak +82 -0
  147. package/.torusguard/workflows/apply.md +11 -4
  148. package/.torusguard/workflows/audit.md +7 -1
  149. package/.torusguard/workflows/authorize.md +2 -1
  150. package/.torusguard/workflows/exploit-check.md +2 -1
  151. package/.torusguard/workflows/harden.md +7 -1
  152. package/.torusguard/workflows/init.md +6 -1
  153. package/.torusguard/workflows/memory.md +2 -1
  154. package/.torusguard/workflows/recheck.md +7 -1
  155. package/.torusguard/workflows/report.md +7 -1
  156. package/.torusguard/workflows/status.md +6 -1
  157. package/.torusguard/workflows/verify.md +6 -1
  158. package/.torusguard/workflows/web-validate.md +2 -1
  159. package/README.md +470 -345
  160. package/bin/torusguard.js +550 -545
  161. package/package.json +1 -1
  162. package/skills/torusguard/SKILL.md +73 -72
  163. package/skills/torusguard/payload/.manifest.json +66 -63
  164. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
  165. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
  166. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
  167. package/skills/torusguard/payload/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
  168. package/skills/torusguard/payload/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
  169. package/skills/torusguard/payload/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
  170. package/skills/torusguard/payload/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
  171. package/skills/torusguard/payload/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
  172. package/skills/torusguard/payload/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
  173. package/skills/torusguard/payload/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
  174. package/skills/torusguard/payload/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
  175. package/skills/torusguard/payload/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
  176. package/skills/torusguard/payload/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
  177. package/skills/torusguard/payload/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
  178. package/skills/torusguard/payload/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
  179. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
  180. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
  181. package/skills/torusguard/payload/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
  182. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
  183. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
  184. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
  185. package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
  186. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
  187. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
  188. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
  189. package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
  190. package/skills/torusguard/payload/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
  191. package/skills/torusguard/payload/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
  192. package/skills/torusguard/payload/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
  193. package/skills/torusguard/payload/rules_catalog.json +520 -0
  194. package/skills/torusguard/payload/scripts/apply_runner.py +383 -377
  195. package/skills/torusguard/payload/scripts/audit_runner.py +1306 -653
  196. package/skills/torusguard/payload/scripts/finding_scorer.py +14 -2
  197. package/skills/torusguard/payload/scripts/harden_runner.py +628 -455
  198. package/skills/torusguard/payload/scripts/recheck_runner.py +248 -238
  199. package/skills/torusguard/payload/scripts/recipes_runner.py +137 -137
  200. package/skills/torusguard/payload/scripts/report_sync.py +423 -0
  201. package/skills/torusguard/payload/scripts/sarif_exporter.py +116 -116
  202. package/skills/torusguard/payload/scripts/term_ui.py +162 -162
  203. package/skills/torusguard/payload/skills/torusguard/SKILL.md +75 -72
  204. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +84 -80
  205. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +87 -83
  206. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +7 -2
  207. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +7 -2
  208. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +7 -2
  209. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +98 -94
  210. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +68 -64
  211. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +81 -77
  212. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +76 -72
  213. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +62 -58
  214. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +7 -2
  215. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +7 -2
  216. package/skills/torusguard/payload/workflows/apply.md +11 -4
  217. package/skills/torusguard/payload/workflows/audit.md +7 -1
  218. package/skills/torusguard/payload/workflows/authorize.md +2 -1
  219. package/skills/torusguard/payload/workflows/exploit-check.md +2 -1
  220. package/skills/torusguard/payload/workflows/harden.md +7 -1
  221. package/skills/torusguard/payload/workflows/init.md +6 -1
  222. package/skills/torusguard/payload/workflows/memory.md +2 -1
  223. package/skills/torusguard/payload/workflows/recheck.md +7 -1
  224. package/skills/torusguard/payload/workflows/report.md +7 -1
  225. package/skills/torusguard/payload/workflows/status.md +6 -1
  226. package/skills/torusguard/payload/workflows/verify.md +6 -1
  227. package/skills/torusguard/payload/workflows/web-validate.md +2 -1
  228. package/skills/torusguard-apply/SKILL.md +84 -80
  229. package/skills/torusguard-audit/SKILL.md +87 -83
  230. package/skills/torusguard-authorize/SKILL.md +7 -2
  231. package/skills/torusguard-exploit-check/SKILL.md +7 -2
  232. package/skills/torusguard-full/SKILL.md +7 -2
  233. package/skills/torusguard-harden/SKILL.md +98 -94
  234. package/skills/torusguard-init/SKILL.md +68 -64
  235. package/skills/torusguard-recheck/SKILL.md +81 -77
  236. package/skills/torusguard-report/SKILL.md +76 -72
  237. package/skills/torusguard-status/SKILL.md +62 -58
  238. package/skills/torusguard-verify/SKILL.md +7 -2
  239. package/skills/torusguard-web-validate/SKILL.md +7 -2
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ TorusGuard Living Security Report Sync Engine (v1.3.5)
4
+ Maintains a living, ground-truth `security_report.md` ledger at the root of the workspace.
5
+ Tracks finding lifecycles from Discovery -> Candidate -> Applied -> Resolved -> Regressed.
6
+ Pure Python 3.10+ standard library (zero external dependencies).
7
+ """
8
+
9
+ import sys
10
+ import os
11
+ import re
12
+ import json
13
+ import datetime
14
+ from pathlib import Path
15
+ from typing import Dict, List, Any, Optional, Tuple
16
+
17
+ # Windows console UTF-8 support
18
+ if sys.stdout and hasattr(sys.stdout, "reconfigure"):
19
+ try:
20
+ getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
21
+ except Exception:
22
+ pass
23
+ if sys.stderr and hasattr(sys.stderr, "reconfigure"):
24
+ try:
25
+ getattr(sys.stderr, "reconfigure")(encoding="utf-8", errors="replace")
26
+ except Exception:
27
+ pass
28
+
29
+ # Timezone: Indian Standard Time (IST, UTC+05:30)
30
+ IST = datetime.timezone(datetime.timedelta(hours=5, minutes=30), name="IST")
31
+
32
+ def get_ist_now() -> datetime.datetime:
33
+ """Return timezone-aware datetime in IST."""
34
+ return datetime.datetime.now(IST)
35
+
36
+ def format_timestamp(dt: Optional[datetime.datetime] = None) -> str:
37
+ if dt is None:
38
+ dt = get_ist_now()
39
+ return dt.strftime("%Y-%m-%d %H:%M:%S IST")
40
+
41
+ def get_report_path(target_root: Path) -> Path:
42
+ """Return absolute path to security_report.md at the workspace root."""
43
+ return target_root / "security_report.md"
44
+
45
+ def parse_existing_report(report_path: Path) -> Dict[str, Any]:
46
+ """Parse existing security_report.md into structured data if it exists."""
47
+ if not report_path.is_file():
48
+ return {"findings": {}, "metadata": {}}
49
+
50
+ try:
51
+ content = report_path.read_text(encoding="utf-8", errors="replace")
52
+ except Exception:
53
+ return {"findings": {}, "metadata": {}}
54
+
55
+ findings = {}
56
+ # Parse individual finding cards: ### [RULE-ID] Title
57
+ card_pattern = re.compile(
58
+ r'###\s+\[(TG-[A-Z]+-\d+)\]\s+(.*?)\n'
59
+ r'- \*\*Finding ID:\*\*\s+`([^`]+)`\n'
60
+ r'- \*\*Status:\*\*\s+([^\n]+)\n'
61
+ r'- \*\*Severity:\*\*\s+([^\n|]+)(?:\|\s+\*\*Confidence:\*\*\s+([^\n]+))?\n'
62
+ r'- \*\*Target:\*\*\s+`([^`]+)`',
63
+ re.MULTILINE
64
+ )
65
+
66
+ for m in card_pattern.finditer(content):
67
+ rule_id = m.group(1).strip()
68
+ title = m.group(2).strip()
69
+ finding_id = m.group(3).strip()
70
+ status = m.group(4).strip()
71
+ severity = m.group(5).strip()
72
+ confidence = m.group(6).strip() if m.group(6) else "70% (High Confidence)"
73
+ target = m.group(7).strip()
74
+
75
+ findings[finding_id] = {
76
+ "finding_id": finding_id,
77
+ "rule_id": rule_id,
78
+ "title": title,
79
+ "status": status,
80
+ "severity": severity,
81
+ "confidence": confidence,
82
+ "target": target,
83
+ "history": []
84
+ }
85
+
86
+ # Extract history tables if present
87
+ history_pattern = re.compile(r'#### Lifecycle History for `([^`]+)`\n([\s\S]*?)(?=\n---|\n###|\Z)')
88
+ for hm in history_pattern.finditer(content):
89
+ fid = hm.group(1).strip()
90
+ table_text = hm.group(2).strip()
91
+ if fid in findings:
92
+ for row in table_text.splitlines():
93
+ if row.startswith("|") and not row.startswith("| Timestamp"):
94
+ cols = [c.strip().strip("`") for c in row.split("|")[1:-1]]
95
+ if len(cols) >= 4 and not cols[0].startswith(":") and not cols[0].startswith("-"):
96
+ findings[fid]["history"].append({
97
+ "timestamp": cols[0],
98
+ "action": cols[1],
99
+ "run_id": cols[2],
100
+ "details": cols[3]
101
+ })
102
+
103
+ return {"findings": findings, "raw_content": content}
104
+
105
+ def compute_health_score(findings: List[Dict[str, Any]]) -> Tuple[int, str]:
106
+ """
107
+ Compute security health score (0-100) based on remaining open/regressed findings.
108
+ Returns (score, status_text).
109
+ """
110
+ critical_count = sum(1 for f in findings if f["severity"].lower() == "critical" and ("OPEN" in f["status"] or "REGRESSED" in f["status"]))
111
+ high_count = sum(1 for f in findings if f["severity"].lower() == "high" and ("OPEN" in f["status"] or "REGRESSED" in f["status"]))
112
+ medium_count = sum(1 for f in findings if f["severity"].lower() == "medium" and ("OPEN" in f["status"] or "REGRESSED" in f["status"]))
113
+ low_count = sum(1 for f in findings if f["severity"].lower() == "low" and ("OPEN" in f["status"] or "REGRESSED" in f["status"]))
114
+
115
+ penalty = (critical_count * 25) + (high_count * 15) + (medium_count * 5) + (low_count * 2)
116
+ score = max(0, min(100, 100 - penalty))
117
+
118
+ if score == 100:
119
+ status = "🟢 HARDENED & SECURE"
120
+ elif score >= 80:
121
+ status = "🟡 MODERATE RISK — ACTION RECOMMENDED"
122
+ elif score >= 50:
123
+ status = "🟠 ELEVATED RISK — PATCHES REQUIRED"
124
+ else:
125
+ status = "🔴 CRITICAL RISK — IMMEDIATE INTERVENTION REQUIRED"
126
+
127
+ return score, status
128
+
129
+ def render_report(findings: List[Dict[str, Any]], target_root: Path) -> str:
130
+ """Render the full living security_report.md document."""
131
+ score, status_text = compute_health_score(findings)
132
+ now_str = format_timestamp()
133
+
134
+ # Counts
135
+ total = len(findings)
136
+ open_count = sum(1 for f in findings if "OPEN" in f["status"])
137
+ verified_count = sum(1 for f in findings if "VERIFIED" in f["status"])
138
+ candidate_count = sum(1 for f in findings if "CANDIDATE" in f["status"])
139
+ applied_count = sum(1 for f in findings if "APPLIED" in f["status"])
140
+ resolved_count = sum(1 for f in findings if "RESOLVED" in f["status"])
141
+ regressed_count = sum(1 for f in findings if "REGRESSED" in f["status"])
142
+ fp_count = sum(1 for f in findings if "FALSE POSITIVE" in f["status"])
143
+
144
+ crit_count = sum(1 for f in findings if f["severity"].lower() == "critical")
145
+ high_count = sum(1 for f in findings if f["severity"].lower() == "high")
146
+ med_count = sum(1 for f in findings if f["severity"].lower() == "medium")
147
+ low_count = sum(1 for f in findings if f["severity"].lower() == "low")
148
+
149
+ lines = [
150
+ "# 🛡️ TorusGuard Living Security Report",
151
+ "",
152
+ "> **Notice:** This document is the living, single-source-of-truth ledger for security findings,",
153
+ "> candidate patches, and verified closures across this repository. Updated automatically by",
154
+ "> TorusGuard CLI commands (`audit`, `harden`, `apply`, `recheck`) and AI chat workflows.",
155
+ "",
156
+ "---",
157
+ "",
158
+ "## 📊 Security Posture Overview",
159
+ "",
160
+ f"- **Health Score:** `{score}/100` — **{status_text}**",
161
+ f"- **Last Updated:** `{now_str}`",
162
+ f"- **Total Tracked Findings:** `{total}`",
163
+ f"- **Status Breakdown:** `🔴 {open_count} Open` · `🟠 {verified_count} Verified` · `🟡 {candidate_count} Candidate` · `🔵 {applied_count} Applied` · `🟢 {resolved_count} Resolved` · `❌ {regressed_count} Regressed` · `⚪ {fp_count} Suppressed`",
164
+ "",
165
+ "| Severity | Total | Open / Regressed | Resolved | Candidate / Applied |",
166
+ "| :--- | :---: | :---: | :---: | :---: |",
167
+ f"| **Critical** | {crit_count} | {sum(1 for f in findings if f['severity'].lower() == 'critical' and ('OPEN' in f['status'] or 'REGRESSED' in f['status']))} | {sum(1 for f in findings if f['severity'].lower() == 'critical' and 'RESOLVED' in f['status'])} | {sum(1 for f in findings if f['severity'].lower() == 'critical' and ('CANDIDATE' in f['status'] or 'APPLIED' in f['status']))} |",
168
+ f"| **High** | {high_count} | {sum(1 for f in findings if f['severity'].lower() == 'high' and ('OPEN' in f['status'] or 'REGRESSED' in f['status']))} | {sum(1 for f in findings if f['severity'].lower() == 'high' and 'RESOLVED' in f['status'])} | {sum(1 for f in findings if f['severity'].lower() == 'high' and ('CANDIDATE' in f['status'] or 'APPLIED' in f['status']))} |",
169
+ f"| **Medium** | {med_count} | {sum(1 for f in findings if f['severity'].lower() == 'medium' and ('OPEN' in f['status'] or 'REGRESSED' in f['status']))} | {sum(1 for f in findings if f['severity'].lower() == 'medium' and 'RESOLVED' in f['status'])} | {sum(1 for f in findings if f['severity'].lower() == 'medium' and ('CANDIDATE' in f['status'] or 'APPLIED' in f['status']))} |",
170
+ f"| **Low** | {low_count} | {sum(1 for f in findings if f['severity'].lower() == 'low' and ('OPEN' in f['status'] or 'REGRESSED' in f['status']))} | {sum(1 for f in findings if f['severity'].lower() == 'low' and 'RESOLVED' in f['status'])} | {sum(1 for f in findings if f['severity'].lower() == 'low' and ('CANDIDATE' in f['status'] or 'APPLIED' in f['status']))} |",
171
+ "",
172
+ "---",
173
+ "",
174
+ "## 🔍 Tracked Findings Detail",
175
+ ""
176
+ ]
177
+
178
+ # Sort findings: Open/Regressed first, then by severity (Critical -> High -> Medium -> Low), then rule_id
179
+ sev_rank = {"critical": 0, "high": 1, "medium": 2, "low": 3}
180
+ def sort_key(f):
181
+ is_active = 0 if ("OPEN" in f["status"] or "REGRESSED" in f["status"]) else 1
182
+ s_rank = sev_rank.get(f["severity"].lower(), 9)
183
+ return (is_active, s_rank, f["rule_id"])
184
+
185
+ sorted_findings = sorted(findings, key=sort_key)
186
+
187
+ for f in sorted_findings:
188
+ lines.append(f"### [{f['rule_id']}] {f['title']}")
189
+ lines.append(f"- **Finding ID:** `{f['finding_id']}`")
190
+ lines.append(f"- **Status:** {f['status']}")
191
+ lines.append(f"- **Severity:** {f['severity']} | **Confidence:** {f.get('confidence', '70% (High Confidence)')}")
192
+ lines.append(f"- **Target:** `{f['target']}`")
193
+ if f.get("cluster"):
194
+ lines.append(f"- **Root-Cause Cluster:** `{f['cluster']}`")
195
+ lines.append("")
196
+ lines.append(f"**What is wrong:** \n{f.get('what_is_wrong', f.get('description', 'Security violation detected by TorusGuard scanner.'))}")
197
+ lines.append("")
198
+ lines.append(f"**Why it matters:** \n{f.get('why_it_matters', 'Violates TorusGuard strict production safety and isolation invariant.')}")
199
+ lines.append("")
200
+ if f.get("what_should_change"):
201
+ lines.append(f"**Governed Remediation:** \n{f['what_should_change']}")
202
+ lines.append("")
203
+ if f.get("snippet"):
204
+ lines.append("```")
205
+ lines.append(f["snippet"].strip())
206
+ lines.append("```")
207
+ lines.append("")
208
+ if f.get("proposed_diff"):
209
+ lines.append("**Proposed Minimal Patch Preview (Ponytail Bounded):**")
210
+ lines.append("```diff")
211
+ lines.append(f["proposed_diff"].strip())
212
+ lines.append("```")
213
+ lines.append("")
214
+
215
+ # History table
216
+ history = f.get("history", [])
217
+ if history:
218
+ lines.append(f"#### Lifecycle History for `{f['finding_id']}`")
219
+ lines.append("| Timestamp | Action | Run ID | Details |")
220
+ lines.append("| :--- | :--- | :--- | :--- |")
221
+ for h in history:
222
+ lines.append(f"| {h['timestamp']} | {h['action']} | `{h['run_id']}` | {h['details']} |")
223
+ lines.append("")
224
+
225
+ lines.append("---")
226
+ lines.append("")
227
+
228
+ return "\n".join(lines)
229
+
230
+
231
+ def record_audit_findings(target_root: Path, findings: List[Dict[str, Any]], run_id: str):
232
+ """
233
+ Called at the conclusion of `npx torusguard audit`.
234
+ Updates security_report.md with new findings or records re-appearance.
235
+ """
236
+ report_file = get_report_path(target_root)
237
+ existing = parse_existing_report(report_file)
238
+ existing_map = existing["findings"]
239
+
240
+ now_str = format_timestamp()
241
+ current_fids = set()
242
+
243
+ for f in findings:
244
+ fid = f.get("finding_id", f"{f['rule_id']}-{f['line_number']}")
245
+ current_fids.add(fid)
246
+ rel_target = f"{f['file_path']}:{f['line_number']}"
247
+
248
+ if fid in existing_map:
249
+ # Finding already known
250
+ item = existing_map[fid]
251
+ item["target"] = rel_target
252
+ item["snippet"] = f.get("snippet", "")
253
+ item["description"] = f.get("description", "")
254
+
255
+ # If it was RESOLVED or APPLIED and reappeared, mark as REGRESSED
256
+ if "RESOLVED" in item["status"] or "APPLIED" in item["status"]:
257
+ item["status"] = "❌ REGRESSED"
258
+ item["history"].append({
259
+ "timestamp": now_str,
260
+ "action": "Regression Detected",
261
+ "run_id": run_id,
262
+ "details": "Vulnerability pattern re-detected in source code after prior closure."
263
+ })
264
+ else:
265
+ # Brand new finding
266
+ conf_str = f"{f.get('confidence_score', 70)}% ({f.get('confidence_band', 'High Confidence')})"
267
+ existing_map[fid] = {
268
+ "finding_id": fid,
269
+ "rule_id": f["rule_id"],
270
+ "title": f["title"],
271
+ "status": "🔴 OPEN",
272
+ "severity": f["severity"],
273
+ "confidence": conf_str,
274
+ "target": rel_target,
275
+ "cluster": f.get("cluster", "cluster-general"),
276
+ "what_is_wrong": f.get("description", ""),
277
+ "why_it_matters": "Violates TorusGuard strict production safety and isolation invariant.",
278
+ "what_should_change": f"Apply Ponytail-compliant patch via `npx torusguard harden`.",
279
+ "snippet": f.get("snippet", ""),
280
+ "history": [{
281
+ "timestamp": now_str,
282
+ "action": "Discovered",
283
+ "run_id": run_id,
284
+ "details": f"Initial discovery during static AST audit in {f['file_path']}."
285
+ }]
286
+ }
287
+
288
+ # For findings in existing report that were OPEN, APPLIED, or CANDIDATE and no longer detected, transition to RESOLVED
289
+ for fid, item in existing_map.items():
290
+ if fid not in current_fids and ("OPEN" in item["status"] or "APPLIED" in item["status"] or "CANDIDATE" in item["status"]):
291
+ item["status"] = "🟢 RESOLVED"
292
+ item["history"].append({
293
+ "timestamp": now_str,
294
+ "action": "Resolved",
295
+ "run_id": run_id,
296
+ "details": "Finding no longer detected in source code during latest audit pass."
297
+ })
298
+
299
+ # Render and save
300
+ rendered = render_report(list(existing_map.values()), target_root)
301
+ report_file.write_text(rendered, encoding="utf-8")
302
+ return report_file
303
+
304
+
305
+ def record_harden_bundles(target_root: Path, bundles: List[Dict[str, Any]], run_id: str):
306
+ """
307
+ Called at the conclusion of `npx torusguard harden`.
308
+ Transitions findings to CANDIDATE 🟡 and attaches minimal patch diff previews.
309
+ """
310
+ report_file = get_report_path(target_root)
311
+ existing = parse_existing_report(report_file)
312
+ existing_map = existing["findings"]
313
+ now_str = format_timestamp()
314
+
315
+ for b in bundles:
316
+ fid = b.get("finding_id")
317
+ rule_id = b.get("rule_id")
318
+ target = f"{b['target_file']}:{b['line_number']}"
319
+
320
+ # Match by finding_id or target
321
+ matched_item = None
322
+ if fid and fid in existing_map:
323
+ matched_item = existing_map[fid]
324
+ else:
325
+ for item in existing_map.values():
326
+ if item["rule_id"] == rule_id and item["target"] == target:
327
+ matched_item = item
328
+ break
329
+
330
+ if matched_item:
331
+ matched_item["status"] = "🟡 CANDIDATE"
332
+ matched_item["what_should_change"] = b.get("what_should_change", "")
333
+ matched_item["proposed_diff"] = b.get("proposed_diff", "")
334
+ matched_item["bundle_id"] = b.get("bundle_id", "")
335
+ matched_item["history"].append({
336
+ "timestamp": now_str,
337
+ "action": "Patch Formulated",
338
+ "run_id": run_id,
339
+ "details": f"Ponytail candidate bundle {b.get('bundle_id')} formulated (+{b.get('additions', 0)} / -{b.get('deletions', 0)} lines)."
340
+ })
341
+
342
+ rendered = render_report(list(existing_map.values()), target_root)
343
+ report_file.write_text(rendered, encoding="utf-8")
344
+ return report_file
345
+
346
+
347
+ def record_applied_patches(target_root: Path, applied_bundles: List[Dict[str, Any]], snapshot_dir: Path, run_id: str):
348
+ """
349
+ Called at the conclusion of `npx torusguard apply`.
350
+ Transitions findings to APPLIED 🔵 and records snapshot path.
351
+ """
352
+ report_file = get_report_path(target_root)
353
+ existing = parse_existing_report(report_file)
354
+ existing_map = existing["findings"]
355
+ now_str = format_timestamp()
356
+
357
+ for b in applied_bundles:
358
+ fid = b.get("finding_id")
359
+ rule_id = b.get("rule_id")
360
+ target = f"{b.get('target_file', '')}:{b.get('line_number', '')}"
361
+
362
+ matched_item = None
363
+ if fid and fid in existing_map:
364
+ matched_item = existing_map[fid]
365
+ else:
366
+ for item in existing_map.values():
367
+ if item["rule_id"] == rule_id and (target in item["target"] or item["target"] in target):
368
+ matched_item = item
369
+ break
370
+
371
+ if matched_item:
372
+ matched_item["status"] = "🔵 APPLIED"
373
+ matched_item["history"].append({
374
+ "timestamp": now_str,
375
+ "action": "Patch Applied",
376
+ "run_id": run_id,
377
+ "details": f"Candidate patch applied to disk. Pre-apply snapshot saved in {snapshot_dir.name}."
378
+ })
379
+
380
+ rendered = render_report(list(existing_map.values()), target_root)
381
+ report_file.write_text(rendered, encoding="utf-8")
382
+ return report_file
383
+
384
+
385
+ def record_recheck_results(target_root: Path, recheck_results: Dict[str, Any], run_id: str):
386
+ """
387
+ Called at the conclusion of `npx torusguard recheck` or `verify`.
388
+ Transitions findings to RESOLVED 🟢 or REGRESSED ❌.
389
+ """
390
+ report_file = get_report_path(target_root)
391
+ existing = parse_existing_report(report_file)
392
+ existing_map = existing["findings"]
393
+ now_str = format_timestamp()
394
+
395
+ fixed = recheck_results.get("fixed", [])
396
+ regressed = recheck_results.get("regressed", [])
397
+ remaining = recheck_results.get("remaining", [])
398
+
399
+ for f in fixed:
400
+ fid = f.get("finding_id")
401
+ if fid and fid in existing_map:
402
+ existing_map[fid]["status"] = "🟢 RESOLVED"
403
+ existing_map[fid]["history"].append({
404
+ "timestamp": now_str,
405
+ "action": "Closure Verified",
406
+ "run_id": run_id,
407
+ "details": "Targeted differential AST re-scan verified fix closure. Zero regressions."
408
+ })
409
+
410
+ for r in regressed:
411
+ fid = r.get("finding_id")
412
+ if fid and fid in existing_map:
413
+ existing_map[fid]["status"] = "❌ REGRESSED"
414
+ existing_map[fid]["history"].append({
415
+ "timestamp": now_str,
416
+ "action": "Regression Detected",
417
+ "run_id": run_id,
418
+ "details": "Vulnerability or new issue detected during recheck pass."
419
+ })
420
+
421
+ rendered = render_report(list(existing_map.values()), target_root)
422
+ report_file.write_text(rendered, encoding="utf-8")
423
+ return report_file
@@ -1,116 +1,116 @@
1
- #!/usr/bin/env python3
2
- """
3
- TorusGuard OASIS SARIF v2.1.0 Exporter
4
- Converts normalized TorusGuard findings into standard SARIF v2.1.0 format with
5
- GitHub Code Scanning multi-analysis category separation and partialFingerprints deduplication.
6
- """
7
-
8
- import sys
9
- import json
10
- import hashlib
11
- import argparse
12
- from typing import List, Dict, Any
13
-
14
- def generate_sarif(findings: List[Dict[str, Any]], run_id: str = "default", category: str = "torusguard/static") -> Dict[str, Any]:
15
- rules_map: Dict[str, Dict[str, Any]] = {}
16
- results: List[Dict[str, Any]] = []
17
-
18
- for f in findings:
19
- rule_id = f.get("rule_id", "TG-GEN-001")
20
- if rule_id not in rules_map:
21
- rules_map[rule_id] = {
22
- "id": rule_id,
23
- "name": f.get("title", rule_id),
24
- "shortDescription": {"text": f.get("title", rule_id)},
25
- "fullDescription": {"text": f.get("description", f.get("title", rule_id))},
26
- "defaultConfiguration": {
27
- "level": "error" if f.get("severity") in ("Critical", "High") else "warning"
28
- },
29
- "properties": {
30
- "tags": ["security", "torusguard", f.get("category", "general")]
31
- }
32
- }
33
-
34
- target = f.get("target", {})
35
- file_path = target.get("file_path", "unknown")
36
- line = target.get("start_line", 1)
37
-
38
- # Generate deduplication fingerprint
39
- seed = f"{rule_id}:{file_path}:{line}"
40
- line_hash = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:16]
41
-
42
- result: Dict[str, Any] = {
43
- "ruleId": rule_id,
44
- "message": {"text": f.get("title", "TorusGuard Security Finding")},
45
- "level": "error" if f.get("severity") in ("Critical", "High") else "warning",
46
- "locations": [
47
- {
48
- "physicalLocation": {
49
- "artifactLocation": {
50
- "uri": file_path,
51
- "uriBaseId": "%SRCROOT%"
52
- },
53
- "region": {
54
- "startLine": line,
55
- "startColumn": 1
56
- }
57
- }
58
- }
59
- ],
60
- "partialFingerprints": {
61
- "primaryLocationLineHash": line_hash
62
- },
63
- "properties": {
64
- "confidence": f.get("confidence", 70),
65
- "status": f.get("status", "Confirmed"),
66
- "finding_id": f.get("finding_id", "TG-FIND-UNKNOWN")
67
- }
68
- }
69
- results.append(result)
70
-
71
- sarif = {
72
- "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
73
- "version": "2.1.0",
74
- "runs": [
75
- {
76
- "tool": {
77
- "driver": {
78
- "name": "TorusGuard",
79
- "semanticVersion": "0.8.0",
80
- "informationUri": "https://github.com/githubmofo/TorusGuard",
81
- "rules": list(rules_map.values())
82
- }
83
- },
84
- "automationDetails": {
85
- "id": f"{category}/{run_id}"
86
- },
87
- "results": results
88
- }
89
- ]
90
- }
91
- return sarif
92
-
93
-
94
- def main():
95
- parser = argparse.ArgumentParser(description="TorusGuard SARIF v2.1.0 Exporter")
96
- parser.add_argument("--input", "-i", help="Path to findings JSON file")
97
- parser.add_argument("--output", "-o", help="Output path for sarif.json")
98
- parser.add_argument("--run-id", default="run-default", help="Unique execution Run ID")
99
- args = parser.parse_args()
100
-
101
- findings = []
102
- if args.input:
103
- with open(args.input, "r", encoding="utf-8") as f:
104
- findings = json.load(f)
105
-
106
- sarif = generate_sarif(findings, run_id=args.run_id)
107
-
108
- if args.output:
109
- with open(args.output, "w", encoding="utf-8") as f:
110
- json.dump(sarif, f, indent=2)
111
- print(f"SARIF log written to: {args.output}")
112
- else:
113
- print(json.dumps(sarif, indent=2))
114
-
115
- if __name__ == "__main__":
116
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ TorusGuard OASIS SARIF v2.1.0 Exporter
4
+ Converts normalized TorusGuard findings into standard SARIF v2.1.0 format with
5
+ GitHub Code Scanning multi-analysis category separation and partialFingerprints deduplication.
6
+ """
7
+
8
+ import sys
9
+ import json
10
+ import hashlib
11
+ import argparse
12
+ from typing import List, Dict, Any
13
+
14
+ def generate_sarif(findings: List[Dict[str, Any]], run_id: str = "default", category: str = "torusguard/static") -> Dict[str, Any]:
15
+ rules_map: Dict[str, Dict[str, Any]] = {}
16
+ results: List[Dict[str, Any]] = []
17
+
18
+ for f in findings:
19
+ rule_id = f.get("rule_id", "TG-GEN-001")
20
+ if rule_id not in rules_map:
21
+ rules_map[rule_id] = {
22
+ "id": rule_id,
23
+ "name": f.get("title", rule_id),
24
+ "shortDescription": {"text": f.get("title", rule_id)},
25
+ "fullDescription": {"text": f.get("description", f.get("title", rule_id))},
26
+ "defaultConfiguration": {
27
+ "level": "error" if f.get("severity") in ("Critical", "High") else "warning"
28
+ },
29
+ "properties": {
30
+ "tags": ["security", "torusguard", f.get("category", "general")]
31
+ }
32
+ }
33
+
34
+ target = f.get("target", {})
35
+ file_path = target.get("file_path", "unknown")
36
+ line = target.get("start_line", 1)
37
+
38
+ # Generate deduplication fingerprint
39
+ seed = f"{rule_id}:{file_path}:{line}"
40
+ line_hash = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:16]
41
+
42
+ result: Dict[str, Any] = {
43
+ "ruleId": rule_id,
44
+ "message": {"text": f.get("title", "TorusGuard Security Finding")},
45
+ "level": "error" if f.get("severity") in ("Critical", "High") else "warning",
46
+ "locations": [
47
+ {
48
+ "physicalLocation": {
49
+ "artifactLocation": {
50
+ "uri": file_path,
51
+ "uriBaseId": "%SRCROOT%"
52
+ },
53
+ "region": {
54
+ "startLine": line,
55
+ "startColumn": 1
56
+ }
57
+ }
58
+ }
59
+ ],
60
+ "partialFingerprints": {
61
+ "primaryLocationLineHash": line_hash
62
+ },
63
+ "properties": {
64
+ "confidence": f.get("confidence", 70),
65
+ "status": f.get("status", "Confirmed"),
66
+ "finding_id": f.get("finding_id", "TG-FIND-UNKNOWN")
67
+ }
68
+ }
69
+ results.append(result)
70
+
71
+ sarif = {
72
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
73
+ "version": "2.1.0",
74
+ "runs": [
75
+ {
76
+ "tool": {
77
+ "driver": {
78
+ "name": "TorusGuard",
79
+ "semanticVersion": "1.3.5",
80
+ "informationUri": "https://github.com/githubmofo/TorusGuard",
81
+ "rules": list(rules_map.values())
82
+ }
83
+ },
84
+ "automationDetails": {
85
+ "id": f"{category}/{run_id}"
86
+ },
87
+ "results": results
88
+ }
89
+ ]
90
+ }
91
+ return sarif
92
+
93
+
94
+ def main():
95
+ parser = argparse.ArgumentParser(description="TorusGuard SARIF v2.1.0 Exporter")
96
+ parser.add_argument("--input", "-i", help="Path to findings JSON file")
97
+ parser.add_argument("--output", "-o", help="Output path for sarif.json")
98
+ parser.add_argument("--run-id", default="run-default", help="Unique execution Run ID")
99
+ args = parser.parse_args()
100
+
101
+ findings = []
102
+ if args.input:
103
+ with open(args.input, "r", encoding="utf-8") as f:
104
+ findings = json.load(f)
105
+
106
+ sarif = generate_sarif(findings, run_id=args.run_id)
107
+
108
+ if args.output:
109
+ with open(args.output, "w", encoding="utf-8") as f:
110
+ json.dump(sarif, f, indent=2)
111
+ print(f"SARIF log written to: {args.output}")
112
+ else:
113
+ print(json.dumps(sarif, indent=2))
114
+
115
+ if __name__ == "__main__":
116
+ main()