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,351 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard Report Formatter (v0.5.4)
|
|
3
|
+
Renders comprehensive, actionable, ticket-ready Markdown and structured JSON audit reports.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
|
+
from .models import (
|
|
8
|
+
AuditReport,
|
|
9
|
+
Finding,
|
|
10
|
+
SeverityLevel,
|
|
11
|
+
ConfidenceBand,
|
|
12
|
+
FindingStatus,
|
|
13
|
+
RemediationPriority,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ReportFormatter:
|
|
18
|
+
"""
|
|
19
|
+
Produces deterministic, human-readable, ticket-ready Markdown and structured JSON audit reports.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
SEVERITY_BADGES = {
|
|
23
|
+
SeverityLevel.CRITICAL: "🔴 Critical",
|
|
24
|
+
SeverityLevel.HIGH: "🟠 High",
|
|
25
|
+
SeverityLevel.MEDIUM: "🟡 Medium",
|
|
26
|
+
SeverityLevel.LOW: "🔵 Low",
|
|
27
|
+
SeverityLevel.INFORMATIONAL: "ℹ️ Info",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
CONFIDENCE_BADGES = {
|
|
31
|
+
ConfidenceBand.CONFIRMED: "🔒 Confirmed",
|
|
32
|
+
ConfidenceBand.HIGH_CONFIDENCE: "🟢 High Confidence",
|
|
33
|
+
ConfidenceBand.MEDIUM_CONFIDENCE: "🟡 Medium Confidence",
|
|
34
|
+
ConfidenceBand.LOW_CONFIDENCE: "🟠 Low Confidence",
|
|
35
|
+
ConfidenceBand.UNCONFIRMED: "⚪ Unconfirmed",
|
|
36
|
+
ConfidenceBand.NEEDS_REVIEW: "🔍 Needs Review",
|
|
37
|
+
ConfidenceBand.INFORMATIONAL: "ℹ️ Informational",
|
|
38
|
+
ConfidenceBand.NOT_APPLICABLE: "⚪ N/A",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
STATUS_BADGES = {
|
|
42
|
+
FindingStatus.CONFIRMED: "🔴 Confirmed",
|
|
43
|
+
FindingStatus.HIGH_CONFIDENCE: "🟠 High Confidence",
|
|
44
|
+
FindingStatus.MEDIUM_CONFIDENCE: "🟡 Medium Confidence",
|
|
45
|
+
FindingStatus.LOW_CONFIDENCE: "⚪ Low Confidence",
|
|
46
|
+
FindingStatus.UNCONFIRMED: "⚪ Unconfirmed",
|
|
47
|
+
FindingStatus.NEEDS_REVIEW: "🔍 Needs Review",
|
|
48
|
+
FindingStatus.REMEDIATED: "🟡 Remediated (Pending Retest)",
|
|
49
|
+
FindingStatus.VERIFIED_FIXED: "🟢 Verified Fixed",
|
|
50
|
+
FindingStatus.SUPPRESSED: "⚪ Suppressed",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
PRIORITY_BADGES = {
|
|
54
|
+
RemediationPriority.IMMEDIATE: "🚨 Immediate (P0)",
|
|
55
|
+
RemediationPriority.NEAR_TERM: "🟠 Near-Term (P1)",
|
|
56
|
+
RemediationPriority.BACKLOG: "🟡 Backlog (P2)",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def render_markdown(report: AuditReport) -> str:
|
|
61
|
+
report.calculate_summary()
|
|
62
|
+
summary = report.summary_counts
|
|
63
|
+
|
|
64
|
+
# Overall posture indicator
|
|
65
|
+
if summary.get("critical", 0) > 0 or summary.get("high", 0) > 0:
|
|
66
|
+
posture_icon = "🔴 Action Required (Pre-Deployment Blockers Found)"
|
|
67
|
+
elif summary.get("medium", 0) > 0:
|
|
68
|
+
posture_icon = "🟡 Warnings Found (Hardening Recommended)"
|
|
69
|
+
elif summary.get("needs_review", 0) > 0:
|
|
70
|
+
posture_icon = "🔍 Needs Review (Architectural Verification Required)"
|
|
71
|
+
else:
|
|
72
|
+
posture_icon = "🟢 Ready / Verified Safe (All Checks Passing)"
|
|
73
|
+
|
|
74
|
+
lines = [
|
|
75
|
+
f"# TorusGuard Security Audit & Remediation Report",
|
|
76
|
+
f"",
|
|
77
|
+
f"> **Project:** `{report.project_name}` | **Repository Reference:** `{report.repository_ref}` ",
|
|
78
|
+
f"> **Engine Version:** TorusGuard `{report.torusguard_version}` (Actionable Remediation & Usability Release) ",
|
|
79
|
+
f"> **Generated At:** `{report.generated_at}` | **Report Owner:** `{report.report_owner}` ",
|
|
80
|
+
f"> **Overall Security Posture:** **{posture_icon}**",
|
|
81
|
+
f"",
|
|
82
|
+
f"---",
|
|
83
|
+
f"",
|
|
84
|
+
f"## 1. 📋 Executive Summary",
|
|
85
|
+
f"",
|
|
86
|
+
f"This security assessment evaluated **{report.project_name}** using TorusGuard's evidence-backed finding lifecycle and deterministic validation engine. The scan analyzed source files, configuration manifests, data queries, and API endpoint boundaries.",
|
|
87
|
+
f"",
|
|
88
|
+
f"- **Key Risk Themes:** " + (
|
|
89
|
+
"Critical authorization boundaries, input handling, and configuration exposures require remediation."
|
|
90
|
+
if summary.get("critical", 0) > 0 or summary.get("high", 0) > 0
|
|
91
|
+
else "No high-severity vulnerabilities detected; defensive baselines satisfied."
|
|
92
|
+
),
|
|
93
|
+
f"- **Validation Status:** `{summary.get('confirmed', 0)}` Confirmed Findings | `{summary.get('needs_review', 0)}` Needs Review | `{summary.get('verified_fixed', 0)}` Verified Fixed",
|
|
94
|
+
f"- **Immediate Action Required:** " + (
|
|
95
|
+
f"Triage and remediate `{summary.get('immediate_priority', 0)}` P0 immediate priority finding(s) before production deployment."
|
|
96
|
+
if summary.get("immediate_priority", 0) > 0
|
|
97
|
+
else "Application meets primary security release criteria."
|
|
98
|
+
),
|
|
99
|
+
f"",
|
|
100
|
+
f"### 📊 Security Metrics at a Glance",
|
|
101
|
+
f"",
|
|
102
|
+
f"| Metric | Count | Stakeholder Guidance |",
|
|
103
|
+
f"|---|:---:|---|",
|
|
104
|
+
f"| **Total Tracked Findings** | `{summary.get('total_findings', 0)}` | Total normalized finding objects |",
|
|
105
|
+
f"| **Immediate Priority (P0)** | `{summary.get('immediate_priority', 0)}` | 🚨 Pre-deployment blockers |",
|
|
106
|
+
f"| **Near-Term Priority (P1)** | `{summary.get('near_term_priority', 0)}` | 🟠 Current sprint patch cycle |",
|
|
107
|
+
f"| **Backlog / Hardening (P2)** | `{summary.get('backlog_priority', 0)}` | 🟡 Defense-in-depth backlog |",
|
|
108
|
+
f"| **Auditable Average Confidence** | `{summary.get('average_confidence_score', 0)}/100` | Mathematical evidence score |",
|
|
109
|
+
f"| **Verified Fixed Post-Retest** | `{summary.get('verified_fixed', 0)}` | 🟢 Retest confirmed resolution |",
|
|
110
|
+
f"",
|
|
111
|
+
f"---",
|
|
112
|
+
f"",
|
|
113
|
+
f"## 2. 🔍 Scope and Methodology",
|
|
114
|
+
f"",
|
|
115
|
+
f"- **Target Application:** `{report.project_name}`",
|
|
116
|
+
f"- **Detected Stack:** `{report.detected_stack.get('language', 'Unknown')}` / `{report.detected_stack.get('framework', 'Unknown')}` (Data layer: `{report.detected_stack.get('data_layer', 'None')}`)",
|
|
117
|
+
f"- **Tested Scope:** Source code AST structures, route handlers, serializer definitions, database queries, upload paths, and environment configurations.",
|
|
118
|
+
f"- **Out of Scope:** Live production network penetration testing, active DDoS simulation, third-party SaaS infrastructure compliance audits.",
|
|
119
|
+
f"- **Interpretation Guide:** Every finding is backed by immutable cryptographic evidence hashes. Findings marked `Needs Review` reflect out-of-band architecture requiring human architectural verification.",
|
|
120
|
+
f"",
|
|
121
|
+
f"---",
|
|
122
|
+
f"",
|
|
123
|
+
f"## 3. 📑 Key Findings Summary Table",
|
|
124
|
+
f"",
|
|
125
|
+
f"| Ref ID | Title | Category | Severity | Confidence | Status | Priority | Location |",
|
|
126
|
+
f"|---|---|---|:---:|:---:|:---:|:---:|---|",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
if not report.findings:
|
|
130
|
+
lines.append("| — | *No findings identified* | — | — | — | 🟢 Safe | — | — |")
|
|
131
|
+
else:
|
|
132
|
+
for f in report.findings:
|
|
133
|
+
sev_badge = ReportFormatter.SEVERITY_BADGES.get(f.severity.level, str(f.severity.level))
|
|
134
|
+
conf_badge = f"{f.confidence.score}/100"
|
|
135
|
+
stat_badge = ReportFormatter.STATUS_BADGES.get(f.status, str(f.status.value))
|
|
136
|
+
pri_badge = ReportFormatter.PRIORITY_BADGES.get(f.remediation_priority, str(f.remediation_priority))
|
|
137
|
+
loc_str = f"`{f.affected_component.target_path}`"
|
|
138
|
+
if f.affected_component.start_line:
|
|
139
|
+
loc_str += f":{f.affected_component.start_line}"
|
|
140
|
+
lines.append(
|
|
141
|
+
f"| `{f.rule_id}` | **{f.title}** | `{f.category.value}` | {sev_badge} | {conf_badge} | {stat_badge} | {pri_badge} | {loc_str} |"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
lines.extend([
|
|
145
|
+
f"",
|
|
146
|
+
f"---",
|
|
147
|
+
f"",
|
|
148
|
+
f"## 4. 🛡️ Detailed Findings",
|
|
149
|
+
f"",
|
|
150
|
+
])
|
|
151
|
+
|
|
152
|
+
if not report.findings:
|
|
153
|
+
lines.append("🎉 **No vulnerabilities detected!** Application complies with all TorusGuard security baselines.\n")
|
|
154
|
+
else:
|
|
155
|
+
for idx, f in enumerate(report.findings, 1):
|
|
156
|
+
sev_badge = ReportFormatter.SEVERITY_BADGES.get(f.severity.level, str(f.severity.level))
|
|
157
|
+
conf_badge = ReportFormatter.CONFIDENCE_BADGES.get(f.confidence.band, str(f.confidence.band))
|
|
158
|
+
status_badge = ReportFormatter.STATUS_BADGES.get(f.status, str(f.status.value))
|
|
159
|
+
pri_badge = ReportFormatter.PRIORITY_BADGES.get(f.remediation_priority, str(f.remediation_priority))
|
|
160
|
+
|
|
161
|
+
loc_str = f"{f.affected_component.target_path}"
|
|
162
|
+
if f.affected_component.start_line:
|
|
163
|
+
loc_str += f":{f.affected_component.start_line}"
|
|
164
|
+
if f.affected_component.symbol:
|
|
165
|
+
loc_str += f" (`{f.affected_component.symbol}`)"
|
|
166
|
+
|
|
167
|
+
factors = f.confidence.factors
|
|
168
|
+
|
|
169
|
+
lines.extend([
|
|
170
|
+
f"### {idx}. [{f.rule_id}] {f.title}",
|
|
171
|
+
f"",
|
|
172
|
+
f"> **Finding ID:** `{f.finding_id}` | **Remediation Priority:** **{pri_badge}** ",
|
|
173
|
+
f"> **Severity:** {sev_badge} | **Auditable Confidence:** {conf_badge} (`{f.confidence.score}/100`) | **Status:** {status_badge} ",
|
|
174
|
+
f"> **Location:** `{loc_str}` | **Lifecycle Stage:** `{f.lifecycle_stage.value}`",
|
|
175
|
+
f"",
|
|
176
|
+
f"#### 🏢 Business Impact & Executive Context",
|
|
177
|
+
f"{f.notes.business_impact}",
|
|
178
|
+
f"",
|
|
179
|
+
f"#### ⚙️ Technical Mechanics & Threat Context",
|
|
180
|
+
f"{f.notes.technical_description}",
|
|
181
|
+
f"",
|
|
182
|
+
f"#### 🔍 Auditable Confidence Score Breakdown (`{f.confidence.score}/100`)",
|
|
183
|
+
f"- **Evidence Quality:** `{factors.evidence_quality}/35` pts (Direct source AST/syntax match)",
|
|
184
|
+
f"- **Reproduction Success:** `{factors.reproduction_success}/25` pts (Deterministic reproduction path)",
|
|
185
|
+
f"- **Independent Confirmations:** `{factors.independent_confirmations}/15` pts (Corroborated across files)",
|
|
186
|
+
f"- **Environmental Clarity:** `{factors.environmental_clarity}/15` pts (No ambiguous out-of-band proxy/service)",
|
|
187
|
+
f"- **Manual Review Status:** `{factors.manual_review_status}/10` pts (Reviewer validation)",
|
|
188
|
+
f"- **Confidence Justification:** {f.confidence.rationale}",
|
|
189
|
+
f"",
|
|
190
|
+
f"#### 🔗 Provenance Chain",
|
|
191
|
+
f"- **Discovery Module:** `{f.provenance.discovery_module}`",
|
|
192
|
+
f"- **Triggering Input:** `{f.provenance.triggering_input}`",
|
|
193
|
+
f"- **Decision Path:**",
|
|
194
|
+
])
|
|
195
|
+
|
|
196
|
+
for step in f.provenance.decision_path:
|
|
197
|
+
lines.append(f" 1. {step}")
|
|
198
|
+
|
|
199
|
+
lines.extend([
|
|
200
|
+
f"- **Verification Step:** {f.provenance.verification_step}",
|
|
201
|
+
f"",
|
|
202
|
+
f"#### 📦 Technical Evidence Package",
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
for ev_idx, ev in enumerate(f.evidence, 1):
|
|
206
|
+
lines.extend([
|
|
207
|
+
f"**Evidence Item #{ev_idx} ({ev.type.value}):** `{ev.location}`",
|
|
208
|
+
f"- **SHA-256 Checksum:** `{ev.sha256_checksum}`",
|
|
209
|
+
f"- **Rationale:** {ev.rationale}",
|
|
210
|
+
f"```",
|
|
211
|
+
f"{ev.get_masked_snippet().strip()}",
|
|
212
|
+
f"```",
|
|
213
|
+
])
|
|
214
|
+
|
|
215
|
+
lines.extend([
|
|
216
|
+
f"#### 🛠️ Prescriptive Remediation",
|
|
217
|
+
f"**Problem Statement:** {f.remediation.problem_statement}",
|
|
218
|
+
f"",
|
|
219
|
+
f"**Recommended Action:** {f.remediation.recommended_fix}",
|
|
220
|
+
f"",
|
|
221
|
+
f"**Framework Patch ({f.remediation.framework_pattern.framework}):**",
|
|
222
|
+
f"```diff",
|
|
223
|
+
f"- // UNSAFE ORIGINAL",
|
|
224
|
+
f"- {f.remediation.framework_pattern.unsafe_snippet.strip()}",
|
|
225
|
+
f"+ // SAFE REMEDIATION",
|
|
226
|
+
f"+ {f.remediation.framework_pattern.safe_snippet.strip()}",
|
|
227
|
+
f"```",
|
|
228
|
+
f"",
|
|
229
|
+
f"**Verification Method:** {f.remediation.verification_method}",
|
|
230
|
+
f"",
|
|
231
|
+
f"**Residual Risk Notes:** {f.remediation.residual_risk_notes}",
|
|
232
|
+
f"",
|
|
233
|
+
f"#### 🔁 Retest & Closure Status",
|
|
234
|
+
])
|
|
235
|
+
|
|
236
|
+
if f.retest_result.retest_performed:
|
|
237
|
+
lines.extend([
|
|
238
|
+
f"- **Retest Executed:** ✅ Yes (`{f.retest_result.retest_timestamp}`)",
|
|
239
|
+
f"- **Closure Status:** **`{f.retest_result.closure_status.value}`**",
|
|
240
|
+
f"- **Retest Method:** `{f.retest_result.retest_method}`",
|
|
241
|
+
f"- **Post-Fix Evidence Hash:** `{f.retest_result.retest_evidence_hash}`",
|
|
242
|
+
f"- **Verifier Notes:** {f.retest_result.verifier_notes}",
|
|
243
|
+
])
|
|
244
|
+
else:
|
|
245
|
+
lines.extend([
|
|
246
|
+
f"- **Retest Executed:** ⏳ Pending (Run `/torusguard recheck` after applying fix)",
|
|
247
|
+
f"- **Closure Status:** `{f.status.value}`",
|
|
248
|
+
])
|
|
249
|
+
|
|
250
|
+
# Copy-pasteable Ticket/Issue payload
|
|
251
|
+
lines.extend([
|
|
252
|
+
f"",
|
|
253
|
+
f"<details>",
|
|
254
|
+
f"<summary>🎫 Copy-Paste Issue Tracker Payload (GitHub / Jira / Linear)</summary>",
|
|
255
|
+
f"",
|
|
256
|
+
f"```markdown",
|
|
257
|
+
f"### [Security] Fix {f.rule_id}: {f.title}",
|
|
258
|
+
f"",
|
|
259
|
+
f"**Priority:** {pri_badge} | **Severity:** {sev_badge} | **Location:** `{loc_str}`",
|
|
260
|
+
f"",
|
|
261
|
+
f"#### Problem",
|
|
262
|
+
f"{f.remediation.problem_statement}",
|
|
263
|
+
f"",
|
|
264
|
+
f"#### Business Impact",
|
|
265
|
+
f"{f.notes.business_impact}",
|
|
266
|
+
f"",
|
|
267
|
+
f"#### Proposed Fix",
|
|
268
|
+
f"{f.remediation.recommended_fix}",
|
|
269
|
+
f"",
|
|
270
|
+
f"#### Verification",
|
|
271
|
+
f"Run `/torusguard recheck` to verify resolution.",
|
|
272
|
+
f"```",
|
|
273
|
+
f"</details>",
|
|
274
|
+
f"",
|
|
275
|
+
f"---",
|
|
276
|
+
f"",
|
|
277
|
+
])
|
|
278
|
+
|
|
279
|
+
# Section 5: Remediation Priorities
|
|
280
|
+
lines.extend([
|
|
281
|
+
f"## 5. 🎯 Remediation Priorities & Triage Roadmap",
|
|
282
|
+
f"",
|
|
283
|
+
f"### 🚨 Immediate Priority (P0 — Pre-Deployment Blockers)",
|
|
284
|
+
])
|
|
285
|
+
|
|
286
|
+
immediate_findings = [f for f in report.findings if f.remediation_priority == RemediationPriority.IMMEDIATE]
|
|
287
|
+
if immediate_findings:
|
|
288
|
+
for f in immediate_findings:
|
|
289
|
+
lines.append(f"- [ ] **[{f.rule_id}] {f.title}** (`{f.affected_component.target_path}`) — {f.remediation.recommended_fix}")
|
|
290
|
+
else:
|
|
291
|
+
lines.append("- *No immediate P0 blockers.*")
|
|
292
|
+
|
|
293
|
+
lines.extend([
|
|
294
|
+
f"",
|
|
295
|
+
f"### 🟠 Near-Term Priority (P1 — Current Sprint Patch Cycle)",
|
|
296
|
+
])
|
|
297
|
+
|
|
298
|
+
near_term_findings = [f for f in report.findings if f.remediation_priority == RemediationPriority.NEAR_TERM]
|
|
299
|
+
if near_term_findings:
|
|
300
|
+
for f in near_term_findings:
|
|
301
|
+
lines.append(f"- [ ] **[{f.rule_id}] {f.title}** (`{f.affected_component.target_path}`) — {f.remediation.recommended_fix}")
|
|
302
|
+
else:
|
|
303
|
+
lines.append("- *No near-term P1 items.*")
|
|
304
|
+
|
|
305
|
+
lines.extend([
|
|
306
|
+
f"",
|
|
307
|
+
f"### 🟡 Backlog / Hardening (P2 — Defense-in-Depth)",
|
|
308
|
+
])
|
|
309
|
+
|
|
310
|
+
backlog_findings = [f for f in report.findings if f.remediation_priority == RemediationPriority.BACKLOG]
|
|
311
|
+
if backlog_findings:
|
|
312
|
+
for f in backlog_findings:
|
|
313
|
+
lines.append(f"- [ ] **[{f.rule_id}] {f.title}** (`{f.affected_component.target_path}`) — {f.remediation.recommended_fix}")
|
|
314
|
+
else:
|
|
315
|
+
lines.append("- *No backlog hardening items.*")
|
|
316
|
+
|
|
317
|
+
# Section 6: Retest & Verification
|
|
318
|
+
lines.extend([
|
|
319
|
+
f"",
|
|
320
|
+
f"---",
|
|
321
|
+
f"",
|
|
322
|
+
f"## 6. 🔁 Retest & Verification Workflow",
|
|
323
|
+
f"1. **Apply Code Patches:** Follow the Before/After framework diffs provided in Section 4.",
|
|
324
|
+
f"2. **Execute Retest:** Run `/torusguard recheck` to verify that findings transition to `Verified Fixed`.",
|
|
325
|
+
f"3. **Inspect Needs Review Items:** For items marked `Needs Review`, manually verify service-layer authorization and cloud IAM policies.",
|
|
326
|
+
f"",
|
|
327
|
+
f"---",
|
|
328
|
+
f"",
|
|
329
|
+
f"## 7. ⚖️ Limitations & Operational Boundaries",
|
|
330
|
+
f"- **Source-Only Analysis:** Findings are derived from static source code inspection, AST patterns, and configuration files.",
|
|
331
|
+
f"- **Manual Review Boundary:** When authorization or validation is delegated to out-of-band microservices, reverse proxies, or cloud IAM, TorusGuard assigns `Needs Review`.",
|
|
332
|
+
f"- **Sensitive Data Masking:** All secrets, tokens, and credentials in evidence snippets are automatically redacted.",
|
|
333
|
+
f"",
|
|
334
|
+
f"---",
|
|
335
|
+
f"",
|
|
336
|
+
f"## 8. 📚 Appendix & Reference Models",
|
|
337
|
+
f"",
|
|
338
|
+
f"### Confidence Scoring Model (0–100)",
|
|
339
|
+
f"- **90–100 (`Confirmed`):** Direct, indisputable proof of vulnerability with reachable sink.",
|
|
340
|
+
f"- **70–89 (`High Confidence`):** Strong static indicators present; localized verification recommended.",
|
|
341
|
+
f"- **50–69 (`Medium Confidence`):** Probable flaw; runtime confirmation recommended.",
|
|
342
|
+
f"- **< 50 (`Needs Review`):** Insufficient evidence or out-of-band architectural delegation.",
|
|
343
|
+
f"",
|
|
344
|
+
f"### Severity Rubric",
|
|
345
|
+
f"- **Critical (P0):** Remote Code Execution, unauthenticated SQL injection, hardcoded secrets, complete auth bypass.",
|
|
346
|
+
f"- **High (P1):** Object-level authorization bypass (IDOR), SSRF, XSS, tenant data leakage.",
|
|
347
|
+
f"- **Medium (P2):** Missing rate limits, insecure cookie attributes, missing CSRF tokens.",
|
|
348
|
+
f"- **Low / Info:** Verbose error banners, public source maps, hardening recommendations.",
|
|
349
|
+
])
|
|
350
|
+
|
|
351
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v6 Minimal Patch Governance Engine
|
|
3
|
+
Enforces strict policy boundaries around automated code modifications:
|
|
4
|
+
- Line churn bounding (max added/removed lines)
|
|
5
|
+
- File count limits (single-file preference)
|
|
6
|
+
- High-risk file escalation (auth, crypto, tenant isolation, DB, uploads, workflows)
|
|
7
|
+
- Comment/boilerplate checks
|
|
8
|
+
- Zero unrelated file changes
|
|
9
|
+
- Automatic application blocking for oversized or high-risk diffs
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field, asdict
|
|
13
|
+
from typing import List, Dict, Any, Tuple, Optional
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Sensitive Path Categories
|
|
19
|
+
SENSITIVE_CATEGORIES = {
|
|
20
|
+
"auth": ["auth", "login", "password", "token", "jwt", "session", "oauth", "oidc", "credential"],
|
|
21
|
+
"tenancy": ["tenant", "tenant_id", "organization_id", "org_id", "workspace_id"],
|
|
22
|
+
"secrets": ["secret", "api_key", "private_key", "ssh_key", "access_key"],
|
|
23
|
+
"crypto": ["crypto", "cipher", "encrypt", "decrypt", "hashlib", "hmac"],
|
|
24
|
+
"uploads": ["upload", "storage", "filepath", "save_file", "download"],
|
|
25
|
+
"workflows": [".github/workflows", "Dockerfile", "Containerfile", "compose.yaml", "docker-compose"]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
HIGH_RISK_KEYWORDS = [kw for kws in SENSITIVE_CATEGORIES.values() for kw in kws]
|
|
29
|
+
|
|
30
|
+
HIGH_RISK_DIRECTORIES = [
|
|
31
|
+
"auth", "authentication", "authorization", "security",
|
|
32
|
+
"crypto", "migrations", ".github/workflows"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class PatchPolicyDecision:
|
|
38
|
+
allowed_auto_apply: bool
|
|
39
|
+
escalation_required: bool
|
|
40
|
+
review_level: str = "Automatic" # "Automatic" | "Peer Review Recommended" | "Mandatory Security Sign-Off"
|
|
41
|
+
rejection_reasons: List[str] = field(default_factory=list)
|
|
42
|
+
risk_factors: List[str] = field(default_factory=list)
|
|
43
|
+
line_additions: int = 0
|
|
44
|
+
line_deletions: int = 0
|
|
45
|
+
files_touched: int = 0
|
|
46
|
+
file_list: List[str] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
49
|
+
return asdict(self)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PatchGovernor:
|
|
53
|
+
"""
|
|
54
|
+
Evaluates proposed diffs against minimal patch governance rules.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
max_additions_per_file: int = 35,
|
|
60
|
+
max_deletions_per_file: int = 25,
|
|
61
|
+
max_total_files: int = 2,
|
|
62
|
+
strict_high_risk_escalation: bool = True,
|
|
63
|
+
):
|
|
64
|
+
self.max_additions_per_file = max_additions_per_file
|
|
65
|
+
self.max_deletions_per_file = max_deletions_per_file
|
|
66
|
+
self.max_total_files = max_total_files
|
|
67
|
+
self.strict_high_risk_escalation = strict_high_risk_escalation
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _parse_diff(diff_content: str, target_file: Optional[str] = None) -> Tuple[set[str], int, int, int]:
|
|
71
|
+
"""Parses unified diff lines to extract modified files, churn counts, and filler comment lines."""
|
|
72
|
+
lines = diff_content.splitlines()
|
|
73
|
+
additions = 0
|
|
74
|
+
deletions = 0
|
|
75
|
+
files: set[str] = set()
|
|
76
|
+
unnecessary_comment_lines = 0
|
|
77
|
+
|
|
78
|
+
for line in lines:
|
|
79
|
+
if line.startswith("+++ b/"):
|
|
80
|
+
files.add(line[6:].strip())
|
|
81
|
+
elif line.startswith("--- a/"):
|
|
82
|
+
old_file = line[6:].strip()
|
|
83
|
+
if old_file != "/dev/null":
|
|
84
|
+
files.add(old_file)
|
|
85
|
+
elif line.startswith("+") and not line.startswith("+++"):
|
|
86
|
+
additions += 1
|
|
87
|
+
stripped = line[1:].strip()
|
|
88
|
+
if stripped.startswith("# TODO:") or stripped.startswith("// Added by AI"):
|
|
89
|
+
unnecessary_comment_lines += 1
|
|
90
|
+
elif line.startswith("-") and not line.startswith("---"):
|
|
91
|
+
deletions += 1
|
|
92
|
+
|
|
93
|
+
if target_file and not files:
|
|
94
|
+
files.add(target_file)
|
|
95
|
+
|
|
96
|
+
return files, additions, deletions, unnecessary_comment_lines
|
|
97
|
+
|
|
98
|
+
def enforce_file_bounds(self, files_count: int) -> Optional[str]:
|
|
99
|
+
"""Checks if total files modified exceed policy threshold."""
|
|
100
|
+
if files_count > self.max_total_files:
|
|
101
|
+
return f"Patch modifies {files_count} files (maximum allowed is {self.max_total_files})."
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def enforce_line_bounds(self, additions: int, deletions: int) -> List[str]:
|
|
105
|
+
"""Checks if line additions or deletions exceed policy thresholds."""
|
|
106
|
+
reasons = []
|
|
107
|
+
if additions > self.max_additions_per_file:
|
|
108
|
+
reasons.append(f"Line additions ({additions}) exceed threshold ({self.max_additions_per_file}).")
|
|
109
|
+
if deletions > self.max_deletions_per_file:
|
|
110
|
+
reasons.append(f"Line deletions ({deletions}) exceed threshold ({self.max_deletions_per_file}).")
|
|
111
|
+
return reasons
|
|
112
|
+
|
|
113
|
+
def check_filler_comments(self, unnecessary_comment_lines: int) -> Optional[str]:
|
|
114
|
+
"""Checks for excessive boilerplate or AI commentary in diff."""
|
|
115
|
+
if unnecessary_comment_lines > 2:
|
|
116
|
+
return "Patch contains excessive boilerplate or commentary."
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def escalate_sensitive_paths(self, files: set[str], diff_content: str = "") -> Tuple[bool, List[str]]:
|
|
120
|
+
"""Identifies if any touched file or diff content matches high-risk sensitive domain keywords."""
|
|
121
|
+
risk_factors = []
|
|
122
|
+
escalation_required = False
|
|
123
|
+
for f in files:
|
|
124
|
+
norm_f = f.lower().replace("\\", "/")
|
|
125
|
+
for kw in HIGH_RISK_KEYWORDS:
|
|
126
|
+
if kw in norm_f:
|
|
127
|
+
risk_factors.append(f"File `{f}` touches high-risk domain keyword `{kw}`.")
|
|
128
|
+
escalation_required = True
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
if diff_content:
|
|
132
|
+
for line in diff_content.splitlines():
|
|
133
|
+
if (line.startswith("+") and not line.startswith("+++")) or (line.startswith("-") and not line.startswith("---")):
|
|
134
|
+
line_lower = line.lower()
|
|
135
|
+
for kw in HIGH_RISK_KEYWORDS:
|
|
136
|
+
if kw in line_lower:
|
|
137
|
+
risk_factors.append(f"Diff line touches high-risk domain keyword `{kw}`.")
|
|
138
|
+
escalation_required = True
|
|
139
|
+
break
|
|
140
|
+
if escalation_required:
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
return escalation_required, risk_factors
|
|
144
|
+
|
|
145
|
+
def determine_review_level(
|
|
146
|
+
self, escalation_required: bool, additions: int, deletions: int, files_count: int
|
|
147
|
+
) -> Tuple[str, bool, List[str]]:
|
|
148
|
+
"""Determines review escalation level and whether auto-apply is blocked."""
|
|
149
|
+
reasons = []
|
|
150
|
+
review_level = "Automatic"
|
|
151
|
+
allowed = True
|
|
152
|
+
|
|
153
|
+
if escalation_required:
|
|
154
|
+
if additions > 10 or deletions > 10 or files_count > 1:
|
|
155
|
+
review_level = "Mandatory Security Sign-Off"
|
|
156
|
+
if self.strict_high_risk_escalation:
|
|
157
|
+
allowed = False
|
|
158
|
+
reasons.append("High-risk file modifications with non-trivial churn require explicit human approval.")
|
|
159
|
+
else:
|
|
160
|
+
review_level = "Peer Review Recommended"
|
|
161
|
+
|
|
162
|
+
return review_level, allowed, reasons
|
|
163
|
+
|
|
164
|
+
def evaluate_patch(self, diff_content: str, target_file: Optional[str] = None) -> PatchPolicyDecision:
|
|
165
|
+
"""
|
|
166
|
+
Parses unified diff and evaluates all governance policy compliance rules.
|
|
167
|
+
"""
|
|
168
|
+
files, additions, deletions, comment_lines = self._parse_diff(diff_content, target_file)
|
|
169
|
+
|
|
170
|
+
rejection_reasons: List[str] = []
|
|
171
|
+
|
|
172
|
+
# 1. File Count Check
|
|
173
|
+
file_err = self.enforce_file_bounds(len(files))
|
|
174
|
+
if file_err:
|
|
175
|
+
rejection_reasons.append(file_err)
|
|
176
|
+
|
|
177
|
+
# 2. Line Churn Check
|
|
178
|
+
rejection_reasons.extend(self.enforce_line_bounds(additions, deletions))
|
|
179
|
+
|
|
180
|
+
# 3. Filler Comment Check
|
|
181
|
+
comment_err = self.check_filler_comments(comment_lines)
|
|
182
|
+
if comment_err:
|
|
183
|
+
rejection_reasons.append(comment_err)
|
|
184
|
+
|
|
185
|
+
# 4. Sensitive Path Escalation
|
|
186
|
+
escalation_required, risk_factors = self.escalate_sensitive_paths(files, diff_content)
|
|
187
|
+
|
|
188
|
+
# 5. Review Level Determination
|
|
189
|
+
review_level, allowed_by_escalation, escalation_reasons = self.determine_review_level(
|
|
190
|
+
escalation_required, additions, deletions, len(files)
|
|
191
|
+
)
|
|
192
|
+
rejection_reasons.extend(escalation_reasons)
|
|
193
|
+
|
|
194
|
+
allowed = (len(rejection_reasons) == 0) and allowed_by_escalation
|
|
195
|
+
|
|
196
|
+
return PatchPolicyDecision(
|
|
197
|
+
allowed_auto_apply=allowed,
|
|
198
|
+
escalation_required=escalation_required,
|
|
199
|
+
review_level=review_level,
|
|
200
|
+
rejection_reasons=rejection_reasons,
|
|
201
|
+
risk_factors=risk_factors,
|
|
202
|
+
line_additions=additions,
|
|
203
|
+
line_deletions=deletions,
|
|
204
|
+
files_touched=len(files),
|
|
205
|
+
file_list=list(files),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def evaluate_diff(self, diff_content: str, target_file: Optional[str] = None) -> PatchPolicyDecision:
|
|
209
|
+
"""Backward-compatible alias for evaluate_patch."""
|
|
210
|
+
return self.evaluate_patch(diff_content, target_file)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TorusGuard v6 Stable Finding Identity & Fingerprinting Engine
|
|
3
|
+
Generates deterministic finding identifiers that survive line-number shifts,
|
|
4
|
+
minor refactorings, and file relocations within the same logical scope.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Dict, Any
|
|
11
|
+
from dataclasses import dataclass, asdict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def normalize_code_snippet(code: str) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Normalizes a code snippet to be whitespace- and comment-tolerant
|
|
17
|
+
so small styling edits or empty lines don't change the region hash.
|
|
18
|
+
"""
|
|
19
|
+
if not code:
|
|
20
|
+
return ""
|
|
21
|
+
lines = []
|
|
22
|
+
for line in code.strip().splitlines():
|
|
23
|
+
stripped = line.strip()
|
|
24
|
+
# Remove single-line comments for hash stability if they are pure comment lines
|
|
25
|
+
if stripped.startswith("#") or stripped.startswith("//"):
|
|
26
|
+
continue
|
|
27
|
+
# Normalize internal whitespace
|
|
28
|
+
normalized = re.sub(r"\s+", " ", stripped)
|
|
29
|
+
if normalized:
|
|
30
|
+
lines.append(normalized)
|
|
31
|
+
return "\n".join(lines)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class FindingFingerprint:
|
|
36
|
+
rule_id: str
|
|
37
|
+
normalized_path: str
|
|
38
|
+
region_hash: str
|
|
39
|
+
sink_signature: Optional[str] = None
|
|
40
|
+
framework_marker: Optional[str] = None
|
|
41
|
+
fingerprint_id: str = ""
|
|
42
|
+
taint_depth: Optional[int] = None
|
|
43
|
+
sanitizer_present: bool = False
|
|
44
|
+
|
|
45
|
+
def __post_init__(self):
|
|
46
|
+
if not self.fingerprint_id:
|
|
47
|
+
self.fingerprint_id = self.compute_fingerprint_id()
|
|
48
|
+
|
|
49
|
+
def compute_fingerprint_id(self) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Computes a stable hash based on (rule_id, normalized_path, region_hash, sink_signature).
|
|
52
|
+
Format: TG-FND-<hash12>
|
|
53
|
+
"""
|
|
54
|
+
data = f"{self.rule_id}|{self.normalized_path}|{self.region_hash}|{self.sink_signature or ''}|{self.framework_marker or ''}"
|
|
55
|
+
h = hashlib.sha256(data.encode("utf-8")).hexdigest()[:12]
|
|
56
|
+
# Clean rule id component for readable prefix
|
|
57
|
+
rule_prefix = self.rule_id.replace("TG-", "").split("-")[0]
|
|
58
|
+
return f"TG-{rule_prefix}-{h}"
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
61
|
+
return asdict(self)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class IdentityEngine:
|
|
65
|
+
"""
|
|
66
|
+
Computes stable finding identities and persists fingerprint maps.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def generate_identity(
|
|
71
|
+
rule_id: str,
|
|
72
|
+
file_path: str,
|
|
73
|
+
code_snippet: str,
|
|
74
|
+
sink_signature: Optional[str] = None,
|
|
75
|
+
framework_marker: Optional[str] = None,
|
|
76
|
+
root_path: Optional[Path] = None,
|
|
77
|
+
taint_depth: Optional[int] = None,
|
|
78
|
+
sanitizer_present: bool = False,
|
|
79
|
+
) -> FindingFingerprint:
|
|
80
|
+
"""
|
|
81
|
+
Generates a stable FindingFingerprint.
|
|
82
|
+
"""
|
|
83
|
+
# Normalize file path relative to root
|
|
84
|
+
norm_path = file_path.replace("\\", "/").lstrip("./")
|
|
85
|
+
if root_path:
|
|
86
|
+
try:
|
|
87
|
+
norm_path = str(Path(file_path).resolve().relative_to(root_path.resolve())).replace("\\", "/")
|
|
88
|
+
except Exception:
|
|
89
|
+
norm_path = file_path.replace("\\", "/").lstrip("./")
|
|
90
|
+
|
|
91
|
+
# Compute normalized region hash
|
|
92
|
+
norm_code = normalize_code_snippet(code_snippet)
|
|
93
|
+
region_hash = hashlib.sha256(norm_code.encode("utf-8")).hexdigest()[:16]
|
|
94
|
+
|
|
95
|
+
return FindingFingerprint(
|
|
96
|
+
rule_id=rule_id,
|
|
97
|
+
normalized_path=norm_path,
|
|
98
|
+
region_hash=region_hash,
|
|
99
|
+
sink_signature=sink_signature,
|
|
100
|
+
framework_marker=framework_marker,
|
|
101
|
+
taint_depth=taint_depth,
|
|
102
|
+
sanitizer_present=sanitizer_present,
|
|
103
|
+
)
|
|
104
|
+
|