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.
- package/.torusguard/.manifest.json +66 -63
- package/.torusguard/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
- package/.torusguard/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
- package/.torusguard/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
- package/.torusguard/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
- package/.torusguard/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
- package/.torusguard/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
- package/.torusguard/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
- package/.torusguard/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
- package/.torusguard/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
- package/.torusguard/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
- package/.torusguard/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
- package/.torusguard/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
- package/.torusguard/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
- package/.torusguard/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
- package/.torusguard/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
- package/.torusguard/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
- package/.torusguard/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
- package/.torusguard/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
- package/.torusguard/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
- package/.torusguard/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
- package/.torusguard/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
- package/.torusguard/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
- package/.torusguard/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
- package/.torusguard/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
- package/.torusguard/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
- package/.torusguard/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
- package/.torusguard/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
- package/.torusguard/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
- package/.torusguard/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
- package/.torusguard/rules_catalog.json +520 -0
- package/.torusguard/runs/report-latest.html +93 -7
- package/.torusguard/runs/run-20260912-214110-audit/findings.json +1256 -0
- package/.torusguard/runs/run-20260912-214110-audit/findings.md +534 -0
- package/.torusguard/runs/run-20260912-214110-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260912-214110-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260912-214813-audit/findings.json +992 -0
- package/.torusguard/runs/run-20260912-214813-audit/findings.md +422 -0
- package/.torusguard/runs/run-20260912-214813-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260912-214813-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-auth-003-49-7c96f3/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215555-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215555-audit/findings.json +530 -0
- package/.torusguard/runs/run-20260912-215555-audit/findings.md +232 -0
- package/.torusguard/runs/run-20260912-215555-audit/manifest.json +13 -0
- package/.torusguard/runs/run-20260912-215555-audit/remediation.md +102 -0
- package/.torusguard/runs/run-20260912-215555-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260912-215731-audit/apply_plan.md +6 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-client-001-7-587129/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-db-002-9-5f7850/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/minimal_patch_plan.md +22 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-001-27-2486f1/patch.diff +10 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-001-12-cf8156/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/minimal_patch_plan.md +23 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-17-876973/patch.diff +11 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/minimal_patch_plan.md +23 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-platform-002-5-40c794/patch.diff +11 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-001-26-affcfa/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-rate-002-54-0b38d2/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-18-a3c1e8/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-001-9-e22e50/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-28-a1a0a1/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-sec-004-30-00569e/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/metadata.json +17 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260912-215731-audit/bundles/bnd-tg-ssrf-004-9-9803ad/patch.diff +9 -0
- package/.torusguard/runs/run-20260912-215731-audit/diff_summary.md +186 -0
- package/.torusguard/runs/run-20260912-215731-audit/findings.json +497 -0
- package/.torusguard/runs/run-20260912-215731-audit/findings.md +218 -0
- package/.torusguard/runs/run-20260912-215731-audit/manifest.json +14 -0
- package/.torusguard/runs/run-20260912-215731-audit/recheck.md +76 -0
- package/.torusguard/runs/run-20260912-215731-audit/remediation.md +234 -0
- package/.torusguard/runs/run-20260912-215731-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260912-220134-audit/findings.json +68 -0
- package/.torusguard/runs/run-20260912-220134-audit/findings.md +36 -0
- package/.torusguard/runs/run-20260912-220134-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260912-220134-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260912-220200-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260912-220200-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260912-220200-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260912-220200-audit/recheck.md +6 -0
- package/.torusguard/runs/run-20260912-220200-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260912-220227-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260912-220227-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260912-220227-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260912-220227-audit/recheck.md +6 -0
- package/.torusguard/runs/run-20260912-220227-audit/summary.md +5 -0
- package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
- package/.torusguard/scripts/apply_runner.py +383 -377
- package/.torusguard/scripts/audit_runner.py +1306 -653
- package/.torusguard/scripts/finding_scorer.py +14 -2
- package/.torusguard/scripts/harden_runner.py +628 -455
- package/.torusguard/scripts/recheck_runner.py +248 -238
- package/.torusguard/scripts/recipes_runner.py +137 -137
- package/.torusguard/scripts/report_sync.py +423 -0
- package/.torusguard/scripts/sarif_exporter.py +116 -116
- package/.torusguard/scripts/term_ui.py +162 -162
- package/.torusguard/snapshots/run-20260912-215731-audit/demo/playground/vulnerable_nextjs/actions.ts.bak +24 -0
- package/.torusguard/snapshots/run-20260912-215731-audit/examples/hardened-react-express/server/index.js.bak +121 -0
- package/.torusguard/snapshots/run-20260912-215731-audit/examples/python/fastapi-vuln/main.py.bak +20 -0
- package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/src/App.jsx.bak +63 -0
- package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/client/vite.config.js.bak +12 -0
- package/.torusguard/snapshots/run-20260912-215731-audit/examples/vulnerable-react-express/server/index.js.bak +82 -0
- package/.torusguard/workflows/apply.md +11 -4
- package/.torusguard/workflows/audit.md +7 -1
- package/.torusguard/workflows/authorize.md +2 -1
- package/.torusguard/workflows/exploit-check.md +2 -1
- package/.torusguard/workflows/harden.md +7 -1
- package/.torusguard/workflows/init.md +6 -1
- package/.torusguard/workflows/memory.md +2 -1
- package/.torusguard/workflows/recheck.md +7 -1
- package/.torusguard/workflows/report.md +7 -1
- package/.torusguard/workflows/status.md +6 -1
- package/.torusguard/workflows/verify.md +6 -1
- package/.torusguard/workflows/web-validate.md +2 -1
- package/README.md +470 -345
- package/bin/torusguard.js +550 -545
- package/package.json +1 -1
- package/skills/torusguard/SKILL.md +73 -72
- package/skills/torusguard/payload/.manifest.json +66 -63
- package/skills/torusguard/payload/rules/business-logic/TG-BIZ-001-client-controlled-sensitive-business-value.md +30 -14
- package/skills/torusguard/payload/rules/business-logic/TG-BIZ-002-replayable-one-time-operation.md +33 -16
- package/skills/torusguard/payload/rules/business-logic/TG-BIZ-003-missing-workflow-state-validation.md +31 -15
- package/skills/torusguard/payload/rules/business-logic/TG-BIZ-004-unrestricted-sensitive-business-flow.md +26 -16
- package/skills/torusguard/payload/rules/cache/TG-CACHE-001-sensitive-response-publicly-cacheable.md +24 -16
- package/skills/torusguard/payload/rules/cache/TG-CACHE-002-missing-user-cache-isolation.md +32 -18
- package/skills/torusguard/payload/rules/cache/TG-CACHE-003-sensitive-data-in-url.md +24 -17
- package/skills/torusguard/payload/rules/csrf/TG-CSRF-001-missing-csrf-protection.md +31 -18
- package/skills/torusguard/payload/rules/csrf/TG-CSRF-002-unsafe-credentialed-cross-origin-request.md +28 -17
- package/skills/torusguard/payload/rules/graphql/TG-GQL-002-resolver-missing-authorization.md +33 -17
- package/skills/torusguard/payload/rules/graphql/TG-GQL-003-unbounded-graphql-pagination-or-batching.md +31 -16
- package/skills/torusguard/payload/rules/graphql/TG-GQL-004-unnecessary-production-introspection.md +25 -16
- package/skills/torusguard/payload/rules/secrets/TG-SEC-005-secret-in-git-history.md +24 -19
- package/skills/torusguard/payload/rules/secrets/TG-SEC-006-secret-in-build-artifact.md +36 -19
- package/skills/torusguard/payload/rules/secrets/TG-SEC-007-secret-in-test-or-example-fixture.md +21 -16
- package/skills/torusguard/payload/rules/ssrf/TG-SSRF-002-missing-internal-network-protection.md +45 -18
- package/skills/torusguard/payload/rules/ssrf/TG-SSRF-003-unsafe-redirect-following.md +20 -16
- package/skills/torusguard/payload/rules/ssrf/TG-SSRF-004-unbounded-outbound-request.md +28 -19
- package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-001-missing-or-ignored-dependency-lockfile.md +25 -18
- package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-003-unsafe-ci-cd-secret-exposure.md +29 -19
- package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-004-unpinned-ci-action.md +23 -18
- package/skills/torusguard/payload/rules/supply-chain/TG-SUPPLY-005-unsafe-dependency-install-script.md +23 -18
- package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-001-missing-webhook-signature-verification.md +36 -16
- package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-002-replayable-webhook.md +26 -17
- package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-003-missing-webhook-idempotency.md +30 -16
- package/skills/torusguard/payload/rules/webhook/TG-WEBHOOK-004-unvalidated-webhook-event-type.md +25 -16
- package/skills/torusguard/payload/rules/websocket/TG-WS-002-missing-channel-level-authorization.md +28 -17
- package/skills/torusguard/payload/rules/websocket/TG-WS-003-unvalidated-websocket-message.md +31 -17
- package/skills/torusguard/payload/rules/websocket/TG-WS-004-unbounded-websocket-connections.md +31 -17
- package/skills/torusguard/payload/rules_catalog.json +520 -0
- package/skills/torusguard/payload/scripts/apply_runner.py +383 -377
- package/skills/torusguard/payload/scripts/audit_runner.py +1306 -653
- package/skills/torusguard/payload/scripts/finding_scorer.py +14 -2
- package/skills/torusguard/payload/scripts/harden_runner.py +628 -455
- package/skills/torusguard/payload/scripts/recheck_runner.py +248 -238
- package/skills/torusguard/payload/scripts/recipes_runner.py +137 -137
- package/skills/torusguard/payload/scripts/report_sync.py +423 -0
- package/skills/torusguard/payload/scripts/sarif_exporter.py +116 -116
- package/skills/torusguard/payload/scripts/term_ui.py +162 -162
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +75 -72
- package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +84 -80
- package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +87 -83
- package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +7 -2
- package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +7 -2
- package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +7 -2
- package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +98 -94
- package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +68 -64
- package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +81 -77
- package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +76 -72
- package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +62 -58
- package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +7 -2
- package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +7 -2
- package/skills/torusguard/payload/workflows/apply.md +11 -4
- package/skills/torusguard/payload/workflows/audit.md +7 -1
- package/skills/torusguard/payload/workflows/authorize.md +2 -1
- package/skills/torusguard/payload/workflows/exploit-check.md +2 -1
- package/skills/torusguard/payload/workflows/harden.md +7 -1
- package/skills/torusguard/payload/workflows/init.md +6 -1
- package/skills/torusguard/payload/workflows/memory.md +2 -1
- package/skills/torusguard/payload/workflows/recheck.md +7 -1
- package/skills/torusguard/payload/workflows/report.md +7 -1
- package/skills/torusguard/payload/workflows/status.md +6 -1
- package/skills/torusguard/payload/workflows/verify.md +6 -1
- package/skills/torusguard/payload/workflows/web-validate.md +2 -1
- package/skills/torusguard-apply/SKILL.md +84 -80
- package/skills/torusguard-audit/SKILL.md +87 -83
- package/skills/torusguard-authorize/SKILL.md +7 -2
- package/skills/torusguard-exploit-check/SKILL.md +7 -2
- package/skills/torusguard-full/SKILL.md +7 -2
- package/skills/torusguard-harden/SKILL.md +98 -94
- package/skills/torusguard-init/SKILL.md +68 -64
- package/skills/torusguard-recheck/SKILL.md +81 -77
- package/skills/torusguard-report/SKILL.md +76 -72
- package/skills/torusguard-status/SKILL.md +62 -58
- package/skills/torusguard-verify/SKILL.md +7 -2
- 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": "
|
|
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()
|