torusguard 2.0.0-alpha → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.torusguard/.manifest.json +94 -30
- 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/rules/container/TG-CONT-001-root-user-execution.md +50 -0
- package/.torusguard/rules/container/TG-CONT-002-docker-socket-mount.md +47 -0
- package/.torusguard/rules/container/TG-CONT-003-privileged-container-mode.md +53 -0
- package/.torusguard/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -0
- package/.torusguard/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -0
- package/.torusguard/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -0
- package/.torusguard/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -0
- package/.torusguard/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -0
- package/.torusguard/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -0
- package/.torusguard/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -0
- package/.torusguard/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -0
- package/.torusguard/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -0
- package/.torusguard/rules_catalog.json +96 -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/finding_scorer.py +43 -13
- package/.torusguard/scripts/manifest_builder.py +1 -1
- package/.torusguard/scripts/skill_profiler.py +26 -0
- package/.torusguard/skills/torusguard/SKILL.md +74 -25
- package/.torusguard/skills/torusguard/bootstrap.py +57 -24
- package/.torusguard/skills/torusguard-ai-guard/SKILL.md +95 -0
- package/.torusguard/skills/torusguard-apply/SKILL.md +60 -34
- package/.torusguard/skills/torusguard-audit/SKILL.md +131 -57
- package/.torusguard/skills/torusguard-authorize/SKILL.md +48 -6
- package/.torusguard/skills/torusguard-container/SKILL.md +94 -0
- package/.torusguard/skills/torusguard-exploit-check/SKILL.md +50 -6
- package/.torusguard/skills/torusguard-full/SKILL.md +62 -19
- package/.torusguard/skills/torusguard-git-mine/SKILL.md +92 -0
- package/.torusguard/skills/torusguard-harden/SKILL.md +81 -50
- package/.torusguard/skills/torusguard-init/SKILL.md +61 -14
- package/.torusguard/skills/torusguard-ocr-scan/SKILL.md +94 -0
- package/.torusguard/skills/torusguard-recheck/SKILL.md +71 -18
- package/.torusguard/skills/torusguard-redos/SKILL.md +91 -0
- package/.torusguard/skills/torusguard-report/SKILL.md +50 -9
- package/.torusguard/skills/torusguard-status/SKILL.md +63 -10
- package/.torusguard/skills/torusguard-verify/SKILL.md +52 -10
- package/.torusguard/skills/torusguard-web-validate/SKILL.md +53 -8
- package/.torusguard/workflows/ai-guard.md +31 -0
- package/.torusguard/workflows/apply.md +32 -55
- package/.torusguard/workflows/audit.md +35 -49
- package/.torusguard/workflows/authorize.md +27 -50
- package/.torusguard/workflows/container.md +29 -0
- package/.torusguard/workflows/exploit-check.md +28 -50
- package/.torusguard/workflows/git-mine.md +25 -0
- package/.torusguard/workflows/harden.md +29 -48
- package/.torusguard/workflows/init.md +27 -50
- package/.torusguard/workflows/memory.md +18 -23
- package/.torusguard/workflows/ocr-scan.md +25 -0
- package/.torusguard/workflows/recheck.md +28 -46
- package/.torusguard/workflows/redos.md +27 -0
- package/.torusguard/workflows/report.md +33 -52
- package/.torusguard/workflows/status.md +31 -52
- package/.torusguard/workflows/verify.md +29 -49
- package/.torusguard/workflows/web-validate.md +22 -45
- package/README.md +96 -60
- package/package.json +7 -2
- package/skills/torusguard/SKILL.md +75 -24
- package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/skills/torusguard/bootstrap.py +60 -71
- package/skills/torusguard/payload/.manifest.json +94 -31
- 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 -0
- package/skills/torusguard/payload/rules/container/TG-CONT-002-docker-socket-mount.md +47 -0
- package/skills/torusguard/payload/rules/container/TG-CONT-003-privileged-container-mode.md +53 -0
- package/skills/torusguard/payload/rules/container/TG-CONT-004-build-arg-secret-exposure.md +43 -0
- package/skills/torusguard/payload/rules/git/TG-GIT-001-historical-secret-in-git-commit.md +44 -0
- package/skills/torusguard/payload/rules/git/TG-GIT-002-plaintext-credentials-in-git-config.md +41 -0
- package/skills/torusguard/payload/rules/git/TG-GIT-003-sensitive-tracked-file-gitignore-breach.md +40 -0
- package/skills/torusguard/payload/rules/rag/TG-RAG-001-untrusted-rag-context-injection.md +72 -0
- package/skills/torusguard/payload/rules/rag/TG-RAG-002-autonomous-llm-tool-unsandboxed-call.md +51 -0
- package/skills/torusguard/payload/rules/rag/TG-RAG-003-unpartitioned-vector-tenant-lookup.md +51 -0
- package/skills/torusguard/payload/rules/redos/TG-REDOS-001-catastrophic-exponential-backtracking.md +46 -0
- package/skills/torusguard/payload/rules/redos/TG-REDOS-002-unbounded-nested-quantifier.md +43 -0
- package/skills/torusguard/payload/rules_catalog.json +338 -518
- package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/audit_runner.py +108 -10
- package/skills/torusguard/payload/scripts/finding_scorer.py +43 -13
- package/skills/torusguard/payload/scripts/manifest_builder.py +1 -1
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +74 -25
- package/skills/torusguard/payload/skills/torusguard/bootstrap.py +57 -24
- package/skills/torusguard/payload/skills/torusguard/references/csharp-security.md +41 -41
- package/skills/torusguard/payload/skills/torusguard/references/go-security.md +41 -41
- package/skills/torusguard/payload/skills/torusguard/references/java-security.md +40 -40
- package/skills/torusguard/payload/skills/torusguard/references/polyglot-security-matrix.md +25 -25
- package/skills/torusguard/payload/skills/torusguard/references/rust-security.md +40 -40
- package/skills/torusguard/payload/skills/torusguard-ai-guard/SKILL.md +95 -0
- package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +60 -34
- package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +131 -57
- package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +48 -6
- package/skills/torusguard/payload/skills/torusguard-container/SKILL.md +94 -0
- package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +50 -6
- package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +62 -19
- package/skills/torusguard/payload/skills/torusguard-git-mine/SKILL.md +92 -0
- package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +81 -50
- package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +61 -14
- package/skills/torusguard/payload/skills/torusguard-ocr-scan/SKILL.md +94 -0
- package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +71 -18
- package/skills/torusguard/payload/skills/torusguard-redos/SKILL.md +91 -0
- package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +50 -9
- package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +63 -10
- package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +52 -10
- package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +53 -8
- package/skills/torusguard/payload/workflows/ai-guard.md +31 -0
- package/skills/torusguard/payload/workflows/apply.md +31 -62
- package/skills/torusguard/payload/workflows/audit.md +35 -55
- package/skills/torusguard/payload/workflows/authorize.md +27 -50
- package/skills/torusguard/payload/workflows/container.md +29 -0
- package/skills/torusguard/payload/workflows/exploit-check.md +28 -50
- package/skills/torusguard/payload/workflows/git-mine.md +25 -0
- package/skills/torusguard/payload/workflows/harden.md +28 -52
- package/skills/torusguard/payload/workflows/init.md +27 -56
- package/skills/torusguard/payload/workflows/memory.md +18 -23
- package/skills/torusguard/payload/workflows/ocr-scan.md +25 -0
- package/skills/torusguard/payload/workflows/recheck.md +28 -46
- package/skills/torusguard/payload/workflows/redos.md +27 -0
- package/skills/torusguard/payload/workflows/report.md +39 -62
- package/skills/torusguard/payload/workflows/status.md +31 -55
- package/skills/torusguard/payload/workflows/torusguard-audit.md +35 -55
- package/skills/torusguard/payload/workflows/verify.md +29 -49
- package/skills/torusguard/payload/workflows/web-validate.md +22 -45
- package/skills/torusguard/references/csharp-security.md +41 -0
- package/skills/torusguard/references/go-security.md +41 -0
- package/skills/torusguard/references/java-security.md +40 -0
- package/skills/torusguard/references/polyglot-security-matrix.md +25 -0
- package/skills/torusguard/references/rust-security.md +40 -0
- package/skills/torusguard-ai-guard/SKILL.md +95 -0
- package/skills/torusguard-apply/SKILL.md +60 -34
- package/skills/torusguard-audit/SKILL.md +130 -57
- package/skills/torusguard-authorize/SKILL.md +48 -6
- package/skills/torusguard-container/SKILL.md +94 -0
- package/skills/torusguard-exploit-check/SKILL.md +50 -6
- package/skills/torusguard-full/SKILL.md +62 -19
- package/skills/torusguard-git-mine/SKILL.md +92 -0
- package/skills/torusguard-harden/SKILL.md +81 -50
- package/skills/torusguard-init/SKILL.md +61 -14
- package/skills/torusguard-ocr-scan/SKILL.md +94 -0
- package/skills/torusguard-recheck/SKILL.md +71 -18
- package/skills/torusguard-redos/SKILL.md +91 -0
- package/skills/torusguard-report/SKILL.md +50 -9
- package/skills/torusguard-status/SKILL.md +63 -10
- package/skills/torusguard-verify/SKILL.md +52 -10
- package/skills/torusguard-web-validate/SKILL.md +53 -8
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Multi-Signal Evidence-Chain Confidence Calibration Engine
|
|
3
|
+
Calibrates finding confidence (0-100) using 7 weighted empirical signals:
|
|
4
|
+
rule severity, taint path confirmation, taint depth, sanitizer absence,
|
|
5
|
+
framework context match, evidence snippet quality, and test fixture suppression.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field, asdict
|
|
9
|
+
from typing import Dict, Any, Tuple, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
SEVERITY_BASE_SCORES = {
|
|
13
|
+
"Critical": 90,
|
|
14
|
+
"High": 75,
|
|
15
|
+
"Medium": 50,
|
|
16
|
+
"Low": 25,
|
|
17
|
+
"Informational": 10
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class EvidenceSignals:
|
|
23
|
+
rule_severity: str = "High"
|
|
24
|
+
taint_path_confirmed: bool = False
|
|
25
|
+
taint_depth: Optional[int] = None
|
|
26
|
+
sanitizer_present: bool = False
|
|
27
|
+
framework_context_match: bool = True
|
|
28
|
+
has_multiline_evidence: bool = True
|
|
29
|
+
is_test_or_mock: bool = False
|
|
30
|
+
memory_boost: int = 0
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
33
|
+
return asdict(self)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConfidenceCalibrator:
|
|
37
|
+
"""Computes evidence-chain calibrated confidence scores."""
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def calculate_score(signals: EvidenceSignals) -> Tuple[int, str, Dict[str, Any]]:
|
|
41
|
+
# 1. rule_severity_base (weight 0.20)
|
|
42
|
+
sev_base = SEVERITY_BASE_SCORES.get(signals.rule_severity, 50)
|
|
43
|
+
w_sev = 0.20 * sev_base
|
|
44
|
+
|
|
45
|
+
# 2. taint_path_confirmed (weight 0.25)
|
|
46
|
+
taint_score = 100 if signals.taint_path_confirmed else 0
|
|
47
|
+
w_taint = 0.25 * taint_score
|
|
48
|
+
|
|
49
|
+
# 3. taint_depth (weight 0.10)
|
|
50
|
+
# direct=100, 1-hop=80, 2-hop=60, 3+=40; if no taint path, 0
|
|
51
|
+
if signals.taint_path_confirmed:
|
|
52
|
+
d = signals.taint_depth if signals.taint_depth is not None else 0
|
|
53
|
+
if d == 0:
|
|
54
|
+
depth_score = 100
|
|
55
|
+
elif d == 1:
|
|
56
|
+
depth_score = 80
|
|
57
|
+
elif d == 2:
|
|
58
|
+
depth_score = 60
|
|
59
|
+
else:
|
|
60
|
+
depth_score = 40
|
|
61
|
+
else:
|
|
62
|
+
depth_score = 0
|
|
63
|
+
w_depth = 0.10 * depth_score
|
|
64
|
+
|
|
65
|
+
# 4. sanitizer_absence (weight 0.15)
|
|
66
|
+
san_score = 0 if signals.sanitizer_present else 100
|
|
67
|
+
w_san = 0.15 * san_score
|
|
68
|
+
|
|
69
|
+
# 5. framework_context_match (weight 0.10)
|
|
70
|
+
ctx_score = 100 if signals.framework_context_match else 50
|
|
71
|
+
w_ctx = 0.10 * ctx_score
|
|
72
|
+
|
|
73
|
+
# 6. evidence_snippet_quality (weight 0.10)
|
|
74
|
+
snippet_score = 100 if signals.has_multiline_evidence else 50
|
|
75
|
+
w_snippet = 0.10 * snippet_score
|
|
76
|
+
|
|
77
|
+
# 7. test fixture penalty
|
|
78
|
+
test_penalty = -50 if signals.is_test_or_mock else 0
|
|
79
|
+
|
|
80
|
+
# Memory boost (-30 to +20)
|
|
81
|
+
mem_boost = signals.memory_boost
|
|
82
|
+
|
|
83
|
+
raw_total = (
|
|
84
|
+
w_sev +
|
|
85
|
+
w_taint +
|
|
86
|
+
w_depth +
|
|
87
|
+
w_san +
|
|
88
|
+
w_ctx +
|
|
89
|
+
w_snippet +
|
|
90
|
+
test_penalty +
|
|
91
|
+
mem_boost
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
final_score = int(round(min(max(raw_total, 0), 100)))
|
|
95
|
+
|
|
96
|
+
# Assign band
|
|
97
|
+
if final_score >= 90:
|
|
98
|
+
band = "Confirmed"
|
|
99
|
+
elif final_score >= 70:
|
|
100
|
+
band = "High Confidence"
|
|
101
|
+
elif final_score >= 50:
|
|
102
|
+
band = "Medium Confidence"
|
|
103
|
+
else:
|
|
104
|
+
band = "Needs Review"
|
|
105
|
+
|
|
106
|
+
breakdown = {
|
|
107
|
+
"rule_severity_score": sev_base,
|
|
108
|
+
"taint_path_confirmed": signals.taint_path_confirmed,
|
|
109
|
+
"taint_depth": signals.taint_depth,
|
|
110
|
+
"sanitizer_present": signals.sanitizer_present,
|
|
111
|
+
"framework_context_match": signals.framework_context_match,
|
|
112
|
+
"has_multiline_evidence": signals.has_multiline_evidence,
|
|
113
|
+
"is_test_or_mock": signals.is_test_or_mock,
|
|
114
|
+
"test_penalty": test_penalty,
|
|
115
|
+
"memory_boost": mem_boost,
|
|
116
|
+
"total_score": final_score,
|
|
117
|
+
"classification_band": band
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return final_score, band, breakdown
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Cross-File Interprocedural Taint Analyzer
|
|
3
|
+
Propagates taint across module and function boundaries using project call graphs.
|
|
4
|
+
Enforces strict 5-hop depth bound to prevent recursion and path explosion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import List, Dict, Set, Optional, Tuple, Any
|
|
10
|
+
|
|
11
|
+
from core.taint import TaintNode, TaintPath
|
|
12
|
+
from core.taint_graph import TaintGraph
|
|
13
|
+
from core.parser import PolyglotParser, ParseResult
|
|
14
|
+
from core.call_graph import CallGraph, CallSite, FunctionDef
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CrossFileTaintAnalyzer:
|
|
18
|
+
"""Interprocedural taint analysis across module boundaries."""
|
|
19
|
+
|
|
20
|
+
MAX_INTERPROCEDURAL_DEPTH = 5
|
|
21
|
+
|
|
22
|
+
def __init__(self, project_root: Path):
|
|
23
|
+
self.project_root = project_root.resolve()
|
|
24
|
+
self.parser = PolyglotParser()
|
|
25
|
+
self.call_graph = CallGraph(self.project_root)
|
|
26
|
+
|
|
27
|
+
def analyze_project(self, file_paths: List[Path]) -> List[TaintPath]:
|
|
28
|
+
"""
|
|
29
|
+
Builds call graph and walks intra-file and cross-file dataflow chains.
|
|
30
|
+
"""
|
|
31
|
+
all_paths: List[TaintPath] = []
|
|
32
|
+
file_graphs: Dict[str, TaintGraph] = {}
|
|
33
|
+
file_contents: Dict[str, str] = {}
|
|
34
|
+
file_languages: Dict[str, str] = {}
|
|
35
|
+
|
|
36
|
+
# 1. Build CallGraph across target files
|
|
37
|
+
self.call_graph.build_from_files(file_paths, self.parser)
|
|
38
|
+
|
|
39
|
+
# 2. Intra-file analysis on each file
|
|
40
|
+
for p in file_paths:
|
|
41
|
+
try:
|
|
42
|
+
rel_p = str(p.resolve().relative_to(self.project_root)).replace("\\", "/")
|
|
43
|
+
content = p.read_text(encoding="utf-8", errors="replace")
|
|
44
|
+
lang = self.parser.detect_language(p)
|
|
45
|
+
file_contents[rel_p] = content
|
|
46
|
+
file_languages[rel_p] = lang
|
|
47
|
+
|
|
48
|
+
tg = TaintGraph()
|
|
49
|
+
paths = tg.analyze_file_code(rel_p, content, lang)
|
|
50
|
+
file_graphs[rel_p] = tg
|
|
51
|
+
all_paths.extend(paths)
|
|
52
|
+
except Exception:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# 3. Interprocedural Propagation:
|
|
56
|
+
# Check if function returns tainted value, propagate to call site LHS
|
|
57
|
+
# Check if call passes tainted argument, propagate to callee parameters
|
|
58
|
+
for (caller_key, call_sites) in self.call_graph.callees.items():
|
|
59
|
+
caller_file, caller_func = caller_key
|
|
60
|
+
tg_caller = file_graphs.get(caller_file)
|
|
61
|
+
if not tg_caller:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
for cs in call_sites:
|
|
65
|
+
if not cs.callee_file or cs.callee_file not in file_graphs:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
tg_callee = file_graphs[cs.callee_file]
|
|
69
|
+
|
|
70
|
+
# Check argument propagation (caller argument -> callee parameter)
|
|
71
|
+
for arg_expr in cs.arguments:
|
|
72
|
+
# Is this argument connected to a tainted source in caller?
|
|
73
|
+
for src_id, src_node in tg_caller.nodes.items():
|
|
74
|
+
if src_node.node_type == "source":
|
|
75
|
+
# Check if src reaches the argument expression
|
|
76
|
+
arg_node = TaintNode(
|
|
77
|
+
variable_name=arg_expr,
|
|
78
|
+
file_path=caller_file,
|
|
79
|
+
line_number=cs.caller_line,
|
|
80
|
+
node_type="transform",
|
|
81
|
+
expression=arg_expr
|
|
82
|
+
)
|
|
83
|
+
# Look for sinks in the callee file
|
|
84
|
+
for sink_id, sink_node in tg_callee.nodes.items():
|
|
85
|
+
if sink_node.node_type == "sink":
|
|
86
|
+
# Create interprocedural path
|
|
87
|
+
cross_path = [src_node, arg_node, sink_node]
|
|
88
|
+
is_sanitized, sanitizers = tg_callee.is_path_sanitized(
|
|
89
|
+
cross_path,
|
|
90
|
+
sink_node.metadata.get("category", ""),
|
|
91
|
+
file_languages.get(cs.callee_file, "python")
|
|
92
|
+
)
|
|
93
|
+
all_paths.append(TaintPath(
|
|
94
|
+
source=src_node,
|
|
95
|
+
sink=sink_node,
|
|
96
|
+
path=cross_path,
|
|
97
|
+
is_sanitized=is_sanitized,
|
|
98
|
+
sanitizers_found=sanitizers
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
return all_paths
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v0.7.0 Bounded Exploitability Confirmation Engine
|
|
3
|
+
Confirms practical reachability and exploitability for approved issue classes
|
|
4
|
+
using safe, non-destructive, single-step verification probes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any, List, Optional
|
|
10
|
+
from dataclasses import dataclass, asdict
|
|
11
|
+
|
|
12
|
+
from core.runtime_validator import WebValidator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExploitabilityStatus(str, Enum):
|
|
16
|
+
RUNTIME_CONFIRMED = "Runtime Confirmed"
|
|
17
|
+
RUNTIME_LIKELY = "Runtime Likely"
|
|
18
|
+
NEEDS_MANUAL_REVIEW = "Needs Manual Review"
|
|
19
|
+
NOT_REPRODUCIBLE_IN_SCOPE = "Not Reproducible in Scope"
|
|
20
|
+
BLOCKED_BY_CONTROLS = "Blocked by Environment / Controls"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ExploitCheckResult:
|
|
25
|
+
finding_id: str
|
|
26
|
+
issue_class: str
|
|
27
|
+
status: str
|
|
28
|
+
confidence_score: int
|
|
29
|
+
probe_url: str
|
|
30
|
+
http_status_observed: int
|
|
31
|
+
proof_summary: str
|
|
32
|
+
reproducible: bool
|
|
33
|
+
remediation_advice: str
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
return asdict(self)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ExploitChecker:
|
|
40
|
+
"""
|
|
41
|
+
Executes bounded, safe probes to confirm practical exploitability of static findings.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def check_auth_bypass(
|
|
46
|
+
cls,
|
|
47
|
+
validator: WebValidator,
|
|
48
|
+
finding_id: str,
|
|
49
|
+
cluster_id: str,
|
|
50
|
+
endpoint_url: str,
|
|
51
|
+
expected_sensitive_marker: Optional[str] = None,
|
|
52
|
+
pre_approved: bool = True
|
|
53
|
+
) -> ExploitCheckResult:
|
|
54
|
+
"""
|
|
55
|
+
Validates whether an endpoint flagged for missing authentication
|
|
56
|
+
returns sensitive data to unauthenticated requests.
|
|
57
|
+
"""
|
|
58
|
+
status, headers, body, decision = validator.execute_probe(
|
|
59
|
+
finding_id=finding_id,
|
|
60
|
+
cluster_id=cluster_id,
|
|
61
|
+
method="GET",
|
|
62
|
+
target_url=endpoint_url,
|
|
63
|
+
headers={}, # Explicitly unauthenticated
|
|
64
|
+
pre_approved=pre_approved,
|
|
65
|
+
route_context="auth_bypass_probe"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if not decision.allowed_to_proceed:
|
|
69
|
+
return ExploitCheckResult(
|
|
70
|
+
finding_id=finding_id,
|
|
71
|
+
issue_class="auth_bypass",
|
|
72
|
+
status=ExploitabilityStatus.BLOCKED_BY_CONTROLS.value,
|
|
73
|
+
confidence_score=50,
|
|
74
|
+
probe_url=endpoint_url,
|
|
75
|
+
http_status_observed=status,
|
|
76
|
+
proof_summary="Probe blocked by TorusGuard Safety Gate.",
|
|
77
|
+
reproducible=False,
|
|
78
|
+
remediation_advice="Review endpoint permissions manually."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if status in [401, 403, 302]:
|
|
82
|
+
# Endpoint has active authentication barrier
|
|
83
|
+
return ExploitCheckResult(
|
|
84
|
+
finding_id=finding_id,
|
|
85
|
+
issue_class="auth_bypass",
|
|
86
|
+
status=ExploitabilityStatus.NOT_REPRODUCIBLE_IN_SCOPE.value,
|
|
87
|
+
confidence_score=20,
|
|
88
|
+
probe_url=endpoint_url,
|
|
89
|
+
http_status_observed=status,
|
|
90
|
+
proof_summary=f"Endpoint returned HTTP {status}, enforcing auth barrier at runtime.",
|
|
91
|
+
reproducible=True,
|
|
92
|
+
remediation_advice="Static finding may be protected by middleware or gateway barrier."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if status == 200:
|
|
96
|
+
if expected_sensitive_marker and expected_sensitive_marker not in body:
|
|
97
|
+
return ExploitCheckResult(
|
|
98
|
+
finding_id=finding_id,
|
|
99
|
+
issue_class="auth_bypass",
|
|
100
|
+
status=ExploitabilityStatus.RUNTIME_LIKELY.value,
|
|
101
|
+
confidence_score=75,
|
|
102
|
+
probe_url=endpoint_url,
|
|
103
|
+
http_status_observed=status,
|
|
104
|
+
proof_summary="Endpoint returned HTTP 200 without auth, but expected payload marker was absent.",
|
|
105
|
+
reproducible=True,
|
|
106
|
+
remediation_advice="Enforce login_required or authentication dependency."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Confirmed unauthenticated access
|
|
110
|
+
return ExploitCheckResult(
|
|
111
|
+
finding_id=finding_id,
|
|
112
|
+
issue_class="auth_bypass",
|
|
113
|
+
status=ExploitabilityStatus.RUNTIME_CONFIRMED.value,
|
|
114
|
+
confidence_score=98,
|
|
115
|
+
probe_url=endpoint_url,
|
|
116
|
+
http_status_observed=status,
|
|
117
|
+
proof_summary=f"Unauthenticated GET to {endpoint_url} returned HTTP 200 with sensitive payload.",
|
|
118
|
+
reproducible=True,
|
|
119
|
+
remediation_advice="Immediate remediation required: Add authentication barrier (e.g. Depends(get_current_user))."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return ExploitCheckResult(
|
|
123
|
+
finding_id=finding_id,
|
|
124
|
+
issue_class="auth_bypass",
|
|
125
|
+
status=ExploitabilityStatus.NEEDS_MANUAL_REVIEW.value,
|
|
126
|
+
confidence_score=60,
|
|
127
|
+
probe_url=endpoint_url,
|
|
128
|
+
http_status_observed=status,
|
|
129
|
+
proof_summary=f"Ambiguous response HTTP {status} received.",
|
|
130
|
+
reproducible=True,
|
|
131
|
+
remediation_advice="Inspect route handler and server error logs."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def check_tenant_isolation(
|
|
136
|
+
cls,
|
|
137
|
+
validator: WebValidator,
|
|
138
|
+
finding_id: str,
|
|
139
|
+
cluster_id: str,
|
|
140
|
+
tenant_a_resource_url: str,
|
|
141
|
+
tenant_b_auth_headers: Dict[str, str],
|
|
142
|
+
tenant_a_data_marker: str,
|
|
143
|
+
pre_approved: bool = True
|
|
144
|
+
) -> ExploitCheckResult:
|
|
145
|
+
"""
|
|
146
|
+
Validates IDOR / Multi-tenant isolation: accesses Tenant A's resource
|
|
147
|
+
using Tenant B's credentials.
|
|
148
|
+
"""
|
|
149
|
+
status, headers, body, decision = validator.execute_probe(
|
|
150
|
+
finding_id=finding_id,
|
|
151
|
+
cluster_id=cluster_id,
|
|
152
|
+
method="GET",
|
|
153
|
+
target_url=tenant_a_resource_url,
|
|
154
|
+
headers=tenant_b_auth_headers,
|
|
155
|
+
pre_approved=pre_approved,
|
|
156
|
+
route_context="tenant_isolation_probe"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if not decision.allowed_to_proceed:
|
|
160
|
+
return ExploitCheckResult(
|
|
161
|
+
finding_id=finding_id,
|
|
162
|
+
issue_class="tenant_isolation",
|
|
163
|
+
status=ExploitabilityStatus.BLOCKED_BY_CONTROLS.value,
|
|
164
|
+
confidence_score=50,
|
|
165
|
+
probe_url=tenant_a_resource_url,
|
|
166
|
+
http_status_observed=status,
|
|
167
|
+
proof_summary="Probe blocked by Safety Gate.",
|
|
168
|
+
reproducible=False,
|
|
169
|
+
remediation_advice="Review tenant boundary manually."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if status in [403, 404]:
|
|
173
|
+
return ExploitCheckResult(
|
|
174
|
+
finding_id=finding_id,
|
|
175
|
+
issue_class="tenant_isolation",
|
|
176
|
+
status=ExploitabilityStatus.NOT_REPRODUCIBLE_IN_SCOPE.value,
|
|
177
|
+
confidence_score=15,
|
|
178
|
+
probe_url=tenant_a_resource_url,
|
|
179
|
+
http_status_observed=status,
|
|
180
|
+
proof_summary=f"Tenant B credentials received HTTP {status} accessing Tenant A resource. Tenant scoping holds.",
|
|
181
|
+
reproducible=True,
|
|
182
|
+
remediation_advice="Static rule may have flagged query without detecting surrounding tenant middleware."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if status == 200 and tenant_a_data_marker in body:
|
|
186
|
+
return ExploitCheckResult(
|
|
187
|
+
finding_id=finding_id,
|
|
188
|
+
issue_class="tenant_isolation",
|
|
189
|
+
status=ExploitabilityStatus.RUNTIME_CONFIRMED.value,
|
|
190
|
+
confidence_score=99,
|
|
191
|
+
probe_url=tenant_a_resource_url,
|
|
192
|
+
http_status_observed=status,
|
|
193
|
+
proof_summary="Tenant B credentials successfully retrieved Tenant A's private data (Cross-Tenant IDOR confirmed).",
|
|
194
|
+
reproducible=True,
|
|
195
|
+
remediation_advice="Add tenant_id filter predicate to database query."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return ExploitCheckResult(
|
|
199
|
+
finding_id=finding_id,
|
|
200
|
+
issue_class="tenant_isolation",
|
|
201
|
+
status=ExploitabilityStatus.NEEDS_MANUAL_REVIEW.value,
|
|
202
|
+
confidence_score=60,
|
|
203
|
+
probe_url=tenant_a_resource_url,
|
|
204
|
+
http_status_observed=status,
|
|
205
|
+
proof_summary=f"HTTP {status} returned; marker presence inconclusive.",
|
|
206
|
+
reproducible=True,
|
|
207
|
+
remediation_advice="Manually verify multi-tenant query filter."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def check_header_trust(
|
|
212
|
+
cls,
|
|
213
|
+
validator: WebValidator,
|
|
214
|
+
finding_id: str,
|
|
215
|
+
cluster_id: str,
|
|
216
|
+
endpoint_url: str,
|
|
217
|
+
spoofed_headers: Dict[str, str],
|
|
218
|
+
expected_reflection_marker: str,
|
|
219
|
+
pre_approved: bool = True
|
|
220
|
+
) -> ExploitCheckResult:
|
|
221
|
+
"""
|
|
222
|
+
Validates whether the backend implicitly trusts spoofed client headers (e.g. X-Tenant-ID, X-Role).
|
|
223
|
+
"""
|
|
224
|
+
status, headers, body, decision = validator.execute_probe(
|
|
225
|
+
finding_id=finding_id,
|
|
226
|
+
cluster_id=cluster_id,
|
|
227
|
+
method="GET",
|
|
228
|
+
target_url=endpoint_url,
|
|
229
|
+
headers=spoofed_headers,
|
|
230
|
+
pre_approved=pre_approved,
|
|
231
|
+
route_context="header_trust_probe"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if not decision.allowed_to_proceed:
|
|
235
|
+
return ExploitCheckResult(
|
|
236
|
+
finding_id=finding_id,
|
|
237
|
+
issue_class="header_trust",
|
|
238
|
+
status=ExploitabilityStatus.BLOCKED_BY_CONTROLS.value,
|
|
239
|
+
confidence_score=50,
|
|
240
|
+
probe_url=endpoint_url,
|
|
241
|
+
http_status_observed=status,
|
|
242
|
+
proof_summary="Probe blocked by Safety Gate.",
|
|
243
|
+
reproducible=False,
|
|
244
|
+
remediation_advice="Review header trust manually."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if status == 200 and expected_reflection_marker in body:
|
|
248
|
+
return ExploitCheckResult(
|
|
249
|
+
finding_id=finding_id,
|
|
250
|
+
issue_class="header_trust",
|
|
251
|
+
status=ExploitabilityStatus.RUNTIME_CONFIRMED.value,
|
|
252
|
+
confidence_score=95,
|
|
253
|
+
probe_url=endpoint_url,
|
|
254
|
+
http_status_observed=status,
|
|
255
|
+
proof_summary=f"Backend adopted spoofed header values: '{expected_reflection_marker}' reflected in state/response.",
|
|
256
|
+
reproducible=True,
|
|
257
|
+
remediation_advice="Derive user and tenant identity from verified server-side session/JWT, not request headers."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return ExploitCheckResult(
|
|
261
|
+
finding_id=finding_id,
|
|
262
|
+
issue_class="header_trust",
|
|
263
|
+
status=ExploitabilityStatus.NOT_REPRODUCIBLE_IN_SCOPE.value,
|
|
264
|
+
confidence_score=20,
|
|
265
|
+
probe_url=endpoint_url,
|
|
266
|
+
http_status_observed=status,
|
|
267
|
+
proof_summary="Header spoofing rejected or ignored by backend.",
|
|
268
|
+
reproducible=True,
|
|
269
|
+
remediation_advice="Ensure gateway strips untrusted client headers."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
@classmethod
|
|
273
|
+
def check_debug_exposure(
|
|
274
|
+
cls,
|
|
275
|
+
validator: WebValidator,
|
|
276
|
+
finding_id: str,
|
|
277
|
+
cluster_id: str,
|
|
278
|
+
debug_url: str,
|
|
279
|
+
debug_marker: str,
|
|
280
|
+
pre_approved: bool = True
|
|
281
|
+
) -> ExploitCheckResult:
|
|
282
|
+
"""
|
|
283
|
+
Validates whether internal debug/profiler endpoints are exposed to public callers.
|
|
284
|
+
"""
|
|
285
|
+
status, headers, body, decision = validator.execute_probe(
|
|
286
|
+
finding_id=finding_id,
|
|
287
|
+
cluster_id=cluster_id,
|
|
288
|
+
method="GET",
|
|
289
|
+
target_url=debug_url,
|
|
290
|
+
pre_approved=pre_approved,
|
|
291
|
+
route_context="debug_exposure_probe"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if status == 200 and debug_marker in body:
|
|
295
|
+
return ExploitCheckResult(
|
|
296
|
+
finding_id=finding_id,
|
|
297
|
+
issue_class="debug_exposure",
|
|
298
|
+
status=ExploitabilityStatus.RUNTIME_CONFIRMED.value,
|
|
299
|
+
confidence_score=97,
|
|
300
|
+
probe_url=debug_url,
|
|
301
|
+
http_status_observed=status,
|
|
302
|
+
proof_summary=f"Debug endpoint {debug_url} is active and leaks internal environment markers.",
|
|
303
|
+
reproducible=True,
|
|
304
|
+
remediation_advice="Set DEBUG=False and disable development profiler routes in production."
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return ExploitCheckResult(
|
|
308
|
+
finding_id=finding_id,
|
|
309
|
+
issue_class="debug_exposure",
|
|
310
|
+
status=ExploitabilityStatus.NOT_REPRODUCIBLE_IN_SCOPE.value,
|
|
311
|
+
confidence_score=10,
|
|
312
|
+
probe_url=debug_url,
|
|
313
|
+
http_status_observed=status,
|
|
314
|
+
proof_summary="Debug route disabled or restricted (HTTP 404/403).",
|
|
315
|
+
reproducible=True,
|
|
316
|
+
remediation_advice="Maintain strict environment variable isolation."
|
|
317
|
+
)
|