torusguard 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.torusguard/.manifest.json +55 -5
- package/.torusguard/core/__init__.py +146 -0
- package/.torusguard/core/agent_roles.py +104 -0
- package/.torusguard/core/ast_walker.py +283 -0
- package/.torusguard/core/authorization.py +218 -0
- package/.torusguard/core/browser_verifier.py +128 -0
- package/.torusguard/core/bundle.py +141 -0
- package/.torusguard/core/call_graph.py +184 -0
- package/.torusguard/core/clustering.py +275 -0
- package/.torusguard/core/confidence.py +120 -0
- package/.torusguard/core/cross_file_taint.py +101 -0
- package/.torusguard/core/exploit_checker.py +317 -0
- package/.torusguard/core/formatter.py +351 -0
- package/.torusguard/core/governance.py +210 -0
- package/.torusguard/core/identity.py +104 -0
- package/.torusguard/core/import_resolver.py +91 -0
- package/.torusguard/core/incremental.py +102 -0
- package/.torusguard/core/lifecycle.py +137 -0
- package/.torusguard/core/models.py +425 -0
- package/.torusguard/core/parallel.py +56 -0
- package/.torusguard/core/parser.py +202 -0
- package/.torusguard/core/rechecker.py +107 -0
- package/.torusguard/core/replay_trace.py +178 -0
- package/.torusguard/core/rules_registry.py +131 -0
- package/.torusguard/core/run_folder.py +60 -0
- package/.torusguard/core/run_manager.py +163 -0
- package/.torusguard/core/runtime_evidence.py +175 -0
- package/.torusguard/core/runtime_validator.py +246 -0
- package/.torusguard/core/safety_gate.py +139 -0
- package/.torusguard/core/sarif.py +189 -0
- package/.torusguard/core/stack_profiler.py +184 -0
- package/.torusguard/core/symbol_table.py +91 -0
- package/.torusguard/core/taint.py +133 -0
- package/.torusguard/core/taint_graph.py +235 -0
- package/.torusguard/core/taint_rules.py +268 -0
- package/.torusguard/core/v070_reporter.py +102 -0
- package/.torusguard/core/v070_workflow.py +339 -0
- package/.torusguard/core/v6_reporter.py +180 -0
- package/.torusguard/core/v6_workflow.py +221 -0
- package/.torusguard/core/watcher.py +58 -0
- package/.torusguard/custom_rules/README.md +24 -0
- package/.torusguard/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
- package/.torusguard/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
- package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/rules_sync.cpython-314.pyc +0 -0
- package/.torusguard/scripts/audit_runner.py +108 -10
- package/.torusguard/scripts/deliberation_tournament.py +136 -0
- package/.torusguard/scripts/finding_scorer.py +43 -13
- package/.torusguard/scripts/reachability_analyzer.py +165 -0
- package/.torusguard/scripts/skill_profiler.py +26 -0
- package/.torusguard/scripts/stride_generator.py +198 -0
- package/.torusguard/skills/torusguard/SKILL.md +6 -2
- package/.torusguard/skills/torusguard-audit/SKILL.md +109 -84
- package/.torusguard/skills/torusguard-review/SKILL.md +27 -0
- package/.torusguard/skills/torusguard-threatmodel/SKILL.md +27 -0
- package/.torusguard/workflows/audit.md +21 -17
- package/.torusguard/workflows/review.md +11 -0
- package/.torusguard/workflows/threatmodel.md +9 -0
- package/README.md +149 -48
- package/package.json +10 -2
- package/skills/torusguard/SKILL.md +6 -2
- package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/skills/torusguard/bootstrap.py +3 -3
- package/skills/torusguard/payload/.manifest.json +56 -7
- package/skills/torusguard/payload/core/__init__.py +146 -0
- package/skills/torusguard/payload/core/agent_roles.py +104 -0
- package/skills/torusguard/payload/core/ast_walker.py +283 -0
- package/skills/torusguard/payload/core/authorization.py +218 -0
- package/skills/torusguard/payload/core/browser_verifier.py +128 -0
- package/skills/torusguard/payload/core/bundle.py +141 -0
- package/skills/torusguard/payload/core/call_graph.py +184 -0
- package/skills/torusguard/payload/core/clustering.py +275 -0
- package/skills/torusguard/payload/core/confidence.py +120 -0
- package/skills/torusguard/payload/core/cross_file_taint.py +101 -0
- package/skills/torusguard/payload/core/exploit_checker.py +317 -0
- package/skills/torusguard/payload/core/formatter.py +351 -0
- package/skills/torusguard/payload/core/governance.py +210 -0
- package/skills/torusguard/payload/core/identity.py +104 -0
- package/skills/torusguard/payload/core/import_resolver.py +91 -0
- package/skills/torusguard/payload/core/incremental.py +102 -0
- package/skills/torusguard/payload/core/lifecycle.py +137 -0
- package/skills/torusguard/payload/core/models.py +425 -0
- package/skills/torusguard/payload/core/parallel.py +56 -0
- package/skills/torusguard/payload/core/parser.py +202 -0
- package/skills/torusguard/payload/core/rechecker.py +107 -0
- package/skills/torusguard/payload/core/replay_trace.py +178 -0
- package/skills/torusguard/payload/core/rules_registry.py +131 -0
- package/skills/torusguard/payload/core/run_folder.py +60 -0
- package/skills/torusguard/payload/core/run_manager.py +163 -0
- package/skills/torusguard/payload/core/runtime_evidence.py +175 -0
- package/skills/torusguard/payload/core/runtime_validator.py +246 -0
- package/skills/torusguard/payload/core/safety_gate.py +139 -0
- package/skills/torusguard/payload/core/sarif.py +189 -0
- package/skills/torusguard/payload/core/stack_profiler.py +184 -0
- package/skills/torusguard/payload/core/symbol_table.py +91 -0
- package/skills/torusguard/payload/core/taint.py +133 -0
- package/skills/torusguard/payload/core/taint_graph.py +235 -0
- package/skills/torusguard/payload/core/taint_rules.py +268 -0
- package/skills/torusguard/payload/core/v070_reporter.py +102 -0
- package/skills/torusguard/payload/core/v070_workflow.py +339 -0
- package/skills/torusguard/payload/core/v6_reporter.py +180 -0
- package/skills/torusguard/payload/core/v6_workflow.py +221 -0
- package/skills/torusguard/payload/core/watcher.py +58 -0
- package/skills/torusguard/payload/custom_rules/README.md +15 -0
- package/skills/torusguard/payload/rules/TG-INPUT-007-unvalidated-redirect.md +53 -0
- package/skills/torusguard/payload/rules/TG-INPUT-008-insecure-deserialization.md +52 -0
- package/skills/torusguard/payload/rules/container/TG-CONT-001-root-user-execution.md +50 -50
- package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -47
- package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -53
- package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -43
- package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -44
- package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -41
- package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -40
- package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -72
- package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -51
- package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -51
- package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -46
- package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -43
- package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
- package/skills/torusguard/payload/scripts/deliberation_tournament.py +127 -0
- package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
- package/skills/torusguard/payload/scripts/reachability_analyzer.py +164 -0
- package/skills/torusguard/payload/scripts/stride_generator.py +196 -0
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +6 -2
- package/skills/torusguard/payload/skills/torusguard/bootstrap.py +3 -3
- package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -95
- package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +109 -84
- package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -94
- package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -92
- package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -94
- package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -91
- package/skills/torusguard/payload/skills/torusguard-review/SKILL.md +27 -0
- package/skills/torusguard/payload/skills/torusguard-threatmodel/SKILL.md +27 -0
- package/skills/torusguard/payload/workflows/ai-guard.md +31 -31
- package/skills/torusguard/payload/workflows/audit.md +21 -17
- package/skills/torusguard/payload/workflows/container.md +29 -29
- package/skills/torusguard/payload/workflows/git-mine.md +25 -25
- package/skills/torusguard/payload/workflows/ocr-scan.md +25 -25
- package/skills/torusguard/payload/workflows/redos.md +27 -27
- package/skills/torusguard/payload/workflows/review.md +11 -0
- package/skills/torusguard/payload/workflows/threatmodel.md +9 -0
- package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
- package/skills/torusguard/references/csharp-security.md +41 -41
- package/skills/torusguard/references/go-security.md +41 -41
- package/skills/torusguard/references/java-security.md +40 -40
- package/skills/torusguard/references/polyglot-security-matrix.md +25 -25
- package/skills/torusguard/references/rust-security.md +40 -40
- package/skills/torusguard-audit/SKILL.md +107 -83
- package/skills/torusguard-review/SKILL.md +27 -0
- package/skills/torusguard-threatmodel/SKILL.md +27 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Taint Engine — Directed Taint Flow Graph
|
|
3
|
+
Builds Def->Use chains, walks reachability paths, detects sanitization points,
|
|
4
|
+
and enumerates source-to-sink vulnerability paths.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections import deque
|
|
8
|
+
from typing import List, Dict, Set, Optional, Any, Tuple
|
|
9
|
+
from core.taint import TaintNode, TaintEdge, TaintPath, TaintPropagator
|
|
10
|
+
from core.taint_rules import get_sources_for_language, get_sinks_for_language, get_sanitizers_for_language
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TaintGraph:
|
|
14
|
+
"""Directed graph tracking taint flow from sources to sinks."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.nodes: Dict[str, TaintNode] = {}
|
|
18
|
+
self.edges: List[TaintEdge] = []
|
|
19
|
+
self.adjacency: Dict[str, List[TaintEdge]] = {}
|
|
20
|
+
self.reverse_adjacency: Dict[str, List[TaintEdge]] = {}
|
|
21
|
+
|
|
22
|
+
def add_node(self, node: TaintNode) -> TaintNode:
|
|
23
|
+
nid = node.node_id()
|
|
24
|
+
if nid not in self.nodes:
|
|
25
|
+
self.nodes[nid] = node
|
|
26
|
+
self.adjacency[nid] = []
|
|
27
|
+
self.reverse_adjacency[nid] = []
|
|
28
|
+
return self.nodes[nid]
|
|
29
|
+
|
|
30
|
+
def add_edge(self, from_node: TaintNode, to_node: TaintNode, edge_type: str = "assignment", line_number: int = 0) -> TaintEdge:
|
|
31
|
+
fn = self.add_node(from_node)
|
|
32
|
+
tn = self.add_node(to_node)
|
|
33
|
+
edge = TaintEdge(from_node=fn, to_node=tn, edge_type=edge_type, line_number=line_number)
|
|
34
|
+
self.edges.append(edge)
|
|
35
|
+
self.adjacency[fn.node_id()].append(edge)
|
|
36
|
+
self.reverse_adjacency[tn.node_id()].append(edge)
|
|
37
|
+
return edge
|
|
38
|
+
|
|
39
|
+
def find_tainted_paths(self, source: TaintNode, sink: TaintNode, max_depth: int = 8) -> List[List[TaintNode]]:
|
|
40
|
+
"""
|
|
41
|
+
BFS/DFS to find all paths from source to sink.
|
|
42
|
+
Returns a list of node paths.
|
|
43
|
+
"""
|
|
44
|
+
source_id = source.node_id()
|
|
45
|
+
sink_id = sink.node_id()
|
|
46
|
+
|
|
47
|
+
if source_id not in self.nodes or sink_id not in self.nodes:
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
paths: List[List[TaintNode]] = []
|
|
51
|
+
# Queue contains: (current_node_id, [current_path])
|
|
52
|
+
queue = deque([(source_id, [self.nodes[source_id]])])
|
|
53
|
+
|
|
54
|
+
while queue:
|
|
55
|
+
curr_id, current_path = queue.popleft()
|
|
56
|
+
|
|
57
|
+
if len(current_path) > max_depth:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
if curr_id == sink_id:
|
|
61
|
+
paths.append(current_path)
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
visited_ids = {n.node_id() for n in current_path}
|
|
65
|
+
|
|
66
|
+
for edge in self.adjacency.get(curr_id, []):
|
|
67
|
+
next_id = edge.to_node.node_id()
|
|
68
|
+
if next_id not in visited_ids:
|
|
69
|
+
queue.append((next_id, current_path + [edge.to_node]))
|
|
70
|
+
|
|
71
|
+
return paths
|
|
72
|
+
|
|
73
|
+
def is_path_sanitized(self, path: List[TaintNode], sink_category: str, language: str) -> Tuple[bool, List[TaintNode]]:
|
|
74
|
+
"""
|
|
75
|
+
Check if any node in the path acts as a sanitizer for the target sink category.
|
|
76
|
+
"""
|
|
77
|
+
sanitizers = get_sanitizers_for_language(language)
|
|
78
|
+
sanitizers_found = []
|
|
79
|
+
|
|
80
|
+
for node in path:
|
|
81
|
+
if node.node_type == "sanitizer":
|
|
82
|
+
sanitizers_found.append(node)
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
# Check if node expression or pattern contains a sanitizer
|
|
86
|
+
for s in sanitizers:
|
|
87
|
+
if s["pattern"] in node.expression or s["pattern"] in node.pattern:
|
|
88
|
+
if not sink_category or sink_category in s.get("cleans", []):
|
|
89
|
+
sanitizers_found.append(node)
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
return len(sanitizers_found) > 0, sanitizers_found
|
|
93
|
+
|
|
94
|
+
def analyze_file_code(
|
|
95
|
+
self,
|
|
96
|
+
file_path: str,
|
|
97
|
+
code_content: str,
|
|
98
|
+
language: str = "python"
|
|
99
|
+
) -> List[TaintPath]:
|
|
100
|
+
"""
|
|
101
|
+
Parses source lines into Def-Use taint graph and discovers source->sink flows.
|
|
102
|
+
Supports variable re-assignments, transformations, and sanitizers.
|
|
103
|
+
"""
|
|
104
|
+
import re
|
|
105
|
+
|
|
106
|
+
sources_catalog = get_sources_for_language(language)
|
|
107
|
+
sinks_catalog = get_sinks_for_language(language)
|
|
108
|
+
sanitizers_catalog = get_sanitizers_for_language(language)
|
|
109
|
+
|
|
110
|
+
lines = code_content.splitlines()
|
|
111
|
+
detected_sources: List[TaintNode] = []
|
|
112
|
+
detected_sinks: List[Tuple[TaintNode, Dict[str, Any]]] = []
|
|
113
|
+
|
|
114
|
+
# Current tainted variables mapping: var_name -> latest TaintNode
|
|
115
|
+
tainted_vars: Dict[str, TaintNode] = {}
|
|
116
|
+
|
|
117
|
+
# 1. First pass: Scan line-by-line for source introductions, assignments, and sink invocations
|
|
118
|
+
for idx, line in enumerate(lines):
|
|
119
|
+
line_num = idx + 1
|
|
120
|
+
stripped = line.strip()
|
|
121
|
+
if not stripped or stripped.startswith(("#", "//", "/*", "*")):
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Check for direct sources
|
|
125
|
+
# Example: user_input = request.GET["id"] or data = req.body
|
|
126
|
+
for src_spec in sources_catalog:
|
|
127
|
+
src_pat = src_spec["pattern"]
|
|
128
|
+
if src_pat in stripped:
|
|
129
|
+
# Look for assignment LHS
|
|
130
|
+
var_name = ""
|
|
131
|
+
# Handle assignment `x = ...` or `const x = ...` or `let x = ...` or `x := ...`
|
|
132
|
+
assign_match = re.match(r"^(?:const|let|var)?\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?::=[=]?|=)\s*(.+)$", stripped)
|
|
133
|
+
if assign_match:
|
|
134
|
+
var_name = assign_match.group(1).strip()
|
|
135
|
+
else:
|
|
136
|
+
var_name = f"anon_src_{line_num}"
|
|
137
|
+
|
|
138
|
+
src_node = TaintNode(
|
|
139
|
+
variable_name=var_name,
|
|
140
|
+
file_path=file_path,
|
|
141
|
+
line_number=line_num,
|
|
142
|
+
node_type="source",
|
|
143
|
+
expression=stripped,
|
|
144
|
+
pattern=src_pat,
|
|
145
|
+
category=src_spec.get("category", "user_input"),
|
|
146
|
+
metadata={"framework": src_spec.get("framework", ""), "label": src_spec.get("label", "")}
|
|
147
|
+
)
|
|
148
|
+
self.add_node(src_node)
|
|
149
|
+
detected_sources.append(src_node)
|
|
150
|
+
if var_name:
|
|
151
|
+
tainted_vars[var_name] = src_node
|
|
152
|
+
|
|
153
|
+
# Check for propagation via assignments: LHS = RHS where RHS contains a tainted variable
|
|
154
|
+
assign_match = re.match(r"^(?:const|let|var)?\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?::=[=]?|=)\s*(.+)$", stripped)
|
|
155
|
+
if assign_match:
|
|
156
|
+
lhs_var = assign_match.group(1).strip()
|
|
157
|
+
rhs_expr = assign_match.group(2).strip()
|
|
158
|
+
|
|
159
|
+
# Check if RHS applies a sanitizer
|
|
160
|
+
is_sanitized = False
|
|
161
|
+
for san_spec in sanitizers_catalog:
|
|
162
|
+
if san_spec["pattern"] in rhs_expr:
|
|
163
|
+
# Sanitized assignment!
|
|
164
|
+
san_node = TaintNode(
|
|
165
|
+
variable_name=lhs_var,
|
|
166
|
+
file_path=file_path,
|
|
167
|
+
line_number=line_num,
|
|
168
|
+
node_type="sanitizer",
|
|
169
|
+
expression=rhs_expr,
|
|
170
|
+
pattern=san_spec["pattern"],
|
|
171
|
+
metadata={"cleans": san_spec.get("cleans", [])}
|
|
172
|
+
)
|
|
173
|
+
self.add_node(san_node)
|
|
174
|
+
# Link if any tainted var was input to sanitizer
|
|
175
|
+
for tv_name, tv_node in list(tainted_vars.items()):
|
|
176
|
+
if re.search(r"\b" + re.escape(tv_name) + r"\b", rhs_expr):
|
|
177
|
+
self.add_edge(tv_node, san_node, edge_type="argument", line_number=line_num)
|
|
178
|
+
tainted_vars[lhs_var] = san_node
|
|
179
|
+
is_sanitized = True
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
if not is_sanitized:
|
|
183
|
+
# Check if RHS mentions any known tainted variable
|
|
184
|
+
for tv_name, tv_node in list(tainted_vars.items()):
|
|
185
|
+
if re.search(r"\b" + re.escape(tv_name) + r"\b", rhs_expr):
|
|
186
|
+
transform_node = TaintNode(
|
|
187
|
+
variable_name=lhs_var,
|
|
188
|
+
file_path=file_path,
|
|
189
|
+
line_number=line_num,
|
|
190
|
+
node_type="transform",
|
|
191
|
+
expression=rhs_expr
|
|
192
|
+
)
|
|
193
|
+
self.add_node(transform_node)
|
|
194
|
+
self.add_edge(tv_node, transform_node, edge_type="assignment", line_number=line_num)
|
|
195
|
+
tainted_vars[lhs_var] = transform_node
|
|
196
|
+
|
|
197
|
+
# Check for Sinks
|
|
198
|
+
for sink_spec in sinks_catalog:
|
|
199
|
+
sink_pat = sink_spec["pattern"]
|
|
200
|
+
if sink_pat in stripped:
|
|
201
|
+
sink_node = TaintNode(
|
|
202
|
+
variable_name="",
|
|
203
|
+
file_path=file_path,
|
|
204
|
+
line_number=line_num,
|
|
205
|
+
node_type="sink",
|
|
206
|
+
expression=stripped,
|
|
207
|
+
pattern=sink_pat,
|
|
208
|
+
category=sink_spec.get("category", ""),
|
|
209
|
+
metadata=sink_spec
|
|
210
|
+
)
|
|
211
|
+
self.add_node(sink_node)
|
|
212
|
+
detected_sinks.append((sink_node, sink_spec))
|
|
213
|
+
|
|
214
|
+
# Check which tainted variables are passed into or present in the sink line
|
|
215
|
+
for tv_name, tv_node in list(tainted_vars.items()):
|
|
216
|
+
if re.search(r"\b" + re.escape(tv_name) + r"\b", stripped):
|
|
217
|
+
self.add_edge(tv_node, sink_node, edge_type="call", line_number=line_num)
|
|
218
|
+
|
|
219
|
+
# 2. Path Finding & Resolution
|
|
220
|
+
results: List[TaintPath] = []
|
|
221
|
+
for sink_node, sink_spec in detected_sinks:
|
|
222
|
+
for src_node in detected_sources:
|
|
223
|
+
paths = self.find_tainted_paths(src_node, sink_node)
|
|
224
|
+
for node_path in paths:
|
|
225
|
+
is_sanitized, sanitizers = self.is_path_sanitized(node_path, sink_spec.get("category", ""), language)
|
|
226
|
+
results.append(TaintPath(
|
|
227
|
+
source=src_node,
|
|
228
|
+
sink=sink_node,
|
|
229
|
+
path=node_path,
|
|
230
|
+
is_sanitized=is_sanitized,
|
|
231
|
+
sanitizers_found=sanitizers,
|
|
232
|
+
confidence_penalty=-40 if is_sanitized else 0
|
|
233
|
+
))
|
|
234
|
+
|
|
235
|
+
return results
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Taint Engine — Polyglot Source, Sink, and Sanitizer Catalogs
|
|
3
|
+
Covers Python, JavaScript/TypeScript, Go, Rust, Java, Ruby, PHP, and C#.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, List, Any, Optional
|
|
7
|
+
|
|
8
|
+
TAINT_SOURCES: Dict[str, List[Dict[str, Any]]] = {
|
|
9
|
+
"python": [
|
|
10
|
+
{"pattern": "request.GET", "framework": "Django", "label": "HTTP Query Param", "category": "user_input"},
|
|
11
|
+
{"pattern": "request.POST", "framework": "Django", "label": "HTTP POST Body", "category": "user_input"},
|
|
12
|
+
{"pattern": "request.data", "framework": "DRF", "label": "DRF Request Data", "category": "user_input"},
|
|
13
|
+
{"pattern": "request.query_params", "framework": "DRF", "label": "DRF Query Params", "category": "user_input"},
|
|
14
|
+
{"pattern": "request.json", "framework": "Flask", "label": "Flask JSON Body", "category": "user_input"},
|
|
15
|
+
{"pattern": "request.args", "framework": "Flask", "label": "Flask Query Param", "category": "user_input"},
|
|
16
|
+
{"pattern": "request.form", "framework": "Flask", "label": "Flask Form Data", "category": "user_input"},
|
|
17
|
+
{"pattern": "request.values", "framework": "Flask", "label": "Flask Form/Args Data", "category": "user_input"},
|
|
18
|
+
{"pattern": "request.headers", "framework": "Flask/Django", "label": "HTTP Headers", "category": "header"},
|
|
19
|
+
{"pattern": "sys.argv", "framework": "stdlib", "label": "CLI Argument", "category": "cli"},
|
|
20
|
+
{"pattern": "os.environ", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
21
|
+
{"pattern": "os.getenv", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
22
|
+
{"pattern": "input(", "framework": "stdlib", "label": "User Console Input", "category": "user_input"},
|
|
23
|
+
{"pattern": "Body(", "framework": "FastAPI", "label": "FastAPI Body Param", "category": "user_input"},
|
|
24
|
+
{"pattern": "Query(", "framework": "FastAPI", "label": "FastAPI Query Param", "category": "user_input"},
|
|
25
|
+
{"pattern": "Header(", "framework": "FastAPI", "label": "FastAPI Header Param", "category": "header"},
|
|
26
|
+
{"pattern": "Path(", "framework": "FastAPI", "label": "FastAPI Path Param", "category": "user_input"},
|
|
27
|
+
],
|
|
28
|
+
"javascript": [
|
|
29
|
+
{"pattern": "req.body", "framework": "Express", "label": "Express Request Body", "category": "user_input"},
|
|
30
|
+
{"pattern": "req.query", "framework": "Express", "label": "Express Query Params", "category": "user_input"},
|
|
31
|
+
{"pattern": "req.params", "framework": "Express", "label": "Express Route Params", "category": "user_input"},
|
|
32
|
+
{"pattern": "req.headers", "framework": "Express", "label": "Express Headers", "category": "header"},
|
|
33
|
+
{"pattern": "request.json()", "framework": "Next.js", "label": "Next.js Request JSON", "category": "user_input"},
|
|
34
|
+
{"pattern": "request.formData()", "framework": "Next.js", "label": "Next.js FormData", "category": "user_input"},
|
|
35
|
+
{"pattern": "formData", "framework": "Next.js", "label": "Server Action FormData", "category": "user_input"},
|
|
36
|
+
{"pattern": "cookies()", "framework": "Next.js", "label": "Next.js Cookies", "category": "user_input"},
|
|
37
|
+
{"pattern": "searchParams", "framework": "Web API", "label": "URL Search Params", "category": "user_input"},
|
|
38
|
+
{"pattern": "event.body", "framework": "Lambda", "label": "AWS Lambda Event Body", "category": "user_input"},
|
|
39
|
+
{"pattern": "event.queryStringParameters", "framework": "Lambda", "label": "Lambda Query Params", "category": "user_input"},
|
|
40
|
+
{"pattern": "process.argv", "framework": "Node stdlib", "label": "CLI Arguments", "category": "cli"},
|
|
41
|
+
{"pattern": "process.env", "framework": "Node stdlib", "label": "Environment Variables", "category": "env"},
|
|
42
|
+
{"pattern": "location.search", "framework": "DOM", "label": "DOM Search Query", "category": "dom"},
|
|
43
|
+
{"pattern": "location.hash", "framework": "DOM", "label": "DOM Hash Fragment", "category": "dom"},
|
|
44
|
+
{"pattern": "window.name", "framework": "DOM", "label": "DOM Window Name", "category": "dom"},
|
|
45
|
+
],
|
|
46
|
+
"go": [
|
|
47
|
+
{"pattern": "r.URL.Query()", "framework": "net/http", "label": "HTTP Query Params", "category": "user_input"},
|
|
48
|
+
{"pattern": "r.FormValue(", "framework": "net/http", "label": "HTTP Form Value", "category": "user_input"},
|
|
49
|
+
{"pattern": "r.PostFormValue(", "framework": "net/http", "label": "HTTP Post Form Value", "category": "user_input"},
|
|
50
|
+
{"pattern": "r.Body", "framework": "net/http", "label": "HTTP Request Body", "category": "user_input"},
|
|
51
|
+
{"pattern": "r.Header.Get(", "framework": "net/http", "label": "HTTP Header", "category": "header"},
|
|
52
|
+
{"pattern": "c.Param(", "framework": "Gin", "label": "Gin Route Param", "category": "user_input"},
|
|
53
|
+
{"pattern": "c.Query(", "framework": "Gin", "label": "Gin Query Param", "category": "user_input"},
|
|
54
|
+
{"pattern": "c.PostForm(", "framework": "Gin", "label": "Gin Form Param", "category": "user_input"},
|
|
55
|
+
{"pattern": "c.BindJSON(", "framework": "Gin", "label": "Gin JSON Binding", "category": "user_input"},
|
|
56
|
+
{"pattern": "os.Args", "framework": "stdlib", "label": "CLI Arguments", "category": "cli"},
|
|
57
|
+
{"pattern": "os.Getenv(", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
58
|
+
],
|
|
59
|
+
"rust": [
|
|
60
|
+
{"pattern": "web::Query", "framework": "actix-web", "label": "Actix Query Extractor", "category": "user_input"},
|
|
61
|
+
{"pattern": "web::Json", "framework": "actix-web", "label": "Actix JSON Extractor", "category": "user_input"},
|
|
62
|
+
{"pattern": "web::Path", "framework": "actix-web", "label": "Actix Path Extractor", "category": "user_input"},
|
|
63
|
+
{"pattern": "axum::extract::Query", "framework": "axum", "label": "Axum Query Extractor", "category": "user_input"},
|
|
64
|
+
{"pattern": "axum::extract::Json", "framework": "axum", "label": "Axum JSON Extractor", "category": "user_input"},
|
|
65
|
+
{"pattern": "std::env::args", "framework": "stdlib", "label": "CLI Arguments", "category": "cli"},
|
|
66
|
+
{"pattern": "std::env::var", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
67
|
+
],
|
|
68
|
+
"java": [
|
|
69
|
+
{"pattern": "@RequestParam", "framework": "Spring", "label": "Spring Request Param", "category": "user_input"},
|
|
70
|
+
{"pattern": "@RequestBody", "framework": "Spring", "label": "Spring Request Body", "category": "user_input"},
|
|
71
|
+
{"pattern": "@PathVariable", "framework": "Spring", "label": "Spring Path Variable", "category": "user_input"},
|
|
72
|
+
{"pattern": "@RequestHeader", "framework": "Spring", "label": "Spring Header", "category": "header"},
|
|
73
|
+
{"pattern": "request.getParameter(", "framework": "Servlet", "label": "Servlet Param", "category": "user_input"},
|
|
74
|
+
{"pattern": "request.getInputStream(", "framework": "Servlet", "label": "Servlet Stream", "category": "user_input"},
|
|
75
|
+
{"pattern": "System.getenv(", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
76
|
+
],
|
|
77
|
+
"ruby": [
|
|
78
|
+
{"pattern": "params[", "framework": "Rails", "label": "Rails Parameters", "category": "user_input"},
|
|
79
|
+
{"pattern": "request.body", "framework": "Rails", "label": "Rails Request Body", "category": "user_input"},
|
|
80
|
+
{"pattern": "request.headers", "framework": "Rails", "label": "Rails Headers", "category": "header"},
|
|
81
|
+
{"pattern": "ENV[", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
82
|
+
{"pattern": "ARGV[", "framework": "stdlib", "label": "CLI Argument", "category": "cli"},
|
|
83
|
+
],
|
|
84
|
+
"php": [
|
|
85
|
+
{"pattern": "$_GET", "framework": "PHP Superglobals", "label": "GET Parameters", "category": "user_input"},
|
|
86
|
+
{"pattern": "$_POST", "framework": "PHP Superglobals", "label": "POST Parameters", "category": "user_input"},
|
|
87
|
+
{"pattern": "$_REQUEST", "framework": "PHP Superglobals", "label": "REQUEST Parameters", "category": "user_input"},
|
|
88
|
+
{"pattern": "$_COOKIE", "framework": "PHP Superglobals", "label": "Cookie Parameters", "category": "user_input"},
|
|
89
|
+
{"pattern": "$_SERVER['HTTP_", "framework": "PHP Superglobals", "label": "HTTP Headers", "category": "header"},
|
|
90
|
+
{"pattern": "$request->input(", "framework": "Laravel", "label": "Laravel Input", "category": "user_input"},
|
|
91
|
+
{"pattern": "$request->query(", "framework": "Laravel", "label": "Laravel Query", "category": "user_input"},
|
|
92
|
+
],
|
|
93
|
+
"csharp": [
|
|
94
|
+
{"pattern": "[FromQuery]", "framework": "ASP.NET Core", "label": "Query Parameter", "category": "user_input"},
|
|
95
|
+
{"pattern": "[FromBody]", "framework": "ASP.NET Core", "label": "Body Parameter", "category": "user_input"},
|
|
96
|
+
{"pattern": "[FromRoute]", "framework": "ASP.NET Core", "label": "Route Parameter", "category": "user_input"},
|
|
97
|
+
{"pattern": "[FromHeader]", "framework": "ASP.NET Core", "label": "Header Parameter", "category": "header"},
|
|
98
|
+
{"pattern": "Request.Query[", "framework": "ASP.NET Core", "label": "Request Query", "category": "user_input"},
|
|
99
|
+
{"pattern": "Request.Form[", "framework": "ASP.NET Core", "label": "Request Form", "category": "user_input"},
|
|
100
|
+
{"pattern": "Environment.GetEnvironmentVariable(", "framework": "stdlib", "label": "Environment Variable", "category": "env"},
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
TAINT_SINKS: Dict[str, List[Dict[str, Any]]] = {
|
|
105
|
+
"python": [
|
|
106
|
+
{"pattern": "execute(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
107
|
+
{"pattern": "raw(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
108
|
+
{"pattern": "cursor.execute(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
109
|
+
{"pattern": "os.system(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
110
|
+
{"pattern": "subprocess.call(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
111
|
+
{"pattern": "subprocess.Popen(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
112
|
+
{"pattern": "subprocess.run(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
113
|
+
{"pattern": "eval(", "category": "code-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
114
|
+
{"pattern": "exec(", "category": "code-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
115
|
+
{"pattern": "open(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
116
|
+
{"pattern": "send_file(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
117
|
+
{"pattern": "redirect(", "category": "open-redirect", "rule": "TG-INPUT-007", "severity": "Medium"},
|
|
118
|
+
{"pattern": "mark_safe(", "category": "xss", "rule": "TG-INPUT-005", "severity": "High"},
|
|
119
|
+
{"pattern": "render_template_string(", "category": "ssti", "rule": "TG-INPUT-005", "severity": "High"},
|
|
120
|
+
{"pattern": "pickle.loads(", "category": "deserialization", "rule": "TG-INPUT-008", "severity": "Critical"},
|
|
121
|
+
{"pattern": "yaml.load(", "category": "deserialization", "rule": "TG-INPUT-008", "severity": "High"},
|
|
122
|
+
{"pattern": "requests.get(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
123
|
+
{"pattern": "requests.post(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
124
|
+
{"pattern": "httpx.get(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
125
|
+
{"pattern": "urllib.request.urlopen(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
126
|
+
{"pattern": "jwt.decode(", "category": "jwt-integrity", "rule": "TG-AUTH-006", "severity": "High"},
|
|
127
|
+
],
|
|
128
|
+
"javascript": [
|
|
129
|
+
{"pattern": "innerHTML", "category": "xss", "rule": "TG-INPUT-003", "severity": "High"},
|
|
130
|
+
{"pattern": "dangerouslySetInnerHTML", "category": "xss", "rule": "TG-INPUT-003", "severity": "High"},
|
|
131
|
+
{"pattern": "document.write(", "category": "xss", "rule": "TG-INPUT-003", "severity": "High"},
|
|
132
|
+
{"pattern": ".query(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
133
|
+
{"pattern": "$queryRawUnsafe", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
134
|
+
{"pattern": "child_process.exec(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
135
|
+
{"pattern": "child_process.spawn(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
136
|
+
{"pattern": "eval(", "category": "code-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
137
|
+
{"pattern": "new Function(", "category": "code-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
138
|
+
{"pattern": "fetch(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
139
|
+
{"pattern": "axios.get(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
140
|
+
{"pattern": "axios.post(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
141
|
+
{"pattern": "res.redirect(", "category": "open-redirect", "rule": "TG-INPUT-007", "severity": "Medium"},
|
|
142
|
+
{"pattern": "fs.readFile(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
143
|
+
{"pattern": "fs.createReadStream(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
144
|
+
],
|
|
145
|
+
"go": [
|
|
146
|
+
{"pattern": "db.Query(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
147
|
+
{"pattern": "db.Exec(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
148
|
+
{"pattern": "exec.Command(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
149
|
+
{"pattern": "http.Get(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
150
|
+
{"pattern": "http.Post(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
151
|
+
{"pattern": "os.Open(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
152
|
+
{"pattern": "os.ReadFile(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
153
|
+
{"pattern": "template.HTML(", "category": "xss", "rule": "TG-INPUT-005", "severity": "High"},
|
|
154
|
+
{"pattern": "http.Redirect(", "category": "open-redirect", "rule": "TG-INPUT-007", "severity": "Medium"},
|
|
155
|
+
],
|
|
156
|
+
"rust": [
|
|
157
|
+
{"pattern": "sqlx::query(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
158
|
+
{"pattern": "Command::new(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
159
|
+
{"pattern": "reqwest::get(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
160
|
+
{"pattern": "std::fs::read(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
161
|
+
],
|
|
162
|
+
"java": [
|
|
163
|
+
{"pattern": "statement.executeQuery(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
164
|
+
{"pattern": "Runtime.getRuntime().exec(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
165
|
+
{"pattern": "new ProcessBuilder(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
166
|
+
{"pattern": "new URL(", "category": "ssrf", "rule": "TG-SSRF-001", "severity": "High"},
|
|
167
|
+
{"pattern": "new FileInputStream(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
168
|
+
],
|
|
169
|
+
"ruby": [
|
|
170
|
+
{"pattern": "ActiveRecord::Base.connection.execute(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
171
|
+
{"pattern": "system(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
172
|
+
{"pattern": "send_file ", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
173
|
+
{"pattern": "redirect_to ", "category": "open-redirect", "rule": "TG-INPUT-007", "severity": "Medium"},
|
|
174
|
+
],
|
|
175
|
+
"php": [
|
|
176
|
+
{"pattern": "mysqli_query(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
177
|
+
{"pattern": "PDO::query(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
178
|
+
{"pattern": "system(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
179
|
+
{"pattern": "shell_exec(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
180
|
+
{"pattern": "include(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
181
|
+
{"pattern": "header('Location:", "category": "open-redirect", "rule": "TG-INPUT-007", "severity": "Medium"},
|
|
182
|
+
],
|
|
183
|
+
"csharp": [
|
|
184
|
+
{"pattern": "SqlCommand(", "category": "sql-injection", "rule": "TG-INPUT-002", "severity": "Critical"},
|
|
185
|
+
{"pattern": "Process.Start(", "category": "command-injection", "rule": "TG-INPUT-003", "severity": "Critical"},
|
|
186
|
+
{"pattern": "File.OpenRead(", "category": "path-traversal", "rule": "TG-INPUT-006", "severity": "High"},
|
|
187
|
+
{"pattern": "Redirect(", "category": "open-redirect", "rule": "TG-INPUT-007", "severity": "Medium"},
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
TAINT_SANITIZERS: Dict[str, List[Dict[str, Any]]] = {
|
|
192
|
+
"python": [
|
|
193
|
+
{"pattern": "int(", "cleans": ["sql-injection", "command-injection", "path-traversal", "ssrf"]},
|
|
194
|
+
{"pattern": "float(", "cleans": ["sql-injection", "command-injection"]},
|
|
195
|
+
{"pattern": "escape(", "cleans": ["xss", "ssti"]},
|
|
196
|
+
{"pattern": "html.escape(", "cleans": ["xss", "ssti"]},
|
|
197
|
+
{"pattern": "bleach.clean(", "cleans": ["xss"]},
|
|
198
|
+
{"pattern": "shlex.quote(", "cleans": ["command-injection"]},
|
|
199
|
+
{"pattern": "secure_filename(", "cleans": ["path-traversal"]},
|
|
200
|
+
{"pattern": "os.path.basename(", "cleans": ["path-traversal"]},
|
|
201
|
+
{"pattern": "Path.resolve()", "cleans": ["path-traversal"]},
|
|
202
|
+
{"pattern": "validators.url(", "cleans": ["ssrf", "open-redirect"]},
|
|
203
|
+
{"pattern": "Depends(", "cleans": ["auth-bypass"]},
|
|
204
|
+
{"pattern": "SafeLoader", "cleans": ["deserialization"]},
|
|
205
|
+
{"pattern": ".safeParse(", "cleans": ["sql-injection", "xss", "command-injection"]},
|
|
206
|
+
],
|
|
207
|
+
"javascript": [
|
|
208
|
+
{"pattern": "parseInt(", "cleans": ["sql-injection", "command-injection", "path-traversal", "ssrf"]},
|
|
209
|
+
{"pattern": "Number(", "cleans": ["sql-injection", "command-injection", "path-traversal"]},
|
|
210
|
+
{"pattern": "encodeURIComponent(", "cleans": ["xss", "open-redirect"]},
|
|
211
|
+
{"pattern": "DOMPurify.sanitize(", "cleans": ["xss"]},
|
|
212
|
+
{"pattern": "z.string().parse(", "cleans": ["sql-injection", "xss"]},
|
|
213
|
+
{"pattern": "z.number()", "cleans": ["sql-injection", "command-injection"]},
|
|
214
|
+
{"pattern": "path.basename(", "cleans": ["path-traversal"]},
|
|
215
|
+
{"pattern": "path.normalize(", "cleans": ["path-traversal"]},
|
|
216
|
+
{"pattern": "escapeHtml(", "cleans": ["xss"]},
|
|
217
|
+
{"pattern": "validator.isURL(", "cleans": ["ssrf", "open-redirect"]},
|
|
218
|
+
],
|
|
219
|
+
"go": [
|
|
220
|
+
{"pattern": "strconv.Atoi(", "cleans": ["sql-injection", "command-injection", "path-traversal"]},
|
|
221
|
+
{"pattern": "strconv.ParseInt(", "cleans": ["sql-injection", "command-injection"]},
|
|
222
|
+
{"pattern": "filepath.Base(", "cleans": ["path-traversal"]},
|
|
223
|
+
{"pattern": "filepath.Clean(", "cleans": ["path-traversal"]},
|
|
224
|
+
{"pattern": "html.EscapeString(", "cleans": ["xss"]},
|
|
225
|
+
{"pattern": "url.Parse(", "cleans": ["open-redirect", "ssrf"]},
|
|
226
|
+
],
|
|
227
|
+
"rust": [
|
|
228
|
+
{"pattern": "parse::<i32>", "cleans": ["sql-injection", "command-injection"]},
|
|
229
|
+
{"pattern": "parse::<u32>", "cleans": ["sql-injection", "command-injection"]},
|
|
230
|
+
{"pattern": "Path::new(", "cleans": ["path-traversal"]},
|
|
231
|
+
],
|
|
232
|
+
"java": [
|
|
233
|
+
{"pattern": "Integer.parseInt(", "cleans": ["sql-injection", "command-injection"]},
|
|
234
|
+
{"pattern": "Long.parseLong(", "cleans": ["sql-injection", "command-injection"]},
|
|
235
|
+
{"pattern": "Paths.get(", "cleans": ["path-traversal"]},
|
|
236
|
+
{"pattern": "ESAPI.encoder()", "cleans": ["xss", "sql-injection"]},
|
|
237
|
+
],
|
|
238
|
+
"ruby": [
|
|
239
|
+
{"pattern": ".to_i", "cleans": ["sql-injection", "command-injection"]},
|
|
240
|
+
{"pattern": "File.basename(", "cleans": ["path-traversal"]},
|
|
241
|
+
{"pattern": "CGI.escapeHTML(", "cleans": ["xss"]},
|
|
242
|
+
],
|
|
243
|
+
"php": [
|
|
244
|
+
{"pattern": "intval(", "cleans": ["sql-injection", "command-injection"]},
|
|
245
|
+
{"pattern": "(int)", "cleans": ["sql-injection", "command-injection"]},
|
|
246
|
+
{"pattern": "htmlspecialchars(", "cleans": ["xss"]},
|
|
247
|
+
{"pattern": "basename(", "cleans": ["path-traversal"]},
|
|
248
|
+
{"pattern": "filter_var(", "cleans": ["ssrf", "xss", "sql-injection"]},
|
|
249
|
+
],
|
|
250
|
+
"csharp": [
|
|
251
|
+
{"pattern": "int.Parse(", "cleans": ["sql-injection", "command-injection"]},
|
|
252
|
+
{"pattern": "int.TryParse(", "cleans": ["sql-injection", "command-injection"]},
|
|
253
|
+
{"pattern": "Path.GetFileName(", "cleans": ["path-traversal"]},
|
|
254
|
+
{"pattern": "HttpUtility.HtmlEncode(", "cleans": ["xss"]},
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_sources_for_language(lang: str) -> List[Dict[str, Any]]:
|
|
260
|
+
return TAINT_SOURCES.get(lang.lower(), [])
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_sinks_for_language(lang: str) -> List[Dict[str, Any]]:
|
|
264
|
+
return TAINT_SINKS.get(lang.lower(), [])
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_sanitizers_for_language(lang: str) -> List[Dict[str, Any]]:
|
|
268
|
+
return TAINT_SANITIZERS.get(lang.lower(), [])
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v0.7.0 Unified Reporting Engine
|
|
3
|
+
Combines static code analysis with runtime validation evidence, exploitability confidence,
|
|
4
|
+
affected routes, remediation updates, and residual risk assessments into actionable Markdown.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Dict, Any, Optional
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class V070Reporter:
|
|
13
|
+
"""
|
|
14
|
+
Renders combined static + runtime validation reports for TorusGuard v0.7.0.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def render_combined_summary(
|
|
19
|
+
cls,
|
|
20
|
+
target_name: str,
|
|
21
|
+
run_id: str,
|
|
22
|
+
auth_id: str,
|
|
23
|
+
static_findings: List[Dict[str, Any]],
|
|
24
|
+
runtime_results: List[Dict[str, Any]],
|
|
25
|
+
clusters: List[Any],
|
|
26
|
+
manual_review_items: Optional[List[Dict[str, Any]]] = None
|
|
27
|
+
) -> str:
|
|
28
|
+
now = datetime.now(timezone.utc).strftime("%B %d, %Y")
|
|
29
|
+
confirmed_count = sum(1 for r in runtime_results if r.get("status") == "Runtime Confirmed")
|
|
30
|
+
likely_count = sum(1 for r in runtime_results if r.get("status") == "Runtime Likely")
|
|
31
|
+
not_repro_count = sum(1 for r in runtime_results if r.get("status") == "Not Reproducible in Scope")
|
|
32
|
+
blocked_count = sum(1 for r in runtime_results if r.get("status") == "Blocked by Environment / Controls")
|
|
33
|
+
|
|
34
|
+
lines = [
|
|
35
|
+
f"# TorusGuard v0.7.0 Unified Security & Runtime Exploitability Report",
|
|
36
|
+
f"\n**Target Application:** `{target_name}`",
|
|
37
|
+
f"**Run Identifier:** `{run_id}`",
|
|
38
|
+
f"**Authorization Reference:** `{auth_id}`",
|
|
39
|
+
f"**Execution Date:** {now}",
|
|
40
|
+
f"**Static Findings Detected:** `{len(static_findings)}`",
|
|
41
|
+
f"**Runtime Confirmed Exploitable:** `{confirmed_count}`",
|
|
42
|
+
f"**Runtime Likely Exploitable:** `{likely_count}`",
|
|
43
|
+
f"**Not Reproducible at Runtime:** `{not_repro_count}`",
|
|
44
|
+
f"**Blocked by Controls/Safety:** `{blocked_count}`",
|
|
45
|
+
"\n---",
|
|
46
|
+
"\n## 🎯 Executive Summary & Exploitability Breakdown",
|
|
47
|
+
"\nThis report pairs static source-code analysis with authorized, bounded runtime validation to distinguish theoretical risks from practically reachable and exploitable vulnerabilities.",
|
|
48
|
+
"\n| Metric | Count | Governance Status |",
|
|
49
|
+
"|---|:---:|---|",
|
|
50
|
+
f"| **Runtime Confirmed Exploitable** | `{confirmed_count}` | 🔴 **Immediate Remediation Required** (Reproducible in live scope) |",
|
|
51
|
+
f"| **Runtime Likely** | `{likely_count}` | 🟠 High Priority (Strong behavioral indicators observed) |",
|
|
52
|
+
f"| **Needs Manual Review** | `{len(manual_review_items or [])}` | 🟡 Engineer Sign-Off Required (Ambiguous boundary) |",
|
|
53
|
+
f"| **Not Reproducible in Scope** | `{not_repro_count}` | 🟢 Mitigated at Runtime (Gateway / Middleware active) |",
|
|
54
|
+
f"| **Blocked by Environment / Controls** | `{blocked_count}` | ⚪ Safety Gate Halted Active Probing |",
|
|
55
|
+
"\n---",
|
|
56
|
+
"\n## 🌐 Endpoint & Route Exploitability Matrix\n",
|
|
57
|
+
"| Finding ID | Rule ID | Target Endpoint | Static Severity | Runtime Exploitability | Reproducible? |",
|
|
58
|
+
"|---|---|---|:---:|:---:|:---:|",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
rt_map = {r.get("finding_id"): r for r in runtime_results}
|
|
62
|
+
for f in static_findings:
|
|
63
|
+
f_id = f.get("finding_id")
|
|
64
|
+
rt = rt_map.get(f_id, {})
|
|
65
|
+
probe_url = rt.get("probe_url", f.get("target", {}).get("file_path", "N/A"))
|
|
66
|
+
status = rt.get("status", "Not Evaluated")
|
|
67
|
+
repro = "✅ Yes" if rt.get("reproducible") else "❌ No"
|
|
68
|
+
lines.append(
|
|
69
|
+
f"| `{f_id}` | `{f.get('rule_id')}` | `{probe_url}` | {f.get('severity', 'Medium')} | **{status}** | {repro} |"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
lines.extend([
|
|
73
|
+
"\n---",
|
|
74
|
+
"\n## 🛡️ Root-Cause Cluster Remediation Plans\n",
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
for c in clusters:
|
|
78
|
+
c_id = getattr(c, "cluster_id", "cluster-general")
|
|
79
|
+
c_title = getattr(c, "title", "General Cluster")
|
|
80
|
+
c_findings = getattr(c, "finding_ids", [])
|
|
81
|
+
lines.extend([
|
|
82
|
+
f"### Root Cause: `{c_id}` — {c_title}",
|
|
83
|
+
f"- **Affected Finding Count:** `{len(c_findings)}` findings",
|
|
84
|
+
f"- **Systemic Fix Advice:** {getattr(c, 'remediation_advice', 'Follow minimal patch guidelines.')}",
|
|
85
|
+
""
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
if manual_review_items:
|
|
89
|
+
lines.extend([
|
|
90
|
+
"\n---",
|
|
91
|
+
"\n## ⚠️ Manual-Review Items & Residual Risk Queue\n",
|
|
92
|
+
])
|
|
93
|
+
for item in manual_review_items:
|
|
94
|
+
lines.append(f"- **Finding `{item.get('finding_id')}`:** {item.get('reason', 'Requires human confirmation.')}")
|
|
95
|
+
|
|
96
|
+
lines.extend([
|
|
97
|
+
"\n---",
|
|
98
|
+
"\n## 🔒 Safety & Governance Audit Statement",
|
|
99
|
+
"All runtime probes documented in this report were executed under strict target authorization and non-destructive safety gates. No state-destroying actions, unauthorized hosts, or weaponized exploit chains were utilized.",
|
|
100
|
+
])
|
|
101
|
+
|
|
102
|
+
return "\n".join(lines) + "\n"
|