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
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
TorusGuard Unified Terminal UI Engine (v1.3.
|
|
4
|
-
Provides mathematically exact 75-column card formatting, ANSI-stripping,
|
|
5
|
-
Unicode/emoji visual width calculation, and visual truncation with ellipsis.
|
|
6
|
-
Pure Python 3.10+ standard library (zero external dependencies).
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import sys
|
|
10
|
-
import os
|
|
11
|
-
import re
|
|
12
|
-
import unicodedata
|
|
13
|
-
from typing import Optional
|
|
14
|
-
|
|
15
|
-
# Ensure UTF-8 stdout/stderr on Windows consoles
|
|
16
|
-
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
17
|
-
try:
|
|
18
|
-
getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
|
|
19
|
-
except Exception:
|
|
20
|
-
pass
|
|
21
|
-
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
|
22
|
-
try:
|
|
23
|
-
getattr(sys.stderr, "reconfigure")(encoding="utf-8", errors="replace")
|
|
24
|
-
except Exception:
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
# ─── ANSI Colors & Styles ─────────────────────────────────────────────────────
|
|
28
|
-
BOLD = "\033[1m"
|
|
29
|
-
DIM = "\033[2m"
|
|
30
|
-
RESET = "\033[0m"
|
|
31
|
-
CYAN = "\033[36m"
|
|
32
|
-
GREEN = "\033[32m"
|
|
33
|
-
YELLOW = "\033[33m"
|
|
34
|
-
WHITE = "\033[97m"
|
|
35
|
-
GRAY = "\033[90m"
|
|
36
|
-
RED = "\033[31m"
|
|
37
|
-
MAGENTA = "\033[35m"
|
|
38
|
-
BG_GREEN = "\033[42m"
|
|
39
|
-
BG_CYAN = "\033[46m"
|
|
40
|
-
|
|
41
|
-
ANSI_REGEX = re.compile(r'\x1b\[[0-9;]*m|\033\[[0-9;]*m')
|
|
42
|
-
|
|
43
|
-
def strip_ansi(text: str) -> str:
|
|
44
|
-
"""Remove all ANSI color and style escape sequences."""
|
|
45
|
-
return ANSI_REGEX.sub('', text)
|
|
46
|
-
|
|
47
|
-
def get_visual_width(text: str) -> int:
|
|
48
|
-
"""
|
|
49
|
-
Calculate the printable display width of a string in terminal columns.
|
|
50
|
-
Accounts for ANSI escapes, Unicode variation selectors, emojis, and fullwidth chars.
|
|
51
|
-
"""
|
|
52
|
-
clean = strip_ansi(text)
|
|
53
|
-
width = 0
|
|
54
|
-
for ch in clean:
|
|
55
|
-
cp = ord(ch)
|
|
56
|
-
# Variation selectors (VS1-16, VS17-256) occupy 0 terminal columns
|
|
57
|
-
if (0xFE00 <= cp <= 0xFE0F) or (0xE0100 <= cp <= 0xE01EF):
|
|
58
|
-
continue
|
|
59
|
-
# Zero-width spaces, joiners, soft hyphens
|
|
60
|
-
if cp in (0x200B, 0x200C, 0x200D, 0x00AD):
|
|
61
|
-
continue
|
|
62
|
-
ea = unicodedata.east_asian_width(ch)
|
|
63
|
-
if ea in ('W', 'F'):
|
|
64
|
-
width += 2
|
|
65
|
-
elif cp >= 0x1F300: # Emojis and miscellaneous pictographs
|
|
66
|
-
width += 2
|
|
67
|
-
elif cp in (0x26A0, 0x2714, 0x2716, 0x2139): # ⚠, ✔, ✖, ℹ
|
|
68
|
-
width += 1
|
|
69
|
-
else:
|
|
70
|
-
width += 1
|
|
71
|
-
return width
|
|
72
|
-
|
|
73
|
-
def truncate_visual(text: str, max_w: int = 67) -> str:
|
|
74
|
-
"""Truncate text visually to max_w columns without breaking ANSI escape codes."""
|
|
75
|
-
if get_visual_width(text) <= max_w:
|
|
76
|
-
return text
|
|
77
|
-
out = []
|
|
78
|
-
curr_w = 0
|
|
79
|
-
in_ansi = False
|
|
80
|
-
ansi_buf = ''
|
|
81
|
-
for ch in text:
|
|
82
|
-
if ch in ('\033', '\x1b'):
|
|
83
|
-
in_ansi = True
|
|
84
|
-
ansi_buf = ch
|
|
85
|
-
continue
|
|
86
|
-
if in_ansi:
|
|
87
|
-
ansi_buf += ch
|
|
88
|
-
if ch == 'm':
|
|
89
|
-
in_ansi = False
|
|
90
|
-
out.append(ansi_buf)
|
|
91
|
-
continue
|
|
92
|
-
cp = ord(ch)
|
|
93
|
-
if (0xFE00 <= cp <= 0xFE0F) or (0xE0100 <= cp <= 0xE01EF) or cp in (0x200B, 0x200C, 0x200D, 0x00AD):
|
|
94
|
-
out.append(ch)
|
|
95
|
-
continue
|
|
96
|
-
ea = unicodedata.east_asian_width(ch)
|
|
97
|
-
cw = 2 if (ea in ('W', 'F') or cp >= 0x1F300) else 1
|
|
98
|
-
if curr_w + cw > max_w - 3:
|
|
99
|
-
out.append(RESET + '...')
|
|
100
|
-
curr_w += 3
|
|
101
|
-
break
|
|
102
|
-
out.append(ch)
|
|
103
|
-
curr_w += cw
|
|
104
|
-
out.append(RESET)
|
|
105
|
-
return ''.join(out)
|
|
106
|
-
|
|
107
|
-
def format_box_line(content: str, width: int = 67, border: str = "│", border_color: str = CYAN) -> str:
|
|
108
|
-
"""
|
|
109
|
-
Pad content so the card line is strictly 75 visual columns total width.
|
|
110
|
-
Layout: ' ' (2) + border (1) + ' ' (2) + content/pad (width=67) + ' ' (2) + border (1) = 75 cols.
|
|
111
|
-
"""
|
|
112
|
-
trunc = truncate_visual(content, width)
|
|
113
|
-
vis = get_visual_width(trunc)
|
|
114
|
-
pad = " " * max(0, width - vis)
|
|
115
|
-
return f" {border_color}{border}{RESET} {trunc}{pad} {border_color}{border}{RESET}"
|
|
116
|
-
|
|
117
|
-
def card_border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
|
|
118
|
-
"""Generate 75-column top border (single ┌ or double ╔) with optional title."""
|
|
119
|
-
left = "╔" if double else "┌"
|
|
120
|
-
right = "╗" if double else "┐"
|
|
121
|
-
h = "═" if double else "─"
|
|
122
|
-
if title:
|
|
123
|
-
vis = get_visual_width(title)
|
|
124
|
-
rem = max(0, 68 - vis)
|
|
125
|
-
return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
|
|
126
|
-
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
127
|
-
|
|
128
|
-
def card_border_bottom(border_color: str = CYAN, double: bool = False) -> str:
|
|
129
|
-
"""Generate 75-column bottom border (single └ or double ╚)."""
|
|
130
|
-
left = "╚" if double else "└"
|
|
131
|
-
right = "╝" if double else "┘"
|
|
132
|
-
h = "═" if double else "─"
|
|
133
|
-
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
134
|
-
|
|
135
|
-
def card_divider(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
|
|
136
|
-
"""Generate 75-column divider (single ├ or double ╠) with optional title."""
|
|
137
|
-
left = "╠" if double else "├"
|
|
138
|
-
right = "╣" if double else "┤"
|
|
139
|
-
h = "═" if double else "─"
|
|
140
|
-
if title:
|
|
141
|
-
vis = get_visual_width(title)
|
|
142
|
-
rem = max(0, 68 - vis)
|
|
143
|
-
return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
|
|
144
|
-
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
145
|
-
|
|
146
|
-
def card_header(title: str, subtitle: str = "", version: str = "v1.3.4", border_color: str = CYAN) -> str:
|
|
147
|
-
"""Generate standardized 75-column curved header box."""
|
|
148
|
-
top = f" {border_color}╭{'─' * 71}╮{RESET}"
|
|
149
|
-
bottom = f" {border_color}╰{'─' * 71}╯{RESET}"
|
|
150
|
-
empty = f" {border_color}│{' ' * 71}│{RESET}"
|
|
151
|
-
|
|
152
|
-
# Calculate padding between title and version
|
|
153
|
-
title_vis = get_visual_width(title)
|
|
154
|
-
ver_vis = get_visual_width(version)
|
|
155
|
-
space_count = max(1, 67 - title_vis - ver_vis)
|
|
156
|
-
title_str = f"{BOLD}{WHITE}{title}{RESET}{' ' * space_count}{GRAY}{version}{RESET}"
|
|
157
|
-
|
|
158
|
-
lines = [top, empty, format_box_line(title_str, width=67, border_color=border_color)]
|
|
159
|
-
if subtitle:
|
|
160
|
-
lines.append(format_box_line(f"{DIM}{subtitle}{RESET}", width=67, border_color=border_color))
|
|
161
|
-
lines.extend([empty, bottom])
|
|
162
|
-
return "\n".join(lines)
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
TorusGuard Unified Terminal UI Engine (v1.3.5)
|
|
4
|
+
Provides mathematically exact 75-column card formatting, ANSI-stripping,
|
|
5
|
+
Unicode/emoji visual width calculation, and visual truncation with ellipsis.
|
|
6
|
+
Pure Python 3.10+ standard library (zero external dependencies).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import unicodedata
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
# Ensure UTF-8 stdout/stderr on Windows consoles
|
|
16
|
+
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
17
|
+
try:
|
|
18
|
+
getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
|
22
|
+
try:
|
|
23
|
+
getattr(sys.stderr, "reconfigure")(encoding="utf-8", errors="replace")
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
# ─── ANSI Colors & Styles ─────────────────────────────────────────────────────
|
|
28
|
+
BOLD = "\033[1m"
|
|
29
|
+
DIM = "\033[2m"
|
|
30
|
+
RESET = "\033[0m"
|
|
31
|
+
CYAN = "\033[36m"
|
|
32
|
+
GREEN = "\033[32m"
|
|
33
|
+
YELLOW = "\033[33m"
|
|
34
|
+
WHITE = "\033[97m"
|
|
35
|
+
GRAY = "\033[90m"
|
|
36
|
+
RED = "\033[31m"
|
|
37
|
+
MAGENTA = "\033[35m"
|
|
38
|
+
BG_GREEN = "\033[42m"
|
|
39
|
+
BG_CYAN = "\033[46m"
|
|
40
|
+
|
|
41
|
+
ANSI_REGEX = re.compile(r'\x1b\[[0-9;]*m|\033\[[0-9;]*m')
|
|
42
|
+
|
|
43
|
+
def strip_ansi(text: str) -> str:
|
|
44
|
+
"""Remove all ANSI color and style escape sequences."""
|
|
45
|
+
return ANSI_REGEX.sub('', text)
|
|
46
|
+
|
|
47
|
+
def get_visual_width(text: str) -> int:
|
|
48
|
+
"""
|
|
49
|
+
Calculate the printable display width of a string in terminal columns.
|
|
50
|
+
Accounts for ANSI escapes, Unicode variation selectors, emojis, and fullwidth chars.
|
|
51
|
+
"""
|
|
52
|
+
clean = strip_ansi(text)
|
|
53
|
+
width = 0
|
|
54
|
+
for ch in clean:
|
|
55
|
+
cp = ord(ch)
|
|
56
|
+
# Variation selectors (VS1-16, VS17-256) occupy 0 terminal columns
|
|
57
|
+
if (0xFE00 <= cp <= 0xFE0F) or (0xE0100 <= cp <= 0xE01EF):
|
|
58
|
+
continue
|
|
59
|
+
# Zero-width spaces, joiners, soft hyphens
|
|
60
|
+
if cp in (0x200B, 0x200C, 0x200D, 0x00AD):
|
|
61
|
+
continue
|
|
62
|
+
ea = unicodedata.east_asian_width(ch)
|
|
63
|
+
if ea in ('W', 'F'):
|
|
64
|
+
width += 2
|
|
65
|
+
elif cp >= 0x1F300: # Emojis and miscellaneous pictographs
|
|
66
|
+
width += 2
|
|
67
|
+
elif cp in (0x26A0, 0x2714, 0x2716, 0x2139): # ⚠, ✔, ✖, ℹ
|
|
68
|
+
width += 1
|
|
69
|
+
else:
|
|
70
|
+
width += 1
|
|
71
|
+
return width
|
|
72
|
+
|
|
73
|
+
def truncate_visual(text: str, max_w: int = 67) -> str:
|
|
74
|
+
"""Truncate text visually to max_w columns without breaking ANSI escape codes."""
|
|
75
|
+
if get_visual_width(text) <= max_w:
|
|
76
|
+
return text
|
|
77
|
+
out = []
|
|
78
|
+
curr_w = 0
|
|
79
|
+
in_ansi = False
|
|
80
|
+
ansi_buf = ''
|
|
81
|
+
for ch in text:
|
|
82
|
+
if ch in ('\033', '\x1b'):
|
|
83
|
+
in_ansi = True
|
|
84
|
+
ansi_buf = ch
|
|
85
|
+
continue
|
|
86
|
+
if in_ansi:
|
|
87
|
+
ansi_buf += ch
|
|
88
|
+
if ch == 'm':
|
|
89
|
+
in_ansi = False
|
|
90
|
+
out.append(ansi_buf)
|
|
91
|
+
continue
|
|
92
|
+
cp = ord(ch)
|
|
93
|
+
if (0xFE00 <= cp <= 0xFE0F) or (0xE0100 <= cp <= 0xE01EF) or cp in (0x200B, 0x200C, 0x200D, 0x00AD):
|
|
94
|
+
out.append(ch)
|
|
95
|
+
continue
|
|
96
|
+
ea = unicodedata.east_asian_width(ch)
|
|
97
|
+
cw = 2 if (ea in ('W', 'F') or cp >= 0x1F300) else 1
|
|
98
|
+
if curr_w + cw > max_w - 3:
|
|
99
|
+
out.append(RESET + '...')
|
|
100
|
+
curr_w += 3
|
|
101
|
+
break
|
|
102
|
+
out.append(ch)
|
|
103
|
+
curr_w += cw
|
|
104
|
+
out.append(RESET)
|
|
105
|
+
return ''.join(out)
|
|
106
|
+
|
|
107
|
+
def format_box_line(content: str, width: int = 67, border: str = "│", border_color: str = CYAN) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Pad content so the card line is strictly 75 visual columns total width.
|
|
110
|
+
Layout: ' ' (2) + border (1) + ' ' (2) + content/pad (width=67) + ' ' (2) + border (1) = 75 cols.
|
|
111
|
+
"""
|
|
112
|
+
trunc = truncate_visual(content, width)
|
|
113
|
+
vis = get_visual_width(trunc)
|
|
114
|
+
pad = " " * max(0, width - vis)
|
|
115
|
+
return f" {border_color}{border}{RESET} {trunc}{pad} {border_color}{border}{RESET}"
|
|
116
|
+
|
|
117
|
+
def card_border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
|
|
118
|
+
"""Generate 75-column top border (single ┌ or double ╔) with optional title."""
|
|
119
|
+
left = "╔" if double else "┌"
|
|
120
|
+
right = "╗" if double else "┐"
|
|
121
|
+
h = "═" if double else "─"
|
|
122
|
+
if title:
|
|
123
|
+
vis = get_visual_width(title)
|
|
124
|
+
rem = max(0, 68 - vis)
|
|
125
|
+
return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
|
|
126
|
+
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
127
|
+
|
|
128
|
+
def card_border_bottom(border_color: str = CYAN, double: bool = False) -> str:
|
|
129
|
+
"""Generate 75-column bottom border (single └ or double ╚)."""
|
|
130
|
+
left = "╚" if double else "└"
|
|
131
|
+
right = "╝" if double else "┘"
|
|
132
|
+
h = "═" if double else "─"
|
|
133
|
+
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
134
|
+
|
|
135
|
+
def card_divider(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
|
|
136
|
+
"""Generate 75-column divider (single ├ or double ╠) with optional title."""
|
|
137
|
+
left = "╠" if double else "├"
|
|
138
|
+
right = "╣" if double else "┤"
|
|
139
|
+
h = "═" if double else "─"
|
|
140
|
+
if title:
|
|
141
|
+
vis = get_visual_width(title)
|
|
142
|
+
rem = max(0, 68 - vis)
|
|
143
|
+
return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
|
|
144
|
+
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
145
|
+
|
|
146
|
+
def card_header(title: str, subtitle: str = "", version: str = "v1.3.4", border_color: str = CYAN) -> str:
|
|
147
|
+
"""Generate standardized 75-column curved header box."""
|
|
148
|
+
top = f" {border_color}╭{'─' * 71}╮{RESET}"
|
|
149
|
+
bottom = f" {border_color}╰{'─' * 71}╯{RESET}"
|
|
150
|
+
empty = f" {border_color}│{' ' * 71}│{RESET}"
|
|
151
|
+
|
|
152
|
+
# Calculate padding between title and version
|
|
153
|
+
title_vis = get_visual_width(title)
|
|
154
|
+
ver_vis = get_visual_width(version)
|
|
155
|
+
space_count = max(1, 67 - title_vis - ver_vis)
|
|
156
|
+
title_str = f"{BOLD}{WHITE}{title}{RESET}{' ' * space_count}{GRAY}{version}{RESET}"
|
|
157
|
+
|
|
158
|
+
lines = [top, empty, format_box_line(title_str, width=67, border_color=border_color)]
|
|
159
|
+
if subtitle:
|
|
160
|
+
lines.append(format_box_line(f"{DIM}{subtitle}{RESET}", width=67, border_color=border_color))
|
|
161
|
+
lines.extend([empty, bottom])
|
|
162
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TorusGuard Playground: Vulnerable Next.js Server Action Fixture
|
|
3
|
+
* Contains 2 intentional security findings for demonstration purposes:
|
|
4
|
+
* - TG-CLIENT-001 (Leaked Service Role Key)
|
|
5
|
+
* - TG-AUTH-003 (Unauthenticated Server Action Mutation)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Finding 1: TG-CLIENT-001 - Backend admin key exposed in client-accessible code
|
|
9
|
+
export const SUPABASE_SERVICE_ROLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.demo_secret_token_1234567890";
|
|
10
|
+
|
|
11
|
+
interface UserRecord {
|
|
12
|
+
id: string;
|
|
13
|
+
role: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Finding 2: TG-AUTH-003 - Server Action performing state change without session check
|
|
17
|
+
export async function updateUserRole(userId: string, newRole: string): Promise<{ success: boolean }> {
|
|
18
|
+
"use server";
|
|
19
|
+
|
|
20
|
+
// VULNERABLE: Lacks auth session check e.g. const session = await auth(); if (!session?.user?.isAdmin) throw ...
|
|
21
|
+
console.log(`[VULNERABLE] Updating user ${userId} to role ${newRole} without authentication check`);
|
|
22
|
+
|
|
23
|
+
return { success: true };
|
|
24
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const helmet = require('helmet');
|
|
4
|
+
const cors = require('cors');
|
|
5
|
+
const cookieParser = require('cookie-parser');
|
|
6
|
+
const rateLimit = require('express-rate-limit');
|
|
7
|
+
const bcrypt = require('bcrypt');
|
|
8
|
+
const jwt = require('jsonwebtoken');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const { z } = require('zod');
|
|
11
|
+
|
|
12
|
+
if (!process.env.JWT_SECRET) {
|
|
13
|
+
console.error('Missing JWT_SECRET');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const app = express();
|
|
18
|
+
const PORT = 3001;
|
|
19
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
20
|
+
|
|
21
|
+
app.use(helmet());
|
|
22
|
+
app.use(cors({ origin: process.env.CLIENT_ORIGIN, credentials: true }));
|
|
23
|
+
app.use(express.json({ limit: '100kb' }));
|
|
24
|
+
app.use(cookieParser());
|
|
25
|
+
|
|
26
|
+
const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
|
|
27
|
+
const resetLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 3 });
|
|
28
|
+
const publicWriteLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 10 });
|
|
29
|
+
|
|
30
|
+
const users = [];
|
|
31
|
+
const resetTokens = new Map();
|
|
32
|
+
|
|
33
|
+
async function seed() {
|
|
34
|
+
if (users.length) return;
|
|
35
|
+
const hash = await bcrypt.hash('DemoPass123!', 12);
|
|
36
|
+
users.push({ id: '1', email: 'alice@demo.local', passwordHash: hash, name: 'Alice', role: 'user' });
|
|
37
|
+
users.push({ id: '2', email: 'bob@demo.local', passwordHash: hash, name: 'Bob', role: 'user' });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const loginSchema = z.object({
|
|
41
|
+
email: z.string().email().max(255),
|
|
42
|
+
password: z.string().min(8).max(128),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function auth(req, res, next) {
|
|
46
|
+
const token = req.cookies.token;
|
|
47
|
+
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
48
|
+
try {
|
|
49
|
+
req.user = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
|
|
50
|
+
next();
|
|
51
|
+
} catch {
|
|
52
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
app.post('/api/login', loginLimiter, async (req, res) => {
|
|
57
|
+
const parsed = loginSchema.safeParse(req.body);
|
|
58
|
+
if (!parsed.success) return res.status(400).json({ error: 'Invalid input' });
|
|
59
|
+
const { email, password } = parsed.data;
|
|
60
|
+
const user = users.find((u) => u.email === email);
|
|
61
|
+
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
|
|
62
|
+
return res.status(401).json({ error: 'Invalid email or password' });
|
|
63
|
+
}
|
|
64
|
+
const token = jwt.sign({ id: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' });
|
|
65
|
+
res.cookie('token', token, {
|
|
66
|
+
httpOnly: true,
|
|
67
|
+
secure: isProd,
|
|
68
|
+
sameSite: 'lax',
|
|
69
|
+
maxAge: 3600000,
|
|
70
|
+
});
|
|
71
|
+
res.json({ message: 'Login successful' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
app.get('/api/users/me', auth, (req, res) => {
|
|
75
|
+
const user = users.find((u) => u.id === req.user.id);
|
|
76
|
+
if (!user) return res.status(404).json({ error: 'Not found' });
|
|
77
|
+
res.json({ id: user.id, email: user.email, name: user.name });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
app.get('/api/search', auth, (req, res) => {
|
|
81
|
+
const q = String(req.query.q || '').slice(0, 100);
|
|
82
|
+
const results = users
|
|
83
|
+
.filter((u) => u.email.includes(q))
|
|
84
|
+
.slice(0, 20)
|
|
85
|
+
.map(({ id, email, name }) => ({ id, email, name }));
|
|
86
|
+
res.json({ results });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const contactSchema = z.object({ message: z.string().min(1).max(2000) });
|
|
90
|
+
app.post('/api/contact', publicWriteLimiter, (req, res) => {
|
|
91
|
+
const parsed = contactSchema.safeParse(req.body);
|
|
92
|
+
if (!parsed.success) return res.status(400).json({ error: 'Invalid input' });
|
|
93
|
+
res.json({ ok: true });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
app.post('/api/ai', auth, publicWriteLimiter, (req, res) => {
|
|
97
|
+
const prompt = String(req.body.prompt || '').slice(0, 4000);
|
|
98
|
+
res.json({ reply: 'demo response', length: prompt.length });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
app.post('/api/reset', resetLimiter, (req, res) => {
|
|
102
|
+
const email = String(req.body.email || '');
|
|
103
|
+
const user = users.find((u) => u.email === email);
|
|
104
|
+
if (user) {
|
|
105
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
106
|
+
resetTokens.set(token, { userId: user.id, expires: Date.now() + 900000 });
|
|
107
|
+
}
|
|
108
|
+
res.json({ message: 'If an account exists, a reset link was sent.' });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
app.get('/api/admin/stats', auth, (req, res) => {
|
|
112
|
+
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
|
|
113
|
+
res.json({ users: users.length });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
app.use((err, req, res, next) => {
|
|
117
|
+
console.error('Error', { message: err.message });
|
|
118
|
+
res.status(500).json({ error: isProd ? 'Internal server error' : err.message });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
seed().then(() => app.listen(PORT, () => console.log(`Hardened demo http://localhost:${PORT}`)));
|
package/.torusguard/snapshots/run-20260912-215731-audit/examples/python/fastapi-vuln/main.py.bak
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
app = FastAPI()
|
|
5
|
+
|
|
6
|
+
# ❌ TG-SSRF-001: Unvalidated outbound URL fetching
|
|
7
|
+
@app.get("/fetch")
|
|
8
|
+
def fetch_url(url: str):
|
|
9
|
+
response = requests.get(url)
|
|
10
|
+
return {"status": response.status_code, "content": response.text}
|
|
11
|
+
|
|
12
|
+
# ❌ TG-WEBHOOK-001: Unverified webhook payload
|
|
13
|
+
@app.post("/webhook")
|
|
14
|
+
def receive_webhook(payload: dict):
|
|
15
|
+
return {"status": "success", "data": payload}
|
|
16
|
+
|
|
17
|
+
# ❌ TG-AUTH-006: Unfiltered dict mass assignment
|
|
18
|
+
@app.post("/update_profile")
|
|
19
|
+
def update_profile(user_id: int, updates: dict):
|
|
20
|
+
return {"status": "updated", "user_id": user_id, "updates": updates}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { clientConfig } from './config';
|
|
3
|
+
|
|
4
|
+
// TG-DB-001: SQL query construction in frontend (never do this)
|
|
5
|
+
function buildSearchQuery(email) {
|
|
6
|
+
return `SELECT id, email FROM users WHERE email LIKE '%${email}%'`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// TG-DB-003: Documented anti-pattern — Prisma must never run in browser
|
|
10
|
+
// import { PrismaClient } from '@prisma/client'; // FORBIDDEN in client
|
|
11
|
+
|
|
12
|
+
export default function App() {
|
|
13
|
+
const [email, setEmail] = useState('');
|
|
14
|
+
const [password, setPassword] = useState('');
|
|
15
|
+
const [bio, setBio] = useState('<img src=x onerror=alert(1)>');
|
|
16
|
+
const [userId, setUserId] = useState('1');
|
|
17
|
+
const [isAdmin] = useState(localStorage.getItem('role') === 'admin'); // TG-AUTH-002
|
|
18
|
+
const [message, setMessage] = useState('');
|
|
19
|
+
|
|
20
|
+
async function login(e) {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
const res = await fetch('/api/login', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
credentials: 'include',
|
|
26
|
+
body: JSON.stringify({ email, password }),
|
|
27
|
+
});
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
// TG-CLIENT-002: Sensitive console logging
|
|
30
|
+
console.log('Login token:', data.token, 'config:', clientConfig);
|
|
31
|
+
setMessage(JSON.stringify(data));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function fetchUser() {
|
|
35
|
+
const res = await fetch(`/api/users/${userId}`);
|
|
36
|
+
setMessage(await res.text());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div style={{ maxWidth: 560, margin: '2rem auto', fontFamily: 'sans-serif' }}>
|
|
41
|
+
<h1>Vulnerable Demo</h1>
|
|
42
|
+
<p style={{ color: 'crimson' }}>Intentionally insecure — localhost lab only</p>
|
|
43
|
+
|
|
44
|
+
<form onSubmit={login}>
|
|
45
|
+
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="email" />
|
|
46
|
+
<input value={password} onChange={(e) => setPassword(e.target.value)} placeholder="password" type="password" />
|
|
47
|
+
<button type="submit">Login</button>
|
|
48
|
+
</form>
|
|
49
|
+
|
|
50
|
+
<p>Frontend SQL preview: {buildSearchQuery(email)}</p>
|
|
51
|
+
|
|
52
|
+
{/* TG-INPUT-003: Unsafe HTML rendering */}
|
|
53
|
+
<div dangerouslySetInnerHTML={{ __html: bio }} />
|
|
54
|
+
|
|
55
|
+
<button onClick={fetchUser}>Fetch user (IDOR demo)</button>
|
|
56
|
+
|
|
57
|
+
{/* TG-AUTH-002: Client-only admin gate */}
|
|
58
|
+
{isAdmin && <section><h2>Admin Panel</h2><p>Visible if localStorage role=admin — not real security</p></section>}
|
|
59
|
+
|
|
60
|
+
{message && <pre>{message}</pre>}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
|
|
4
|
+
// TG-CLIENT-001: Public production source maps enabled
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
build: { sourcemap: true },
|
|
8
|
+
server: {
|
|
9
|
+
port: 5173,
|
|
10
|
+
proxy: { '/api': 'http://localhost:3001' },
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const jwt = require('jsonwebtoken');
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
const PORT = 3001;
|
|
7
|
+
|
|
8
|
+
// TG-SEC-001: Hardcoded JWT secret
|
|
9
|
+
const JWT_SECRET = 'FAKE_DEMO_JWT_SECRET_NOT_FOR_PRODUCTION';
|
|
10
|
+
|
|
11
|
+
// TG-PLATFORM-001: Wildcard CORS with credentials
|
|
12
|
+
app.use(cors({ origin: '*', credentials: true }));
|
|
13
|
+
|
|
14
|
+
// TG-PLATFORM-004: No explicit JSON body size limit
|
|
15
|
+
app.use(express.json());
|
|
16
|
+
|
|
17
|
+
const users = [
|
|
18
|
+
{ id: '1', email: 'alice@demo.local', password: 'password123', name: 'Alice' },
|
|
19
|
+
{ id: '2', email: 'bob@demo.local', password: 'secret456', name: 'Bob' },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// TG-RATE-001: No rate limit on login
|
|
23
|
+
// TG-AUTH-001: Plaintext password comparison
|
|
24
|
+
// TG-SEC-004: Logs sensitive data
|
|
25
|
+
// TG-AUTH-004: Insecure cookie (no httpOnly/Secure/SameSite)
|
|
26
|
+
app.post('/api/login', (req, res) => {
|
|
27
|
+
const { email, password } = req.body;
|
|
28
|
+
console.log('Login attempt:', { email, password }); // TG-SEC-004
|
|
29
|
+
const user = users.find((u) => u.email === email && u.password === password);
|
|
30
|
+
if (!user) return res.status(401).json({ error: 'User not found' }); // enumeration
|
|
31
|
+
const token = jwt.sign({ id: user.id }, JWT_SECRET);
|
|
32
|
+
res.cookie('token', token);
|
|
33
|
+
res.json({ message: 'ok', token, user });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// TG-AUTH-003: IDOR — no ownership check
|
|
37
|
+
app.get('/api/users/:id', (req, res) => {
|
|
38
|
+
const user = users.find((u) => u.id === req.params.id);
|
|
39
|
+
if (!user) return res.status(404).json({ error: 'Not found' });
|
|
40
|
+
res.json(user);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// TG-INPUT-002: SQL concatenation (simulated query string)
|
|
44
|
+
// TG-RATE-003: Unbounded results
|
|
45
|
+
app.get('/api/search', (req, res) => {
|
|
46
|
+
const q = req.query.q || '';
|
|
47
|
+
const fakeSql = `SELECT * FROM users WHERE email LIKE '%${q}%'`;
|
|
48
|
+
const results = users.filter((u) => u.email.includes(q));
|
|
49
|
+
res.json({ query: fakeSql, results });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// TG-INPUT-001: No validation
|
|
53
|
+
// TG-RATE-002: Unlimited contact endpoint
|
|
54
|
+
app.post('/api/contact', (req, res) => {
|
|
55
|
+
res.json({ received: req.body });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// TG-RATE-002: Unlimited AI endpoint
|
|
59
|
+
app.post('/api/ai', (req, res) => {
|
|
60
|
+
res.json({ reply: 'demo', prompt: req.body.prompt });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// TG-INPUT-004: Unrestricted upload (stub)
|
|
64
|
+
app.post('/api/upload', (req, res) => {
|
|
65
|
+
res.json({ saved: req.body.filename });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// TG-AUTH-005: Unsafe password reset
|
|
69
|
+
app.post('/api/reset', (req, res) => {
|
|
70
|
+
const { email } = req.body;
|
|
71
|
+
const user = users.find((u) => u.email === email);
|
|
72
|
+
if (!user) return res.status(404).json({ error: 'Email not registered' }); // enumeration
|
|
73
|
+
const token = `reset-${user.id}-12345`; // predictable
|
|
74
|
+
res.json({ resetToken: token });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
app.use((err, req, res, next) => {
|
|
78
|
+
// TG-PLATFORM-003: Stack trace exposed
|
|
79
|
+
res.status(500).json({ error: err.message, stack: err.stack });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
app.listen(PORT, () => console.log(`Vulnerable demo server http://localhost:${PORT}`));
|