torusguard 2.0.0-alpha → 2.1.1

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 (227) hide show
  1. package/.torusguard/.manifest.json +94 -30
  2. package/.torusguard/core/__init__.py +146 -0
  3. package/.torusguard/core/agent_roles.py +104 -0
  4. package/.torusguard/core/ast_walker.py +283 -0
  5. package/.torusguard/core/authorization.py +218 -0
  6. package/.torusguard/core/browser_verifier.py +128 -0
  7. package/.torusguard/core/bundle.py +141 -0
  8. package/.torusguard/core/call_graph.py +184 -0
  9. package/.torusguard/core/clustering.py +275 -0
  10. package/.torusguard/core/confidence.py +120 -0
  11. package/.torusguard/core/cross_file_taint.py +101 -0
  12. package/.torusguard/core/exploit_checker.py +317 -0
  13. package/.torusguard/core/formatter.py +351 -0
  14. package/.torusguard/core/governance.py +210 -0
  15. package/.torusguard/core/identity.py +104 -0
  16. package/.torusguard/core/import_resolver.py +91 -0
  17. package/.torusguard/core/incremental.py +102 -0
  18. package/.torusguard/core/lifecycle.py +137 -0
  19. package/.torusguard/core/models.py +425 -0
  20. package/.torusguard/core/parallel.py +56 -0
  21. package/.torusguard/core/parser.py +202 -0
  22. package/.torusguard/core/rechecker.py +107 -0
  23. package/.torusguard/core/replay_trace.py +178 -0
  24. package/.torusguard/core/rules_registry.py +131 -0
  25. package/.torusguard/core/run_folder.py +60 -0
  26. package/.torusguard/core/run_manager.py +163 -0
  27. package/.torusguard/core/runtime_evidence.py +175 -0
  28. package/.torusguard/core/runtime_validator.py +246 -0
  29. package/.torusguard/core/safety_gate.py +139 -0
  30. package/.torusguard/core/sarif.py +189 -0
  31. package/.torusguard/core/stack_profiler.py +184 -0
  32. package/.torusguard/core/symbol_table.py +91 -0
  33. package/.torusguard/core/taint.py +133 -0
  34. package/.torusguard/core/taint_graph.py +235 -0
  35. package/.torusguard/core/taint_rules.py +268 -0
  36. package/.torusguard/core/v070_reporter.py +102 -0
  37. package/.torusguard/core/v070_workflow.py +339 -0
  38. package/.torusguard/core/v6_reporter.py +180 -0
  39. package/.torusguard/core/v6_workflow.py +221 -0
  40. package/.torusguard/core/watcher.py +58 -0
  41. package/.torusguard/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
  42. package/.torusguard/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
  43. package/.torusguard/rules/container/TG-CONT-001-root-user-execution.md +50 -0
  44. package/.torusguard/rules/container/TG-CONT-002-docker-socket-mount.md +47 -0
  45. package/.torusguard/rules/container/TG-CONT-003-privileged-container-mode.md +53 -0
  46. package/.torusguard/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -0
  47. package/.torusguard/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -0
  48. package/.torusguard/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -0
  49. package/.torusguard/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -0
  50. package/.torusguard/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -0
  51. package/.torusguard/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -0
  52. package/.torusguard/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -0
  53. package/.torusguard/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -0
  54. package/.torusguard/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -0
  55. package/.torusguard/rules_catalog.json +96 -0
  56. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  57. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  58. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  59. package/.torusguard/scripts/__pycache__/rules_sync.cpython-314.pyc +0 -0
  60. package/.torusguard/scripts/audit_runner.py +108 -10
  61. package/.torusguard/scripts/finding_scorer.py +43 -13
  62. package/.torusguard/scripts/manifest_builder.py +1 -1
  63. package/.torusguard/scripts/skill_profiler.py +26 -0
  64. package/.torusguard/skills/torusguard/SKILL.md +74 -25
  65. package/.torusguard/skills/torusguard/bootstrap.py +57 -24
  66. package/.torusguard/skills/torusguard-ai-guard/SKILL.md +95 -0
  67. package/.torusguard/skills/torusguard-apply/SKILL.md +60 -34
  68. package/.torusguard/skills/torusguard-audit/SKILL.md +131 -57
  69. package/.torusguard/skills/torusguard-authorize/SKILL.md +48 -6
  70. package/.torusguard/skills/torusguard-container/SKILL.md +94 -0
  71. package/.torusguard/skills/torusguard-exploit-check/SKILL.md +50 -6
  72. package/.torusguard/skills/torusguard-full/SKILL.md +62 -19
  73. package/.torusguard/skills/torusguard-git-mine/SKILL.md +92 -0
  74. package/.torusguard/skills/torusguard-harden/SKILL.md +81 -50
  75. package/.torusguard/skills/torusguard-init/SKILL.md +61 -14
  76. package/.torusguard/skills/torusguard-ocr-scan/SKILL.md +94 -0
  77. package/.torusguard/skills/torusguard-recheck/SKILL.md +71 -18
  78. package/.torusguard/skills/torusguard-redos/SKILL.md +91 -0
  79. package/.torusguard/skills/torusguard-report/SKILL.md +50 -9
  80. package/.torusguard/skills/torusguard-status/SKILL.md +63 -10
  81. package/.torusguard/skills/torusguard-verify/SKILL.md +52 -10
  82. package/.torusguard/skills/torusguard-web-validate/SKILL.md +53 -8
  83. package/.torusguard/workflows/ai-guard.md +31 -0
  84. package/.torusguard/workflows/apply.md +32 -55
  85. package/.torusguard/workflows/audit.md +35 -49
  86. package/.torusguard/workflows/authorize.md +27 -50
  87. package/.torusguard/workflows/container.md +29 -0
  88. package/.torusguard/workflows/exploit-check.md +28 -50
  89. package/.torusguard/workflows/git-mine.md +25 -0
  90. package/.torusguard/workflows/harden.md +29 -48
  91. package/.torusguard/workflows/init.md +27 -50
  92. package/.torusguard/workflows/memory.md +18 -23
  93. package/.torusguard/workflows/ocr-scan.md +25 -0
  94. package/.torusguard/workflows/recheck.md +28 -46
  95. package/.torusguard/workflows/redos.md +27 -0
  96. package/.torusguard/workflows/report.md +33 -52
  97. package/.torusguard/workflows/status.md +31 -52
  98. package/.torusguard/workflows/verify.md +29 -49
  99. package/.torusguard/workflows/web-validate.md +22 -45
  100. package/README.md +96 -60
  101. package/package.json +7 -2
  102. package/skills/torusguard/SKILL.md +75 -24
  103. package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
  104. package/skills/torusguard/bootstrap.py +60 -71
  105. package/skills/torusguard/payload/.manifest.json +94 -31
  106. package/skills/torusguard/payload/core/__init__.py +146 -0
  107. package/skills/torusguard/payload/core/agent_roles.py +104 -0
  108. package/skills/torusguard/payload/core/ast_walker.py +283 -0
  109. package/skills/torusguard/payload/core/authorization.py +218 -0
  110. package/skills/torusguard/payload/core/browser_verifier.py +128 -0
  111. package/skills/torusguard/payload/core/bundle.py +141 -0
  112. package/skills/torusguard/payload/core/call_graph.py +184 -0
  113. package/skills/torusguard/payload/core/clustering.py +275 -0
  114. package/skills/torusguard/payload/core/confidence.py +120 -0
  115. package/skills/torusguard/payload/core/cross_file_taint.py +101 -0
  116. package/skills/torusguard/payload/core/exploit_checker.py +317 -0
  117. package/skills/torusguard/payload/core/formatter.py +351 -0
  118. package/skills/torusguard/payload/core/governance.py +210 -0
  119. package/skills/torusguard/payload/core/identity.py +104 -0
  120. package/skills/torusguard/payload/core/import_resolver.py +91 -0
  121. package/skills/torusguard/payload/core/incremental.py +102 -0
  122. package/skills/torusguard/payload/core/lifecycle.py +137 -0
  123. package/skills/torusguard/payload/core/models.py +425 -0
  124. package/skills/torusguard/payload/core/parallel.py +56 -0
  125. package/skills/torusguard/payload/core/parser.py +202 -0
  126. package/skills/torusguard/payload/core/rechecker.py +107 -0
  127. package/skills/torusguard/payload/core/replay_trace.py +178 -0
  128. package/skills/torusguard/payload/core/rules_registry.py +131 -0
  129. package/skills/torusguard/payload/core/run_folder.py +60 -0
  130. package/skills/torusguard/payload/core/run_manager.py +163 -0
  131. package/skills/torusguard/payload/core/runtime_evidence.py +175 -0
  132. package/skills/torusguard/payload/core/runtime_validator.py +246 -0
  133. package/skills/torusguard/payload/core/safety_gate.py +139 -0
  134. package/skills/torusguard/payload/core/sarif.py +189 -0
  135. package/skills/torusguard/payload/core/stack_profiler.py +184 -0
  136. package/skills/torusguard/payload/core/symbol_table.py +91 -0
  137. package/skills/torusguard/payload/core/taint.py +133 -0
  138. package/skills/torusguard/payload/core/taint_graph.py +235 -0
  139. package/skills/torusguard/payload/core/taint_rules.py +268 -0
  140. package/skills/torusguard/payload/core/v070_reporter.py +102 -0
  141. package/skills/torusguard/payload/core/v070_workflow.py +339 -0
  142. package/skills/torusguard/payload/core/v6_reporter.py +180 -0
  143. package/skills/torusguard/payload/core/v6_workflow.py +221 -0
  144. package/skills/torusguard/payload/core/watcher.py +58 -0
  145. package/skills/torusguard/payload/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
  146. package/skills/torusguard/payload/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
  147. package/skills/torusguard/payload/rules/container/TG-CONT-001-root-user-execution.md +50 -0
  148. package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -0
  149. package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -0
  150. package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -0
  151. package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -0
  152. package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -0
  153. package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -0
  154. package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -0
  155. package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -0
  156. package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -0
  157. package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -0
  158. package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -0
  159. package/skills/torusguard/payload/rules_catalog.json +338 -518
  160. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  161. package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
  162. package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
  163. package/skills/torusguard/payload/scripts/manifest_builder.py +1 -1
  164. package/skills/torusguard/payload/skills/torusguard/SKILL.md +74 -25
  165. package/skills/torusguard/payload/skills/torusguard/bootstrap.py +57 -24
  166. package/skills/torusguard/payload/skills/torusguard/references/csharp-security.md +41 -41
  167. package/skills/torusguard/payload/skills/torusguard/references/go-security.md +41 -41
  168. package/skills/torusguard/payload/skills/torusguard/references/java-security.md +40 -40
  169. package/skills/torusguard/payload/skills/torusguard/references/polyglot-security-matrix.md +25 -25
  170. package/skills/torusguard/payload/skills/torusguard/references/rust-security.md +40 -40
  171. package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -0
  172. package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +60 -34
  173. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +131 -57
  174. package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +48 -6
  175. package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -0
  176. package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +50 -6
  177. package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +62 -19
  178. package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -0
  179. package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +81 -50
  180. package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +61 -14
  181. package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -0
  182. package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +71 -18
  183. package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -0
  184. package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +50 -9
  185. package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +63 -10
  186. package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +52 -10
  187. package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +53 -8
  188. package/skills/torusguard/payload/workflows/ai-guard.md +31 -0
  189. package/skills/torusguard/payload/workflows/apply.md +31 -62
  190. package/skills/torusguard/payload/workflows/audit.md +35 -55
  191. package/skills/torusguard/payload/workflows/authorize.md +27 -50
  192. package/skills/torusguard/payload/workflows/container.md +29 -0
  193. package/skills/torusguard/payload/workflows/exploit-check.md +28 -50
  194. package/skills/torusguard/payload/workflows/git-mine.md +25 -0
  195. package/skills/torusguard/payload/workflows/harden.md +28 -52
  196. package/skills/torusguard/payload/workflows/init.md +27 -56
  197. package/skills/torusguard/payload/workflows/memory.md +18 -23
  198. package/skills/torusguard/payload/workflows/ocr-scan.md +25 -0
  199. package/skills/torusguard/payload/workflows/recheck.md +28 -46
  200. package/skills/torusguard/payload/workflows/redos.md +27 -0
  201. package/skills/torusguard/payload/workflows/report.md +39 -62
  202. package/skills/torusguard/payload/workflows/status.md +31 -55
  203. package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
  204. package/skills/torusguard/payload/workflows/verify.md +29 -49
  205. package/skills/torusguard/payload/workflows/web-validate.md +22 -45
  206. package/skills/torusguard/references/csharp-security.md +41 -0
  207. package/skills/torusguard/references/go-security.md +41 -0
  208. package/skills/torusguard/references/java-security.md +40 -0
  209. package/skills/torusguard/references/polyglot-security-matrix.md +25 -0
  210. package/skills/torusguard/references/rust-security.md +40 -0
  211. package/skills/torusguard-ai-guard/SKILL.md +95 -0
  212. package/skills/torusguard-apply/SKILL.md +60 -34
  213. package/skills/torusguard-audit/SKILL.md +130 -57
  214. package/skills/torusguard-authorize/SKILL.md +48 -6
  215. package/skills/torusguard-container/SKILL.md +94 -0
  216. package/skills/torusguard-exploit-check/SKILL.md +50 -6
  217. package/skills/torusguard-full/SKILL.md +62 -19
  218. package/skills/torusguard-git-mine/SKILL.md +92 -0
  219. package/skills/torusguard-harden/SKILL.md +81 -50
  220. package/skills/torusguard-init/SKILL.md +61 -14
  221. package/skills/torusguard-ocr-scan/SKILL.md +94 -0
  222. package/skills/torusguard-recheck/SKILL.md +71 -18
  223. package/skills/torusguard-redos/SKILL.md +91 -0
  224. package/skills/torusguard-report/SKILL.md +50 -9
  225. package/skills/torusguard-status/SKILL.md +63 -10
  226. package/skills/torusguard-verify/SKILL.md +52 -10
  227. package/skills/torusguard-web-validate/SKILL.md +53 -8
@@ -0,0 +1,91 @@
1
+ """
2
+ TorusGuard Import Resolver
3
+ Resolves language-specific import specifiers to canonical file system paths.
4
+ Supports Python, JavaScript, TypeScript, and Go.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Optional, List
9
+
10
+
11
+ COMMON_EXTENSIONS = [
12
+ ".py", ".ts", ".tsx", ".js", ".mjs", ".cjs", ".go"
13
+ ]
14
+
15
+
16
+ class ImportResolver:
17
+ """Resolves relative and package imports to on-disk source files."""
18
+
19
+ def __init__(self, project_root: Path):
20
+ self.project_root = project_root.resolve()
21
+
22
+ def resolve(self, importing_file: Path, import_module: str) -> Optional[Path]:
23
+ """
24
+ Resolves `import_module` referenced by `importing_file` to an absolute Path on disk.
25
+ """
26
+ if not import_module:
27
+ return None
28
+
29
+ importing_file = importing_file.resolve()
30
+ current_dir = importing_file.parent
31
+
32
+ # 1. Relative import (e.g. ./utils, ../db, .services)
33
+ if import_module.startswith("."):
34
+ # Python style: .models or ..utils
35
+ if import_module.startswith(".."):
36
+ rel_parts = import_module.lstrip(".")
37
+ target_dir = current_dir.parent
38
+ cand = target_dir / rel_parts.replace(".", "/")
39
+ elif import_module.startswith("."):
40
+ rel_parts = import_module.lstrip(".")
41
+ cand = current_dir / rel_parts.replace(".", "/")
42
+ else:
43
+ cand = current_dir / import_module
44
+
45
+ resolved = self._try_extensions(cand)
46
+ if resolved:
47
+ return resolved
48
+
49
+ # 2. Direct path relative to current directory
50
+ direct_cand = current_dir / import_module
51
+ resolved = self._try_extensions(direct_cand)
52
+ if resolved:
53
+ return resolved
54
+
55
+ # 3. Project root relative import (e.g. core.taint, src.services.auth)
56
+ dotted_cand = self.project_root / import_module.replace(".", "/")
57
+ resolved = self._try_extensions(dotted_cand)
58
+ if resolved:
59
+ return resolved
60
+
61
+ # 4. Check under common subdirectories (src, app, lib, internal)
62
+ for sub in ("src", "app", "lib", "internal"):
63
+ sub_cand = self.project_root / sub / import_module.replace(".", "/")
64
+ resolved = self._try_extensions(sub_cand)
65
+ if resolved:
66
+ return resolved
67
+
68
+ return None
69
+
70
+ def _try_extensions(self, base_path: Path) -> Optional[Path]:
71
+ # Check direct file with extension already
72
+ if base_path.is_file():
73
+ return base_path
74
+
75
+ # Check with each extension
76
+ for ext in COMMON_EXTENSIONS:
77
+ cand = base_path.with_suffix(ext)
78
+ if cand.is_file():
79
+ return cand
80
+
81
+ # Check directory index / __init__
82
+ if base_path.is_dir():
83
+ py_init = base_path / "__init__.py"
84
+ if py_init.is_file():
85
+ return py_init
86
+ for ext in (".ts", ".js", ".tsx"):
87
+ idx = base_path / f"index{ext}"
88
+ if idx.is_file():
89
+ return idx
90
+
91
+ return None
@@ -0,0 +1,102 @@
1
+ """
2
+ TorusGuard Incremental Static Scanner & Hash Cache
3
+ Identifies modified or newly added source files using cryptographic content hashes
4
+ to enable sub-second re-scans on large codebases.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Dict, List, Set, Optional, Any, Tuple
9
+ import json
10
+ import hashlib
11
+ import time
12
+
13
+
14
+ class IncrementalScanner:
15
+ """Manages file modification tracking and AST cache persistence."""
16
+
17
+ def __init__(self, target_root: Path, cache_file: Optional[Path] = None):
18
+ self.target_root = target_root.resolve()
19
+ if cache_file:
20
+ self.cache_file = cache_file.resolve()
21
+ else:
22
+ cache_dir = self.target_root / ".torusguard" / "cache"
23
+ cache_dir.mkdir(parents=True, exist_ok=True)
24
+ self.cache_file = cache_dir / "ast_cache.json"
25
+
26
+ self.cache_data: Dict[str, Dict[str, Any]] = self._load_cache()
27
+
28
+ def _load_cache(self) -> Dict[str, Dict[str, Any]]:
29
+ if self.cache_file.is_file():
30
+ try:
31
+ with open(self.cache_file, "r", encoding="utf-8") as f:
32
+ return json.load(f)
33
+ except Exception:
34
+ return {}
35
+ return {}
36
+
37
+ def save_cache(self) -> None:
38
+ try:
39
+ self.cache_file.parent.mkdir(parents=True, exist_ok=True)
40
+ with open(self.cache_file, "w", encoding="utf-8") as f:
41
+ json.dump(self.cache_data, f, indent=2)
42
+ except Exception:
43
+ pass
44
+
45
+ @staticmethod
46
+ def compute_file_hash(file_path: Path) -> str:
47
+ """Computes SHA256 of file content."""
48
+ h = hashlib.sha256()
49
+ try:
50
+ with open(file_path, "rb") as f:
51
+ while chunk := f.read(65536):
52
+ h.update(chunk)
53
+ return h.hexdigest()
54
+ except Exception:
55
+ return ""
56
+
57
+ def get_changed_files(self, all_files: List[Path]) -> Tuple[List[Path], List[Path]]:
58
+ """
59
+ Compares current file hashes to cache.
60
+ Returns (changed_files, unchanged_files).
61
+ """
62
+ changed: List[Path] = []
63
+ unchanged: List[Path] = []
64
+
65
+ for f in all_files:
66
+ try:
67
+ rel_path = str(f.resolve().relative_to(self.target_root)).replace("\\", "/")
68
+ except Exception:
69
+ rel_path = str(f).replace("\\", "/")
70
+
71
+ current_hash = self.compute_file_hash(f)
72
+ cached_entry = self.cache_data.get(rel_path)
73
+
74
+ if cached_entry and cached_entry.get("hash") == current_hash:
75
+ unchanged.append(f)
76
+ else:
77
+ changed.append(f)
78
+
79
+ return changed, unchanged
80
+
81
+ def get_cached_findings(self, file_path: Path) -> List[Dict[str, Any]]:
82
+ try:
83
+ rel_path = str(file_path.resolve().relative_to(self.target_root)).replace("\\", "/")
84
+ except Exception:
85
+ rel_path = str(file_path).replace("\\", "/")
86
+ entry = self.cache_data.get(rel_path)
87
+ if entry:
88
+ return entry.get("findings", [])
89
+ return []
90
+
91
+ def update_file_cache(self, file_path: Path, findings: List[Dict[str, Any]]) -> None:
92
+ try:
93
+ rel_path = str(file_path.resolve().relative_to(self.target_root)).replace("\\", "/")
94
+ except Exception:
95
+ rel_path = str(file_path).replace("\\", "/")
96
+ current_hash = self.compute_file_hash(file_path)
97
+ self.cache_data[rel_path] = {
98
+ "hash": current_hash,
99
+ "timestamp": time.time(),
100
+ "findings_count": len(findings),
101
+ "findings": findings
102
+ }
@@ -0,0 +1,137 @@
1
+ """
2
+ TorusGuard Finding Lifecycle State Machine (v0.5.1)
3
+ Manages progression across Detect -> Classify -> Verify -> Remediate -> Recheck -> Archive.
4
+ """
5
+
6
+ from typing import List, Tuple, Optional
7
+ import datetime
8
+ import hashlib
9
+ from .models import (
10
+ Finding,
11
+ LifecycleStage,
12
+ FindingStatus,
13
+ ConfidenceBand,
14
+ RetestRecord,
15
+ )
16
+
17
+
18
+ class LifecycleTransitionError(Exception):
19
+ """Raised when an invalid lifecycle state transition is attempted."""
20
+ pass
21
+
22
+
23
+ class FindingLifecycleManager:
24
+ """
25
+ Implements formal lifecycle progression and validation rules for TorusGuard v0.5.1 findings.
26
+ """
27
+
28
+ ALLOWED_TRANSITIONS = {
29
+ LifecycleStage.DETECT: [LifecycleStage.CLASSIFY, LifecycleStage.ARCHIVE],
30
+ LifecycleStage.CLASSIFY: [LifecycleStage.VERIFY, LifecycleStage.ARCHIVE],
31
+ LifecycleStage.VERIFY: [LifecycleStage.REMEDIATE, LifecycleStage.CLASSIFY, LifecycleStage.ARCHIVE],
32
+ LifecycleStage.REMEDIATE: [LifecycleStage.RECHECK, LifecycleStage.VERIFY],
33
+ LifecycleStage.RECHECK: [LifecycleStage.ARCHIVE, LifecycleStage.VERIFY, LifecycleStage.REMEDIATE],
34
+ LifecycleStage.ARCHIVE: [], # Terminal state
35
+ }
36
+
37
+ @staticmethod
38
+ def transition(finding: Finding, target_stage: LifecycleStage, note: Optional[str] = None) -> Finding:
39
+ current = finding.lifecycle_stage
40
+ allowed = FindingLifecycleManager.ALLOWED_TRANSITIONS.get(current, [])
41
+
42
+ if target_stage not in allowed:
43
+ raise LifecycleTransitionError(
44
+ f"Invalid lifecycle transition from '{current.value}' to '{target_stage.value}'. "
45
+ f"Allowed target stages: {[s.value for s in allowed]}"
46
+ )
47
+
48
+ now = datetime.datetime.utcnow().isoformat() + "Z"
49
+
50
+ if target_stage == LifecycleStage.CLASSIFY:
51
+ if not finding.rule_id or not finding.category or not finding.severity:
52
+ raise LifecycleTransitionError("Classification requires rule_id, category, and severity.")
53
+ finding.status = FindingStatus.UNCONFIRMED
54
+
55
+ elif target_stage == LifecycleStage.VERIFY:
56
+ # Verification assertion: If no source/test evidence or evidence is insufficient, force Needs Review
57
+ has_sufficient_evidence = any(
58
+ e.is_sufficient_for_confirmed for e in finding.evidence
59
+ )
60
+ if not has_sufficient_evidence and finding.confidence.band == ConfidenceBand.CONFIRMED:
61
+ finding.confidence.band = ConfidenceBand.NEEDS_REVIEW
62
+ finding.status = FindingStatus.NEEDS_REVIEW
63
+ elif finding.confidence.band == ConfidenceBand.CONFIRMED:
64
+ finding.status = FindingStatus.CONFIRMED
65
+ else:
66
+ finding.status = FindingStatus.HIGH_CONFIDENCE if finding.confidence.score >= 70 else FindingStatus.NEEDS_REVIEW
67
+
68
+ finding.timestamps.verified_at = now
69
+
70
+ elif target_stage == LifecycleStage.REMEDIATE:
71
+ if not finding.remediation or not finding.remediation.recommended_fix:
72
+ raise LifecycleTransitionError("Remediation stage requires a complete remediation proposal.")
73
+ finding.status = FindingStatus.REMEDIATED
74
+ finding.timestamps.remediated_at = now
75
+
76
+ elif target_stage == LifecycleStage.RECHECK:
77
+ # Recheck requires retest record verification
78
+ if not finding.retest_result.retest_performed:
79
+ raise LifecycleTransitionError("Recheck stage requires an explicit retest to have been executed.")
80
+ if finding.retest_result.closure_status == FindingStatus.VERIFIED_FIXED:
81
+ finding.status = FindingStatus.VERIFIED_FIXED
82
+ finding.timestamps.retested_at = now
83
+
84
+ elif target_stage == LifecycleStage.ARCHIVE:
85
+ if finding.status not in (FindingStatus.VERIFIED_FIXED, FindingStatus.SUPPRESSED):
86
+ # Allow archive but note state
87
+ pass
88
+
89
+ finding.lifecycle_stage = target_stage
90
+ finding.timestamps.updated_at = now
91
+ return finding
92
+
93
+ @staticmethod
94
+ def execute_retest(
95
+ finding: Finding,
96
+ post_fix_code: str,
97
+ safe_pattern_verified: bool,
98
+ retest_method: str = "Differential Static Re-audit",
99
+ verifier_notes: str = "Verified safe via AST/pattern inspection."
100
+ ) -> Tuple[bool, str]:
101
+ """
102
+ Executes a formal retest on post-fix source code, hashing the evidence and updating closure status.
103
+ """
104
+ now = datetime.datetime.utcnow().isoformat() + "Z"
105
+ evidence_hash = hashlib.sha256(post_fix_code.strip().encode("utf-8")).hexdigest()
106
+
107
+ if safe_pattern_verified:
108
+ finding.retest_result = RetestRecord(
109
+ retest_performed=True,
110
+ closure_status=FindingStatus.VERIFIED_FIXED,
111
+ fix_applied=finding.remediation.recommended_fix,
112
+ retest_method=retest_method,
113
+ retest_evidence_hash=evidence_hash,
114
+ residual_risk=finding.remediation.residual_risk_notes,
115
+ verifier_notes=verifier_notes,
116
+ retest_timestamp=now,
117
+ )
118
+ finding.status = FindingStatus.VERIFIED_FIXED
119
+ finding.lifecycle_stage = LifecycleStage.RECHECK
120
+ finding.timestamps.retested_at = now
121
+ finding.timestamps.updated_at = now
122
+ return True, f"Finding {finding.finding_id} ({finding.rule_id}) successfully verified fixed. Evidence SHA256: {evidence_hash[:12]}..."
123
+ else:
124
+ finding.retest_result = RetestRecord(
125
+ retest_performed=True,
126
+ closure_status=FindingStatus.OPEN,
127
+ fix_applied=finding.remediation.recommended_fix,
128
+ retest_method=retest_method,
129
+ retest_evidence_hash=evidence_hash,
130
+ residual_risk="Remediation pattern missing or incomplete.",
131
+ verifier_notes="Unsafe pattern still detected in post-fix code.",
132
+ retest_timestamp=now,
133
+ )
134
+ finding.status = FindingStatus.OPEN
135
+ finding.lifecycle_stage = LifecycleStage.VERIFY
136
+ finding.timestamps.updated_at = now
137
+ return False, f"Retest failed for {finding.finding_id}: unsafe pattern remains."