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.
- package/.torusguard/.manifest.json +47 -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/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__/rules_sync.cpython-314.pyc +0 -0
- package/.torusguard/scripts/audit_runner.py +108 -10
- package/.torusguard/scripts/finding_scorer.py +43 -13
- package/.torusguard/scripts/skill_profiler.py +26 -0
- package/.torusguard/skills/torusguard/SKILL.md +6 -2
- package/.torusguard/skills/torusguard-audit/SKILL.md +109 -84
- package/.torusguard/workflows/audit.md +21 -17
- package/README.md +19 -11
- package/package.json +7 -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 +48 -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/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/finding_scorer.py +43 -13
- 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/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/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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v6 SARIF v2.1.0 Exporter
|
|
3
|
+
Emits standard SARIF-compatible JSON reports for CI/CD, GitHub Security Code Scanning, and SIEM tool integration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SarifExporter:
|
|
12
|
+
"""
|
|
13
|
+
Transforms TorusGuard findings and clusters into standard SARIF v2.1.0 format.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
SARIF_VERSION = "2.1.0"
|
|
17
|
+
SARIF_SCHEMA = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def map_severity_to_level(severity: str) -> str:
|
|
21
|
+
"""Maps TorusGuard SeverityLevel to standard SARIF result level."""
|
|
22
|
+
if severity in ["Critical", "High"]:
|
|
23
|
+
return "error"
|
|
24
|
+
elif severity in ["Low", "Informational"]:
|
|
25
|
+
return "note"
|
|
26
|
+
return "warning"
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _build_rule_entry(rule_id: str, title: str, sarif_level: str, category: str = "security") -> Dict[str, Any]:
|
|
30
|
+
"""Constructs a SARIF reportingDescriptor object for a rule."""
|
|
31
|
+
return {
|
|
32
|
+
"id": rule_id,
|
|
33
|
+
"name": rule_id.replace("-", "_"),
|
|
34
|
+
"shortDescription": {"text": title},
|
|
35
|
+
"fullDescription": {"text": f"TorusGuard Rule {rule_id}: {title}"},
|
|
36
|
+
"defaultConfiguration": {"level": sarif_level},
|
|
37
|
+
"help": {"text": f"Read documentation for {rule_id} at https://github.com/githubmofo/TorusGuard"},
|
|
38
|
+
"properties": {"category": category}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def _build_result_entry(
|
|
43
|
+
cls, f: Dict[str, Any], rule_index: int, sarif_level: str, analysis_category: str
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
"""Constructs a single SARIF result item with fingerprints and location."""
|
|
46
|
+
rule_id = f.get("rule_id", "TG-GENERIC")
|
|
47
|
+
finding_id = f.get("finding_id", "")
|
|
48
|
+
title = f.get("title", rule_id)
|
|
49
|
+
confidence_score = f.get("confidence_score", 80)
|
|
50
|
+
confidence_band = f.get("confidence_band", "High Confidence")
|
|
51
|
+
cluster_id = f.get("cluster_id", "")
|
|
52
|
+
|
|
53
|
+
target = f.get("target", {})
|
|
54
|
+
file_path = target.get("file_path", "unknown").replace("\\", "/")
|
|
55
|
+
start_line = max(1, target.get("line_start", 1))
|
|
56
|
+
end_line = max(start_line, target.get("line_end", start_line))
|
|
57
|
+
evidence_snippet = f.get("evidence", {}).get("code_snippet", "")
|
|
58
|
+
fingerprint = f.get("fingerprint_id", finding_id)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"ruleId": rule_id,
|
|
62
|
+
"ruleIndex": rule_index,
|
|
63
|
+
"level": sarif_level,
|
|
64
|
+
"message": {
|
|
65
|
+
"text": f"[{rule_id}] {title} (Confidence: {confidence_score}/100 - {confidence_band})"
|
|
66
|
+
},
|
|
67
|
+
"locations": [
|
|
68
|
+
{
|
|
69
|
+
"physicalLocation": {
|
|
70
|
+
"artifactLocation": {
|
|
71
|
+
"uri": file_path,
|
|
72
|
+
"uriBaseId": "%SRCROOT%"
|
|
73
|
+
},
|
|
74
|
+
"region": {
|
|
75
|
+
"startLine": start_line,
|
|
76
|
+
"endLine": end_line,
|
|
77
|
+
"snippet": {"text": evidence_snippet}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"partialFingerprints": {
|
|
83
|
+
"primaryLocationLineHash": fingerprint[:16] if fingerprint else "0" * 16,
|
|
84
|
+
"torusguard/v6/identity": fingerprint
|
|
85
|
+
},
|
|
86
|
+
"fingerprints": {
|
|
87
|
+
"torusguard/v6/stable_identity": fingerprint
|
|
88
|
+
},
|
|
89
|
+
"properties": {
|
|
90
|
+
"finding_id": finding_id,
|
|
91
|
+
"confidence_score": confidence_score,
|
|
92
|
+
"confidence_band": confidence_band,
|
|
93
|
+
"cluster_id": cluster_id,
|
|
94
|
+
"recheck_status": f.get("recheck_status", "Unrechecked"),
|
|
95
|
+
"runtime_exploitability": f.get("runtime_exploitability", "Not Tested"),
|
|
96
|
+
"tags": ["security", "governed-remediation", cluster_id, analysis_category]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def generate_sarif(
|
|
102
|
+
cls,
|
|
103
|
+
findings: List[Dict[str, Any]],
|
|
104
|
+
clusters: Optional[List[Dict[str, Any]]] = None,
|
|
105
|
+
tool_version: str = "0.7.0",
|
|
106
|
+
analysis_category: str = "torusguard/static",
|
|
107
|
+
) -> Dict[str, Any]:
|
|
108
|
+
"""
|
|
109
|
+
Builds a complete SARIF log dict supporting multi-analysis category partitioning.
|
|
110
|
+
"""
|
|
111
|
+
rules_map: Dict[str, Dict[str, Any]] = {}
|
|
112
|
+
results: List[Dict[str, Any]] = []
|
|
113
|
+
|
|
114
|
+
for f in findings:
|
|
115
|
+
rule_id = f.get("rule_id", "TG-GENERIC")
|
|
116
|
+
title = f.get("title", rule_id)
|
|
117
|
+
severity = f.get("severity", "High")
|
|
118
|
+
sarif_level = cls.map_severity_to_level(severity)
|
|
119
|
+
|
|
120
|
+
if rule_id not in rules_map:
|
|
121
|
+
rules_map[rule_id] = cls._build_rule_entry(
|
|
122
|
+
rule_id, title, sarif_level, category=f.get("category", "security")
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
rule_index = list(rules_map.keys()).index(rule_id)
|
|
126
|
+
result_obj = cls._build_result_entry(f, rule_index, sarif_level, analysis_category)
|
|
127
|
+
results.append(result_obj)
|
|
128
|
+
|
|
129
|
+
sarif_payload = {
|
|
130
|
+
"$schema": cls.SARIF_SCHEMA,
|
|
131
|
+
"version": cls.SARIF_VERSION,
|
|
132
|
+
"runs": [
|
|
133
|
+
{
|
|
134
|
+
"tool": {
|
|
135
|
+
"driver": {
|
|
136
|
+
"name": "TorusGuard",
|
|
137
|
+
"semanticVersion": tool_version,
|
|
138
|
+
"informationUri": "https://github.com/githubmofo/TorusGuard",
|
|
139
|
+
"rules": list(rules_map.values()),
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"automationDetails": {
|
|
143
|
+
"id": f"{analysis_category}/"
|
|
144
|
+
},
|
|
145
|
+
"results": results
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return sarif_payload
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def validate_github_sarif(cls, sarif_dict: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
|
154
|
+
"""
|
|
155
|
+
Validates that a SARIF payload meets GitHub Code Scanning upload constraints:
|
|
156
|
+
- $schema is present
|
|
157
|
+
- version is 2.1.0
|
|
158
|
+
- runs contains at least one driver with rules
|
|
159
|
+
- results have valid ruleIds, locations with artifactLocation.uri, and partialFingerprints
|
|
160
|
+
"""
|
|
161
|
+
errors = []
|
|
162
|
+
if sarif_dict.get("version") != "2.1.0":
|
|
163
|
+
errors.append("SARIF version must be 2.1.0")
|
|
164
|
+
if not sarif_dict.get("$schema"):
|
|
165
|
+
errors.append("SARIF missing $schema header")
|
|
166
|
+
|
|
167
|
+
runs = sarif_dict.get("runs", [])
|
|
168
|
+
if not runs:
|
|
169
|
+
errors.append("SARIF must contain at least one run")
|
|
170
|
+
return False, errors
|
|
171
|
+
|
|
172
|
+
driver = runs[0].get("tool", {}).get("driver", {})
|
|
173
|
+
if not driver.get("name"):
|
|
174
|
+
errors.append("Tool driver must specify name")
|
|
175
|
+
|
|
176
|
+
results = runs[0].get("results", [])
|
|
177
|
+
for idx, res in enumerate(results):
|
|
178
|
+
if not res.get("ruleId"):
|
|
179
|
+
errors.append(f"Result {idx} missing ruleId")
|
|
180
|
+
if not res.get("locations"):
|
|
181
|
+
errors.append(f"Result {idx} missing locations")
|
|
182
|
+
else:
|
|
183
|
+
uri = res["locations"][0].get("physicalLocation", {}).get("artifactLocation", {}).get("uri")
|
|
184
|
+
if not uri:
|
|
185
|
+
errors.append(f"Result {idx} missing artifactLocation uri")
|
|
186
|
+
if "partialFingerprints" not in res:
|
|
187
|
+
errors.append(f"Result {idx} missing partialFingerprints for GitHub deduplication")
|
|
188
|
+
|
|
189
|
+
return len(errors) == 0, errors
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v6.2 Modern Stack Profiler & Version Family Detector
|
|
3
|
+
Analyzes project manifests, code syntax, and dependency configurations to detect
|
|
4
|
+
framework version families (Django 5.x/4.x, FastAPI/Pydantic v2, SQLAlchemy 2.0, Next.js 14+),
|
|
5
|
+
async paradigms, container/workflow setups, and modern configuration patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field, asdict
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Any, Optional, Set
|
|
11
|
+
import re
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class StackProfile:
|
|
17
|
+
primary_language: str = "Python"
|
|
18
|
+
framework: str = "Unknown"
|
|
19
|
+
version_family: str = "Modern"
|
|
20
|
+
is_async: bool = False
|
|
21
|
+
orm_layer: str = "None"
|
|
22
|
+
orm_version_family: str = "2.0+"
|
|
23
|
+
config_loader: str = "Standard"
|
|
24
|
+
dependency_manager: str = "pip"
|
|
25
|
+
container_engine: Optional[str] = None
|
|
26
|
+
ci_platform: Optional[str] = None
|
|
27
|
+
frontend_framework: Optional[str] = None
|
|
28
|
+
detected_features: List[str] = field(default_factory=list)
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
31
|
+
return asdict(self)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StackProfiler:
|
|
35
|
+
"""
|
|
36
|
+
Inspects repository files to determine comprehensive modern stack profile.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def profile_repository(cls, repo_path: Path) -> StackProfile:
|
|
41
|
+
profile = StackProfile()
|
|
42
|
+
detected_features = []
|
|
43
|
+
|
|
44
|
+
if not repo_path.exists():
|
|
45
|
+
return profile
|
|
46
|
+
|
|
47
|
+
# Collect files
|
|
48
|
+
all_files: List[Path] = []
|
|
49
|
+
try:
|
|
50
|
+
for p in repo_path.rglob("*"):
|
|
51
|
+
if p.is_file() and not any(part.startswith(".") and part != ".github" for part in p.parts):
|
|
52
|
+
all_files.append(p)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
file_names = {f.name for f in all_files}
|
|
57
|
+
file_paths_str = [str(f.relative_to(repo_path)).replace("\\", "/") for f in all_files]
|
|
58
|
+
|
|
59
|
+
# 1. Dependency Manager & Ecosystem Detection
|
|
60
|
+
if "uv.lock" in file_names:
|
|
61
|
+
profile.dependency_manager = "uv"
|
|
62
|
+
detected_features.append("uv (Astral) fast package manager")
|
|
63
|
+
elif "poetry.lock" in file_names:
|
|
64
|
+
profile.dependency_manager = "poetry"
|
|
65
|
+
detected_features.append("Poetry packaging")
|
|
66
|
+
elif "Pipfile.lock" in file_names:
|
|
67
|
+
profile.dependency_manager = "pipenv"
|
|
68
|
+
elif "pnpm-lock.yaml" in file_names:
|
|
69
|
+
profile.dependency_manager = "pnpm"
|
|
70
|
+
elif "package-lock.json" in file_names:
|
|
71
|
+
profile.dependency_manager = "npm"
|
|
72
|
+
elif "yarn.lock" in file_names:
|
|
73
|
+
profile.dependency_manager = "yarn"
|
|
74
|
+
elif "go.mod" in file_names:
|
|
75
|
+
profile.primary_language = "Go"
|
|
76
|
+
profile.dependency_manager = "go modules"
|
|
77
|
+
detected_features.append("Go Modules package ecosystem")
|
|
78
|
+
elif "Cargo.toml" in file_names:
|
|
79
|
+
profile.primary_language = "Rust"
|
|
80
|
+
profile.dependency_manager = "cargo"
|
|
81
|
+
detected_features.append("Cargo package manager (Rust)")
|
|
82
|
+
elif "pom.xml" in file_names:
|
|
83
|
+
profile.primary_language = "Java"
|
|
84
|
+
profile.dependency_manager = "maven"
|
|
85
|
+
detected_features.append("Maven project layout")
|
|
86
|
+
elif "build.gradle" in file_names or "build.gradle.kts" in file_names:
|
|
87
|
+
profile.primary_language = "Kotlin" if "build.gradle.kts" in file_names else "Java"
|
|
88
|
+
profile.dependency_manager = "gradle"
|
|
89
|
+
detected_features.append("Gradle build tool")
|
|
90
|
+
elif any(f.endswith(".csproj") for f in file_names):
|
|
91
|
+
profile.primary_language = "C#"
|
|
92
|
+
profile.dependency_manager = "dotnet / nuget"
|
|
93
|
+
detected_features.append(".NET / C# project ecosystem")
|
|
94
|
+
elif "composer.json" in file_names:
|
|
95
|
+
profile.primary_language = "PHP"
|
|
96
|
+
profile.dependency_manager = "composer"
|
|
97
|
+
detected_features.append("Composer package manager (PHP)")
|
|
98
|
+
elif "Gemfile" in file_names:
|
|
99
|
+
profile.primary_language = "Ruby"
|
|
100
|
+
profile.dependency_manager = "bundler"
|
|
101
|
+
detected_features.append("Bundler package manager (Ruby)")
|
|
102
|
+
elif "pyproject.toml" in file_names:
|
|
103
|
+
profile.dependency_manager = "pyproject.toml (PEP 621)"
|
|
104
|
+
|
|
105
|
+
# 2. Container & CI Platform
|
|
106
|
+
if any("Dockerfile" in name or "Containerfile" in name for name in file_names):
|
|
107
|
+
profile.container_engine = "Docker / OCI Container"
|
|
108
|
+
detected_features.append("Containerized deployment (Dockerfile)")
|
|
109
|
+
if any(p.startswith(".github/workflows/") for p in file_paths_str):
|
|
110
|
+
profile.ci_platform = "GitHub Actions"
|
|
111
|
+
detected_features.append("GitHub Actions CI/CD workflows")
|
|
112
|
+
|
|
113
|
+
# 3. Source Code Inspection
|
|
114
|
+
py_files = [f for f in all_files if f.suffix in [".py", ".pyi"]]
|
|
115
|
+
ts_files = [f for f in all_files if f.suffix in [".ts", ".tsx", ".js", ".jsx"]]
|
|
116
|
+
|
|
117
|
+
py_content_sample = ""
|
|
118
|
+
for pf in py_files[:20]:
|
|
119
|
+
try:
|
|
120
|
+
py_content_sample += pf.read_text(encoding="utf-8", errors="ignore") + "\n"
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
ts_content_sample = ""
|
|
125
|
+
for tf in ts_files[:20]:
|
|
126
|
+
try:
|
|
127
|
+
ts_content_sample += tf.read_text(encoding="utf-8", errors="ignore") + "\n"
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# Check async paradigm
|
|
132
|
+
if "async def " in py_content_sample or "await " in py_content_sample:
|
|
133
|
+
profile.is_async = True
|
|
134
|
+
detected_features.append("Async / Await Coroutines")
|
|
135
|
+
|
|
136
|
+
# Framework & Version Family Detection
|
|
137
|
+
if "from fastapi" in py_content_sample or "import fastapi" in py_content_sample:
|
|
138
|
+
profile.framework = "FastAPI"
|
|
139
|
+
if "Annotated[" in py_content_sample or "model_validator" in py_content_sample or "pydantic_settings" in py_content_sample:
|
|
140
|
+
profile.version_family = "FastAPI 0.100+ (Pydantic v2)"
|
|
141
|
+
detected_features.append("Pydantic v2 Typed Validation & Annotated Dependencies")
|
|
142
|
+
else:
|
|
143
|
+
profile.version_family = "FastAPI 0.9x (Legacy)"
|
|
144
|
+
|
|
145
|
+
elif "django" in py_content_sample or "DJANGO_SETTINGS_MODULE" in py_content_sample:
|
|
146
|
+
profile.framework = "Django"
|
|
147
|
+
if "aget(" in py_content_sample or "asgiref" in py_content_sample or "async def " in py_content_sample:
|
|
148
|
+
profile.version_family = "Django 5.x (Async Native)"
|
|
149
|
+
detected_features.append("Django 5.x Async ORM & Views")
|
|
150
|
+
else:
|
|
151
|
+
profile.version_family = "Django 4.x / Standard"
|
|
152
|
+
|
|
153
|
+
elif "from flask" in py_content_sample or "import flask" in py_content_sample:
|
|
154
|
+
profile.framework = "Flask"
|
|
155
|
+
profile.version_family = "Flask 3.x"
|
|
156
|
+
|
|
157
|
+
# ORM Layer
|
|
158
|
+
if "sqlalchemy" in py_content_sample:
|
|
159
|
+
profile.orm_layer = "SQLAlchemy"
|
|
160
|
+
if "select(" in py_content_sample or "AsyncSession" in py_content_sample or "mapped_column" in py_content_sample:
|
|
161
|
+
profile.orm_version_family = "SQLAlchemy 2.0+ (Modern 2.0 Syntax)"
|
|
162
|
+
detected_features.append("SQLAlchemy 2.0 select() queries & async sessions")
|
|
163
|
+
else:
|
|
164
|
+
profile.orm_version_family = "SQLAlchemy 1.4 (Legacy Query API)"
|
|
165
|
+
elif profile.framework == "Django":
|
|
166
|
+
profile.orm_layer = "Django ORM"
|
|
167
|
+
profile.orm_version_family = "Django Model Manager"
|
|
168
|
+
|
|
169
|
+
# Frontend Framework
|
|
170
|
+
if '"next"' in ts_content_sample or "'next'" in ts_content_sample or "use server" in ts_content_sample:
|
|
171
|
+
profile.frontend_framework = "Next.js 14+ (App Router)"
|
|
172
|
+
detected_features.append("Next.js App Router & Server Actions")
|
|
173
|
+
elif "react" in ts_content_sample:
|
|
174
|
+
profile.frontend_framework = "React"
|
|
175
|
+
|
|
176
|
+
# Configuration Loading
|
|
177
|
+
if "pydantic_settings" in py_content_sample or "BaseSettings" in py_content_sample:
|
|
178
|
+
profile.config_loader = "pydantic-settings (Type-Safe Env)"
|
|
179
|
+
detected_features.append("pydantic-settings environment validation")
|
|
180
|
+
elif "os.getenv" in py_content_sample or "os.environ" in py_content_sample:
|
|
181
|
+
profile.config_loader = "os.environ"
|
|
182
|
+
|
|
183
|
+
profile.detected_features = detected_features
|
|
184
|
+
return profile
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Symbol Table Engine
|
|
3
|
+
Per-file symbol resolution for tracking variable definitions, scopes, types,
|
|
4
|
+
and import aliases across language scopes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Dict, List, Optional, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class VariableInfo:
|
|
13
|
+
name: str
|
|
14
|
+
defined_line: int
|
|
15
|
+
scope_name: str = "global"
|
|
16
|
+
assigned_expression: str = ""
|
|
17
|
+
is_tainted: bool = False
|
|
18
|
+
inferred_type: str = "unknown"
|
|
19
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SymbolTable:
|
|
23
|
+
"""Tracks variable definitions, scopes, and types within a single file."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, file_path: str = ""):
|
|
26
|
+
self.file_path = file_path
|
|
27
|
+
self.variables: Dict[str, List[VariableInfo]] = {}
|
|
28
|
+
self.import_aliases: Dict[str, str] = {}
|
|
29
|
+
self.function_defs: Dict[str, int] = {}
|
|
30
|
+
|
|
31
|
+
def add_variable(
|
|
32
|
+
self,
|
|
33
|
+
name: str,
|
|
34
|
+
line: int,
|
|
35
|
+
scope: str = "global",
|
|
36
|
+
expression: str = "",
|
|
37
|
+
is_tainted: bool = False,
|
|
38
|
+
inferred_type: str = "unknown"
|
|
39
|
+
) -> VariableInfo:
|
|
40
|
+
var_info = VariableInfo(
|
|
41
|
+
name=name,
|
|
42
|
+
defined_line=line,
|
|
43
|
+
scope_name=scope,
|
|
44
|
+
assigned_expression=expression,
|
|
45
|
+
is_tainted=is_tainted,
|
|
46
|
+
inferred_type=inferred_type
|
|
47
|
+
)
|
|
48
|
+
if name not in self.variables:
|
|
49
|
+
self.variables[name] = []
|
|
50
|
+
self.variables[name].append(var_info)
|
|
51
|
+
return var_info
|
|
52
|
+
|
|
53
|
+
def resolve_variable(self, name: str, at_line: int) -> Optional[VariableInfo]:
|
|
54
|
+
"""
|
|
55
|
+
Given a variable name and current line, returns its most recent definition
|
|
56
|
+
preceding or at that line.
|
|
57
|
+
"""
|
|
58
|
+
defs = self.variables.get(name, [])
|
|
59
|
+
candidate = None
|
|
60
|
+
for d in defs:
|
|
61
|
+
if d.defined_line <= at_line:
|
|
62
|
+
if candidate is None or d.defined_line > candidate.defined_line:
|
|
63
|
+
candidate = d
|
|
64
|
+
return candidate
|
|
65
|
+
|
|
66
|
+
def add_import_alias(self, alias: str, canonical_name: str) -> None:
|
|
67
|
+
"""Register an import alias (e.g. `from flask import request as req`)."""
|
|
68
|
+
self.import_aliases[alias] = canonical_name
|
|
69
|
+
|
|
70
|
+
def get_import_alias(self, alias: str) -> Optional[str]:
|
|
71
|
+
"""Resolve an alias to its canonical symbol or module."""
|
|
72
|
+
return self.import_aliases.get(alias)
|
|
73
|
+
|
|
74
|
+
def mark_tainted(self, name: str, at_line: int) -> None:
|
|
75
|
+
var_info = self.resolve_variable(name, at_line)
|
|
76
|
+
if var_info:
|
|
77
|
+
var_info.is_tainted = True
|
|
78
|
+
else:
|
|
79
|
+
self.add_variable(name=name, line=at_line, is_tainted=True)
|
|
80
|
+
|
|
81
|
+
def is_tainted(self, name: str, at_line: int) -> bool:
|
|
82
|
+
var_info = self.resolve_variable(name, at_line)
|
|
83
|
+
return var_info.is_tainted if var_info else False
|
|
84
|
+
|
|
85
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
86
|
+
return {
|
|
87
|
+
"file_path": self.file_path,
|
|
88
|
+
"variables": {k: [v.defined_line for v in vs] for k, vs in self.variables.items()},
|
|
89
|
+
"import_aliases": self.import_aliases,
|
|
90
|
+
"functions": self.function_defs
|
|
91
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Taint Engine — Core Taint Models & Propagation Dataclasses
|
|
3
|
+
Defines source, sink, sanitizer entities and dataflow propagation primitives.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field, asdict
|
|
7
|
+
from typing import List, Dict, Any, Optional, Set
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class TaintSource:
|
|
12
|
+
pattern: str
|
|
13
|
+
framework: str
|
|
14
|
+
label: str
|
|
15
|
+
category: str
|
|
16
|
+
language: str
|
|
17
|
+
line_number: int = 0
|
|
18
|
+
file_path: str = ""
|
|
19
|
+
variable_name: str = ""
|
|
20
|
+
|
|
21
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
22
|
+
return asdict(self)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class TaintSink:
|
|
27
|
+
pattern: str
|
|
28
|
+
category: str
|
|
29
|
+
rule_id: str
|
|
30
|
+
severity: str
|
|
31
|
+
language: str
|
|
32
|
+
line_number: int = 0
|
|
33
|
+
file_path: str = ""
|
|
34
|
+
argument_name: str = ""
|
|
35
|
+
|
|
36
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
37
|
+
return asdict(self)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class TaintSanitizer:
|
|
42
|
+
pattern: str
|
|
43
|
+
cleans: List[str]
|
|
44
|
+
language: str
|
|
45
|
+
line_number: int = 0
|
|
46
|
+
file_path: str = ""
|
|
47
|
+
|
|
48
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
49
|
+
return asdict(self)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class TaintNode:
|
|
54
|
+
variable_name: str
|
|
55
|
+
file_path: str
|
|
56
|
+
line_number: int
|
|
57
|
+
node_type: str # "source" | "transform" | "sink" | "sanitizer"
|
|
58
|
+
expression: str = ""
|
|
59
|
+
pattern: str = ""
|
|
60
|
+
category: str = ""
|
|
61
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
def node_id(self) -> str:
|
|
64
|
+
return f"{self.file_path}:{self.line_number}:{self.variable_name}:{self.node_type}"
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
67
|
+
return asdict(self)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class TaintEdge:
|
|
72
|
+
from_node: TaintNode
|
|
73
|
+
to_node: TaintNode
|
|
74
|
+
edge_type: str # "assignment" | "argument" | "return" | "attribute" | "call"
|
|
75
|
+
line_number: int = 0
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
78
|
+
return {
|
|
79
|
+
"from_node": self.from_node.node_id(),
|
|
80
|
+
"to_node": self.to_node.node_id(),
|
|
81
|
+
"edge_type": self.edge_type,
|
|
82
|
+
"line_number": self.line_number,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class TaintPath:
|
|
88
|
+
source: TaintNode
|
|
89
|
+
sink: TaintNode
|
|
90
|
+
path: List[TaintNode]
|
|
91
|
+
is_sanitized: bool = False
|
|
92
|
+
sanitizers_found: List[TaintNode] = field(default_factory=list)
|
|
93
|
+
confidence_penalty: int = 0
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def depth(self) -> int:
|
|
97
|
+
return max(0, len(self.path) - 1)
|
|
98
|
+
|
|
99
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
100
|
+
return {
|
|
101
|
+
"source": self.source.to_dict(),
|
|
102
|
+
"sink": self.sink.to_dict(),
|
|
103
|
+
"depth": self.depth,
|
|
104
|
+
"is_sanitized": self.is_sanitized,
|
|
105
|
+
"sanitizers_found": [s.to_dict() for s in self.sanitizers_found],
|
|
106
|
+
"path_nodes": [n.to_dict() for n in self.path],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TaintPropagator:
|
|
111
|
+
"""
|
|
112
|
+
Evaluates statement-level and expression-level taint transfer.
|
|
113
|
+
Determines if assignment, function call, or operation propagates taint
|
|
114
|
+
from RHS variables to LHS target.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def propagates_taint(source_vars: Set[str], expression: str) -> bool:
|
|
119
|
+
"""Checks if any currently tainted variable appears in the expression."""
|
|
120
|
+
if not source_vars or not expression:
|
|
121
|
+
return False
|
|
122
|
+
import re
|
|
123
|
+
tokens = set(re.findall(r"\b[A-Za-z_][A-Za-z0-9_]*\b", expression))
|
|
124
|
+
return bool(tokens.intersection(source_vars))
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def is_sanitizer_expression(expression: str, sanitizers: List[Dict[str, Any]], category: str) -> Optional[Dict[str, Any]]:
|
|
128
|
+
"""Returns the matching sanitizer if the expression applies a known sanitizer for the category."""
|
|
129
|
+
for s in sanitizers:
|
|
130
|
+
if s["pattern"] in expression:
|
|
131
|
+
if not category or category in s.get("cleans", []):
|
|
132
|
+
return s
|
|
133
|
+
return None
|