torusguard 1.3.5 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.torusguard/.manifest.json +35 -35
  2. package/.torusguard/runs/report-latest.html +2685 -303
  3. package/.torusguard/runs/results-latest.sarif +20 -0
  4. package/.torusguard/runs/run-20260918-104221-audit/findings.json +1 -0
  5. package/.torusguard/runs/run-20260918-104221-audit/findings.md +8 -0
  6. package/.torusguard/runs/run-20260918-104221-audit/manifest.json +12 -0
  7. package/.torusguard/runs/run-20260918-104221-audit/summary.md +5 -0
  8. package/.torusguard/runs/run-20260918-104236-audit/findings.json +2314 -0
  9. package/.torusguard/runs/run-20260918-104236-audit/findings.md +960 -0
  10. package/.torusguard/runs/run-20260918-104236-audit/manifest.json +12 -0
  11. package/.torusguard/runs/run-20260918-104236-audit/summary.md +5 -0
  12. package/.torusguard/runs/run-20260918-105723-audit/findings.json +2382 -0
  13. package/.torusguard/runs/run-20260918-105723-audit/findings.md +960 -0
  14. package/.torusguard/runs/run-20260918-105723-audit/manifest.json +12 -0
  15. package/.torusguard/runs/run-20260918-105723-audit/summary.md +5 -0
  16. package/.torusguard/runs/run-20260918-105731-audit/findings.json +1 -0
  17. package/.torusguard/runs/run-20260918-105731-audit/findings.md +8 -0
  18. package/.torusguard/runs/run-20260918-105731-audit/manifest.json +12 -0
  19. package/.torusguard/runs/run-20260918-105731-audit/summary.md +5 -0
  20. package/.torusguard/runs/run-20260918-105940-audit/findings.json +1 -0
  21. package/.torusguard/runs/run-20260918-105940-audit/findings.md +8 -0
  22. package/.torusguard/runs/run-20260918-105940-audit/manifest.json +12 -0
  23. package/.torusguard/runs/run-20260918-105940-audit/summary.md +5 -0
  24. package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  25. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  26. package/.torusguard/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
  27. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  28. package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  29. package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  30. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  31. package/.torusguard/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
  32. package/.torusguard/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
  33. package/.torusguard/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
  34. package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  35. package/.torusguard/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
  36. package/.torusguard/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  37. package/.torusguard/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
  38. package/.torusguard/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  39. package/.torusguard/scripts/apply_runner.py +11 -5
  40. package/.torusguard/scripts/audit_runner.py +222 -15
  41. package/.torusguard/scripts/harden_runner.py +3 -3
  42. package/.torusguard/scripts/html_reporter.py +2668 -648
  43. package/.torusguard/scripts/recheck_runner.py +3 -3
  44. package/.torusguard/scripts/recipes_runner.py +3 -3
  45. package/.torusguard/scripts/report_sync.py +370 -74
  46. package/.torusguard/scripts/sarif_exporter.py +48 -8
  47. package/.torusguard/scripts/stack_detect.py +44 -2
  48. package/.torusguard/scripts/term_ui.py +1 -1
  49. package/.torusguard/skills/torusguard/SKILL.md +75 -72
  50. package/.torusguard/skills/torusguard-apply/SKILL.md +84 -80
  51. package/.torusguard/skills/torusguard-audit/SKILL.md +87 -83
  52. package/.torusguard/skills/torusguard-authorize/SKILL.md +7 -2
  53. package/.torusguard/skills/torusguard-exploit-check/SKILL.md +7 -2
  54. package/.torusguard/skills/torusguard-full/SKILL.md +7 -2
  55. package/.torusguard/skills/torusguard-harden/SKILL.md +98 -94
  56. package/.torusguard/skills/torusguard-init/SKILL.md +68 -64
  57. package/.torusguard/skills/torusguard-recheck/SKILL.md +81 -77
  58. package/.torusguard/skills/torusguard-report/SKILL.md +93 -72
  59. package/.torusguard/skills/torusguard-status/SKILL.md +62 -58
  60. package/.torusguard/skills/torusguard-verify/SKILL.md +7 -2
  61. package/.torusguard/skills/torusguard-web-validate/SKILL.md +7 -2
  62. package/.torusguard/workflows/apply.md +1 -1
  63. package/.torusguard/workflows/audit.md +1 -1
  64. package/.torusguard/workflows/authorize.md +1 -1
  65. package/.torusguard/workflows/exploit-check.md +1 -1
  66. package/.torusguard/workflows/harden.md +1 -1
  67. package/.torusguard/workflows/init.md +1 -1
  68. package/.torusguard/workflows/memory.md +1 -1
  69. package/.torusguard/workflows/recheck.md +1 -1
  70. package/.torusguard/workflows/report.md +1 -1
  71. package/.torusguard/workflows/status.md +1 -1
  72. package/.torusguard/workflows/verify.md +1 -1
  73. package/.torusguard/workflows/web-validate.md +1 -1
  74. package/README.md +14 -8
  75. package/bin/torusguard.js +56 -20
  76. package/package.json +1 -1
  77. package/skills/torusguard/SKILL.md +1 -1
  78. package/skills/torusguard/bootstrap.py +7 -2
  79. package/skills/torusguard/payload/.manifest.json +35 -35
  80. package/skills/torusguard/payload/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  81. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
  82. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  83. package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
  84. package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
  85. package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
  86. package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  87. package/skills/torusguard/payload/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  88. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-311.pyc +0 -0
  89. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  90. package/skills/torusguard/payload/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  91. package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-311.pyc +0 -0
  92. package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
  93. package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-311.pyc +0 -0
  94. package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
  95. package/skills/torusguard/payload/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
  96. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
  97. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  98. package/skills/torusguard/payload/scripts/__pycache__/rules_sync.cpython-311.pyc +0 -0
  99. package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-311.pyc +0 -0
  100. package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
  101. package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  102. package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-311.pyc +0 -0
  103. package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
  104. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
  105. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  106. package/skills/torusguard/payload/scripts/apply_runner.py +11 -5
  107. package/skills/torusguard/payload/scripts/audit_runner.py +222 -15
  108. package/skills/torusguard/payload/scripts/harden_runner.py +3 -3
  109. package/skills/torusguard/payload/scripts/html_reporter.py +2668 -648
  110. package/skills/torusguard/payload/scripts/recheck_runner.py +3 -3
  111. package/skills/torusguard/payload/scripts/recipes_runner.py +3 -3
  112. package/skills/torusguard/payload/scripts/report_sync.py +370 -74
  113. package/skills/torusguard/payload/scripts/sarif_exporter.py +48 -8
  114. package/skills/torusguard/payload/scripts/stack_detect.py +44 -2
  115. package/skills/torusguard/payload/scripts/term_ui.py +1 -1
  116. package/skills/torusguard/payload/skills/torusguard/SKILL.md +1 -1
  117. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +1 -1
  118. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +1 -1
  119. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +1 -1
  120. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +1 -1
  121. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +1 -1
  122. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +1 -1
  123. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +1 -1
  124. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +1 -1
  125. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +40 -23
  126. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +1 -1
  127. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +1 -1
  128. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +1 -1
  129. package/skills/torusguard/payload/workflows/apply.md +1 -1
  130. package/skills/torusguard/payload/workflows/audit.md +1 -1
  131. package/skills/torusguard/payload/workflows/authorize.md +1 -1
  132. package/skills/torusguard/payload/workflows/exploit-check.md +1 -1
  133. package/skills/torusguard/payload/workflows/harden.md +1 -1
  134. package/skills/torusguard/payload/workflows/init.md +1 -1
  135. package/skills/torusguard/payload/workflows/memory.md +1 -1
  136. package/skills/torusguard/payload/workflows/recheck.md +1 -1
  137. package/skills/torusguard/payload/workflows/report.md +1 -1
  138. package/skills/torusguard/payload/workflows/status.md +1 -1
  139. package/skills/torusguard/payload/workflows/verify.md +1 -1
  140. package/skills/torusguard/payload/workflows/web-validate.md +1 -1
  141. package/skills/torusguard-apply/SKILL.md +1 -1
  142. package/skills/torusguard-audit/SKILL.md +1 -1
  143. package/skills/torusguard-authorize/SKILL.md +1 -1
  144. package/skills/torusguard-exploit-check/SKILL.md +1 -1
  145. package/skills/torusguard-full/SKILL.md +1 -1
  146. package/skills/torusguard-harden/SKILL.md +1 -1
  147. package/skills/torusguard-init/SKILL.md +1 -1
  148. package/skills/torusguard-recheck/SKILL.md +1 -1
  149. package/skills/torusguard-report/SKILL.md +40 -23
  150. package/skills/torusguard-status/SKILL.md +1 -1
  151. package/skills/torusguard-verify/SKILL.md +1 -1
  152. package/skills/torusguard-web-validate/SKILL.md +1 -1
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- TorusGuard Autonomous Static Security Audit Engine (v1.3.5)
3
+ TorusGuard Autonomous Static Security Audit Engine (v1.3.6)
4
4
  Multi-language static pattern & AST security scanner.
5
5
  Evaluates source trees against canonical TorusGuard rule families,
6
6
  augments confidence via the persistent security memory subsystem,
@@ -117,7 +117,7 @@ except Exception:
117
117
  rem = max(0, 68 - vis)
118
118
  return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
119
119
  return f" {border_color}{left}{h * 71}{right}{RESET}"
120
- def card_header(title: str, subtitle: str = "", version: str = "v1.3.5", border_color: str = CYAN) -> str:
120
+ def card_header(title: str, subtitle: str = "", version: str = "v1.3.6", border_color: str = CYAN) -> str:
121
121
  top = f" {border_color}╭{'─' * 71}╮{RESET}"
122
122
  bottom = f" {border_color}╰{'─' * 71}╯{RESET}"
123
123
  empty = f" {border_color}│{' ' * 71}│{RESET}"
@@ -948,6 +948,153 @@ RULE_PATTERNS: List[RulePattern] = [
948
948
  }
949
949
  ]
950
950
 
951
+ # ─── Rule Precision Map ───────────────────────────────────────────────────────
952
+ # Calibrates how precise each rule's regex is (0.0 = very broad, 1.0 = definitive)
953
+ # Used to adjust confidence scores: broad rules get lower base evidence quality.
954
+ RULE_PRECISION: Dict[str, float] = {
955
+ "TG-SEC-001": 0.90, "TG-SEC-002": 0.85, "TG-SEC-003": 0.70,
956
+ "TG-SEC-004": 0.55, "TG-SEC-005": 0.80, "TG-SEC-006": 0.75,
957
+ "TG-SEC-007": 0.95,
958
+ "TG-AUTH-001": 0.85, "TG-AUTH-002": 0.50, "TG-AUTH-003": 0.70,
959
+ "TG-AUTH-004": 0.80, "TG-AUTH-005": 0.75, "TG-AUTH-006": 0.65,
960
+ "TG-AUTH-007": 0.60, "TG-AUTH-008": 0.70,
961
+ "TG-DB-001": 0.75, "TG-DB-002": 0.90, "TG-DB-003": 0.80, "TG-DB-004": 0.55,
962
+ "TG-INPUT-001": 0.40, "TG-INPUT-002": 0.85, "TG-INPUT-003": 0.35,
963
+ "TG-INPUT-004": 0.80, "TG-INPUT-005": 0.75, "TG-INPUT-006": 0.70,
964
+ "TG-RATE-001": 0.45, "TG-RATE-002": 0.45, "TG-RATE-003": 0.50,
965
+ "TG-AGENT-001": 0.80, "TG-AGENT-002": 0.85, "TG-AGENT-003": 0.60,
966
+ "TG-AGENT-004": 0.55,
967
+ "TG-SSRF-001": 0.75, "TG-SSRF-002": 0.90, "TG-SSRF-003": 0.50,
968
+ "TG-SSRF-004": 0.65,
969
+ "TG-WEBHOOK-001": 0.55, "TG-WEBHOOK-002": 0.50, "TG-WEBHOOK-003": 0.45,
970
+ "TG-WEBHOOK-004": 0.60,
971
+ "TG-WS-001": 0.50, "TG-WS-002": 0.55, "TG-WS-003": 0.50, "TG-WS-004": 0.55,
972
+ "TG-CSRF-001": 0.60, "TG-CSRF-002": 0.80,
973
+ "TG-GQL-001": 0.55, "TG-GQL-002": 0.50, "TG-GQL-003": 0.50, "TG-GQL-004": 0.85,
974
+ "TG-SUPPLY-001": 0.90, "TG-SUPPLY-002": 0.80, "TG-SUPPLY-003": 0.85,
975
+ "TG-SUPPLY-004": 0.60, "TG-SUPPLY-005": 0.80, "TG-SUPPLY-006": 0.85,
976
+ "TG-BIZ-001": 0.60, "TG-BIZ-002": 0.55, "TG-BIZ-003": 0.65, "TG-BIZ-004": 0.70,
977
+ "TG-CACHE-001": 0.65, "TG-CACHE-002": 0.50, "TG-CACHE-003": 0.55,
978
+ "TG-CLIENT-001": 0.80, "TG-CLIENT-002": 0.75,
979
+ "TG-PLATFORM-001": 0.70, "TG-PLATFORM-002": 0.50, "TG-PLATFORM-003": 0.75,
980
+ "TG-PLATFORM-004": 0.60,
981
+ "TG-DIFF-001": 0.90, "TG-DIFF-002": 0.85, "TG-DIFF-003": 0.80,
982
+ "TG-EDGE-001": 0.55, "TG-EDGE-003": 0.60,
983
+ }
984
+
985
+ # ─── File-Type Discrimination ─────────────────────────────────────────────────
986
+ # Maps file path patterns to sets of rule families that should be SUPPRESSED
987
+ FILE_TYPE_SUPPRESSIONS: List[Tuple[re.Pattern, set]] = [
988
+ # Config & build tool files — suppress injection rules for eval/exec
989
+ (re.compile(r'(?:webpack|vite|rollup|esbuild|jest|vitest|babel|eslint|prettier|postcss|tailwind)\.config', re.IGNORECASE),
990
+ {"TG-INPUT-003"}),
991
+ # Type declaration files — no runtime code
992
+ (re.compile(r'\.d\.ts$'),
993
+ {"TG-INPUT-001", "TG-INPUT-002", "TG-INPUT-003", "TG-INPUT-004", "TG-INPUT-005",
994
+ "TG-RATE-001", "TG-RATE-002", "TG-RATE-003", "TG-DB-001", "TG-DB-004"}),
995
+ # .env.example / .env.sample — template files, not real credentials
996
+ (re.compile(r'\.env\.(?:example|sample|template|defaults)$', re.IGNORECASE),
997
+ {"TG-SEC-003", "TG-SEC-001"}),
998
+ # Lock files — suppress secret scanning
999
+ (re.compile(r'(?:package-lock|yarn\.lock|pnpm-lock|Pipfile\.lock|poetry\.lock)'),
1000
+ {"TG-SEC-001", "TG-SEC-005"}),
1001
+ # Migration files — suppress input validation rules (generated SQL)
1002
+ (re.compile(r'/migrations?/', re.IGNORECASE),
1003
+ {"TG-INPUT-001", "TG-INPUT-002", "TG-RATE-001"}),
1004
+ # Dockerfiles — suppress frontend-only rules, keep container-specific rules
1005
+ (re.compile(r'Dockerfile', re.IGNORECASE),
1006
+ {"TG-INPUT-003", "TG-RATE-001", "TG-RATE-002", "TG-DB-001", "TG-GQL-001"}),
1007
+ ]
1008
+
1009
+ def get_suppressed_rules_for_file(file_path: str) -> set:
1010
+ """Return the set of rule IDs that should be suppressed for this file type."""
1011
+ suppressed: set = set()
1012
+ for pattern, rules in FILE_TYPE_SUPPRESSIONS:
1013
+ if pattern.search(file_path):
1014
+ suppressed.update(rules)
1015
+ return suppressed
1016
+
1017
+
1018
+ # ─── Context-Aware Validation Engine ──────────────────────────────────────────
1019
+ def validate_finding_context(
1020
+ rule_id: str, line_idx: int, lines: List[str],
1021
+ file_path: str, full_content: str
1022
+ ) -> bool:
1023
+ """
1024
+ Post-match context validation. Returns False if the finding is a known
1025
+ false-positive pattern based on surrounding code context.
1026
+ """
1027
+ # Compute lookahead (next 5 lines) and lookbehind (prev 5 lines)
1028
+ lookahead = "\n".join(lines[line_idx:min(line_idx + 6, len(lines))])
1029
+ lookbehind = "\n".join(lines[max(0, line_idx - 5):line_idx])
1030
+
1031
+ # ── TG-INPUT-001: req.body destructuring followed by schema validation ────
1032
+ if rule_id == "TG-INPUT-001":
1033
+ if re.search(r'\.(parse|validate|safeParse|check|verify|sanitize)\s*\(', lookahead):
1034
+ return False
1035
+ # Zod/Joi/Yup schema declared before destructuring
1036
+ if re.search(r'(?:z\.|Joi\.|yup\.|schema\.).*\.(parse|validate)', lookbehind + lookahead):
1037
+ return False
1038
+
1039
+ # ── TG-INPUT-003: eval/exec in sandboxed or build-tool context ────────────
1040
+ if rule_id == "TG-INPUT-003":
1041
+ current_line = lines[line_idx].strip() if line_idx < len(lines) else ""
1042
+ # Suppress if it's a JSON.parse (not dangerous)
1043
+ if "JSON.parse" in current_line or "json.loads" in current_line:
1044
+ return False
1045
+ # Suppress eval in CSS-in-JS tagged templates
1046
+ if re.search(r'styled|css`|keyframes', lookbehind):
1047
+ return False
1048
+
1049
+ # ── TG-RATE-001/002: Rate limiter already applied earlier in file ─────────
1050
+ if rule_id in ("TG-RATE-001", "TG-RATE-002"):
1051
+ if re.search(r'(?:rateLimit|rateLimiter|slowDown|throttle|express-rate-limit)\s*\(', full_content):
1052
+ return False
1053
+ # Check if middleware is applied on the same router
1054
+ if re.search(r'app\.use\s*\([^)]*(?:rateLimit|limiter|throttle)', full_content):
1055
+ return False
1056
+
1057
+ # ── TG-PLATFORM-002: Helmet used anywhere in the file or app setup ────────
1058
+ if rule_id == "TG-PLATFORM-002":
1059
+ if "helmet" in full_content and re.search(r'(?:app|server)\.use\s*\(\s*helmet', full_content):
1060
+ return False
1061
+
1062
+ # ── TG-SSRF-004: requests.get with timeout in kwargs or session defaults ──
1063
+ if rule_id == "TG-SSRF-004":
1064
+ # Check if a session with default timeout is set earlier
1065
+ if re.search(r'session\.(?:request\.)?timeout|DEFAULT_TIMEOUT', full_content):
1066
+ return False
1067
+
1068
+ # ── TG-AUTH-003: JWT verify with algorithms in options object ──────────────
1069
+ if rule_id == "TG-AUTH-003":
1070
+ if re.search(r'algorithms\s*:', lookahead):
1071
+ return False
1072
+
1073
+ # ── TG-GQL-001: ApolloServer with plugins/validation rules elsewhere ──────
1074
+ if rule_id == "TG-GQL-001":
1075
+ if re.search(r'(?:depthLimit|costAnalysis|complexityLimit|validationRules)', full_content):
1076
+ return False
1077
+
1078
+ # ── TG-SEC-004: Logging that redacts sensitive fields ──────────────────────
1079
+ if rule_id == "TG-SEC-004":
1080
+ if re.search(r'(?:redact|mask|sanitize|\*{3,}|\[REDACTED\])', lookahead):
1081
+ return False
1082
+
1083
+ # ── TG-DB-004: findById with ownership check in next few lines ────────────
1084
+ if rule_id == "TG-DB-004":
1085
+ if re.search(r'(?:tenantId|tenant_id|organization_id|user\.id|userId|ownerId)', lookahead):
1086
+ return False
1087
+
1088
+ # ── TG-WEBHOOK-001: Signature verification in handler body ────────────────
1089
+ if rule_id == "TG-WEBHOOK-001":
1090
+ # Check if HMAC/signature verification happens in the function body
1091
+ func_body = "\n".join(lines[line_idx:min(line_idx + 20, len(lines))])
1092
+ if re.search(r'(?:verifySignature|hmac|createHmac|verify_webhook|constructEvent)', func_body):
1093
+ return False
1094
+
1095
+ return True # Keep the finding
1096
+
1097
+
951
1098
  # File extensions to scan
952
1099
  SOURCE_EXTENSIONS = {
953
1100
  ".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
@@ -962,11 +1109,16 @@ SKIP_DIRS = {
962
1109
  }
963
1110
 
964
1111
  def is_test_file(path_str: str) -> bool:
965
- """Check if file belongs to tests, fixtures, or test harnesses."""
966
- clean = path_str.replace("\\", "/").lower()
967
- markers = ["/test/", "/tests/", "/spec/", "/specs/", "/fixtures/", "/mock/", "/mocks/",
968
- "/harness/", "__tests__", "test_", "_test.", ".test.", ".spec."]
969
- return any(m in clean for m in markers)
1112
+ """Check if file belongs to tests, fixtures, or test harnesses across any language."""
1113
+ clean = "/" + path_str.replace("\\", "/").strip("/").lower() + "/"
1114
+ markers = [
1115
+ "/test/", "/tests/", "/spec/", "/specs/", "/fixtures/", "/mock/", "/mocks/",
1116
+ "/harness/", "/qa-runs/", "/test_repos/", "/__tests__/"
1117
+ ]
1118
+ if any(m in clean for m in markers):
1119
+ return True
1120
+ filename = Path(path_str).name.lower()
1121
+ return filename.startswith("test_") or "_test." in filename or ".test." in filename or ".spec." in filename
970
1122
 
971
1123
 
972
1124
  def find_files_to_scan(target_root: Path, include_tests: bool = False) -> List[Path]:
@@ -987,7 +1139,10 @@ def find_files_to_scan(target_root: Path, include_tests: bool = False) -> List[P
987
1139
 
988
1140
 
989
1141
  def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
990
- """Scan a single source file against the TorusGuard rule families."""
1142
+ """Scan a single source file against the TorusGuard rule families.
1143
+ Enhanced with context-aware validation, file-type discrimination,
1144
+ developer suppression comments, canary tags, and multi-finding support.
1145
+ """
991
1146
  findings = []
992
1147
  try:
993
1148
  content = file_path.read_text(encoding="utf-8", errors="replace")
@@ -998,13 +1153,24 @@ def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
998
1153
  rel_path = str(file_path.relative_to(target_root)).replace("\\", "/")
999
1154
  is_test = is_test_file(rel_path)
1000
1155
 
1156
+ # Phase 1c: File-type discrimination — get suppressed rules for this file
1157
+ suppressed_rules = get_suppressed_rules_for_file(rel_path)
1158
+
1001
1159
  for rule in RULE_PATTERNS:
1002
1160
  rule_id = rule["rule_id"]
1003
- if rule_id == "TG-PLATFORM-002" and "helmet" in content and "app.use(helmet" in content:
1161
+
1162
+ # Skip rules suppressed by file-type discrimination
1163
+ if rule_id in suppressed_rules:
1004
1164
  continue
1165
+
1005
1166
  patterns: List[Tuple[Pattern[str], str]] = rule["patterns"]
1167
+ found_lines: set = set() # Track matched lines to avoid duplicates
1168
+
1006
1169
  for regex, desc in patterns:
1007
1170
  for idx, line in enumerate(lines):
1171
+ if idx in found_lines:
1172
+ continue # Already reported a finding on this line for this rule
1173
+
1008
1174
  stripped = line.strip()
1009
1175
  if stripped.startswith(("#", "//", "/*", "*")):
1010
1176
  if "bypass" not in stripped.lower() and "todo" not in stripped.lower():
@@ -1012,6 +1178,27 @@ def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
1012
1178
 
1013
1179
  match = regex.search(line)
1014
1180
  if match:
1181
+ line_lower = line.lower()
1182
+ prev_line = lines[idx - 1].lower() if idx > 0 else ""
1183
+
1184
+ # Developer suppression annotations (e.g. # torusguard: ignore, // tg-ignore)
1185
+ if "torusguard: ignore" in line_lower or "torusguard: ignore" in prev_line or \
1186
+ "tg-ignore" in line_lower or "tg-ignore" in prev_line:
1187
+ if rule_id != "TG-DIFF-001":
1188
+ continue
1189
+
1190
+ # Developer test canary annotations (# torusguard: test-canary)
1191
+ is_canary = (
1192
+ "torusguard: test-canary" in line_lower or "torusguard: test-canary" in prev_line or
1193
+ "tg-canary" in line_lower or "tg-canary" in prev_line or
1194
+ "test-canary" in line_lower or "test-canary" in prev_line or
1195
+ is_test
1196
+ )
1197
+
1198
+ # Phase 1a: Context-aware false positive suppression
1199
+ if not validate_finding_context(rule_id, idx, lines, rel_path, content):
1200
+ continue # Suppressed by context validation
1201
+
1015
1202
  line_num = idx + 1
1016
1203
  seed = f"{rule_id}:{rel_path}:{line_num}:{stripped}"
1017
1204
  fp = f"{rule_id}-{hashlib.sha256(seed.encode('utf-8')).hexdigest()[:8]}"
@@ -1025,7 +1212,7 @@ def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
1025
1212
  "rule_id": rule_id,
1026
1213
  "title": rule["title"],
1027
1214
  "description": desc,
1028
- "severity": "Low" if is_test else rule["severity"],
1215
+ "severity": "Low" if (is_test or is_canary) else rule["severity"],
1029
1216
  "category": rule["category"],
1030
1217
  "cluster": rule["cluster"],
1031
1218
  "file_path": rel_path,
@@ -1033,13 +1220,15 @@ def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
1033
1220
  "matched_text": match.group(0)[:80],
1034
1221
  "snippet": snippet,
1035
1222
  "is_test": is_test,
1223
+ "is_canary": is_canary,
1224
+ "rule_precision": RULE_PRECISION.get(rule_id, 0.60),
1036
1225
  "target": {
1037
1226
  "file_path": rel_path,
1038
1227
  "start_line": line_num,
1039
1228
  "end_line": line_num
1040
1229
  }
1041
1230
  })
1042
- break
1231
+ found_lines.add(idx)
1043
1232
  return findings
1044
1233
 
1045
1234
 
@@ -1056,6 +1245,15 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1056
1245
  scored = []
1057
1246
  clusters: Dict[str, List[Dict[str, Any]]] = {}
1058
1247
 
1248
+ # Phase 1d: Pre-compute cross-file corroboration bonus
1249
+ # If multiple rule families flag the same file, each finding gets +5 confidence
1250
+ file_rule_families: Dict[str, set] = {}
1251
+ for f in findings:
1252
+ fp = f["file_path"]
1253
+ if fp not in file_rule_families:
1254
+ file_rule_families[fp] = set()
1255
+ file_rule_families[fp].add(f["category"])
1256
+
1059
1257
  for f in findings:
1060
1258
  score = 70
1061
1259
  band = "High Confidence"
@@ -1063,10 +1261,19 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1063
1261
 
1064
1262
  if finding_scorer:
1065
1263
  try:
1066
- eq = 35
1264
+ # Phase 1d: Dynamic evidence quality based on rule precision
1265
+ precision = f.get("rule_precision", RULE_PRECISION.get(f["rule_id"], 0.60))
1266
+ eq = max(10, int(35 * precision)) # Scale evidence quality by precision
1067
1267
  rs = 0
1068
- ic = 5
1069
- ec = 15
1268
+
1269
+ # Corroboration bonus: multiple rule families in same file
1270
+ families_in_file = len(file_rule_families.get(f["file_path"], set()))
1271
+ ic = min(15, 5 + (families_in_file - 1) * 3) if families_in_file > 1 else 5
1272
+
1273
+ # Environmental clarity: production files score higher
1274
+ is_prod = not f.get("is_test", False)
1275
+ ec = 15 if is_prod else 5
1276
+
1070
1277
  mr = 0
1071
1278
  s, b, facts = finding_scorer.compute_confidence_score(
1072
1279
  evidence_quality=eq,
@@ -1166,7 +1373,7 @@ def print_audit_dashboard(target_root: Path, file_count: int, scored_findings: L
1166
1373
  stack_str = f"{detected_stack.get('framework')} ({detected_stack.get('language')})"
1167
1374
 
1168
1375
  print()
1169
- print(card_header("🛡️ TORUSGUARD STATIC SECURITY AUDIT", "Autonomous AST & Invariant Security Scanner", "v1.3.5"))
1376
+ print(card_header("🛡️ TORUSGUARD STATIC SECURITY AUDIT", "Autonomous AST & Invariant Security Scanner", "v1.3.6"))
1170
1377
  print()
1171
1378
 
1172
1379
  print(card_border_top("Audit Execution Scope"))
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- TorusGuard Autonomous Remediation Engine (v1.3.5)
3
+ TorusGuard Autonomous Remediation Engine (v1.3.6)
4
4
  Evaluates audit findings against canonical patch templates, enforces Ponytail bounds
5
5
  (<= 35 additions, <= 25 deletions), packages structured candidate remediation bundles,
6
6
  and renders unified patch diffs with standardized 75-column terminal UI.
@@ -58,7 +58,7 @@ def box_line(content: str, width: int = 67, border: str = "│", border_color: s
58
58
  return term_ui.format_box_line(content, width=width, border=border, border_color=border_color)
59
59
  return f" {border_color}{border}{RESET} {content}"
60
60
 
61
- def box_header(title: str, subtitle: str = "", version: str = "v1.3.5", border_color: str = CYAN) -> str:
61
+ def box_header(title: str, subtitle: str = "", version: str = "v1.3.6", border_color: str = CYAN) -> str:
62
62
  if term_ui:
63
63
  return term_ui.card_header(title, subtitle, version, border_color)
64
64
  return f"=== {title} ({version}) ==="
@@ -564,7 +564,7 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
564
564
 
565
565
  # Print 75-column Terminal Cards
566
566
  print()
567
- print(box_header("🛡️ TORUSGUARD GOVERNED REMEDIATION ENGINE", "Autonomous Minimal Patch Formulation & Ponytail Packaging", "v1.3.5"))
567
+ print(box_header("🛡️ TORUSGUARD GOVERNED REMEDIATION ENGINE", "Autonomous Minimal Patch Formulation & Ponytail Packaging", "v1.3.6"))
568
568
  print()
569
569
 
570
570
  print(border_top("Remediation Scope"))