torusguard 1.3.5 → 1.3.6
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 +35 -35
- package/.torusguard/runs/report-latest.html +2685 -303
- package/.torusguard/runs/results-latest.sarif +20 -0
- package/.torusguard/runs/run-20260918-104221-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260918-104221-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260918-104221-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260918-104221-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260918-104236-audit/findings.json +2314 -0
- package/.torusguard/runs/run-20260918-104236-audit/findings.md +960 -0
- package/.torusguard/runs/run-20260918-104236-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260918-104236-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260918-105723-audit/findings.json +2382 -0
- package/.torusguard/runs/run-20260918-105723-audit/findings.md +960 -0
- package/.torusguard/runs/run-20260918-105723-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260918-105723-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260918-105731-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260918-105731-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260918-105731-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260918-105731-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260918-105940-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260918-105940-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260918-105940-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260918-105940-audit/summary.md +5 -0
- package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
- package/.torusguard/scripts/apply_runner.py +11 -5
- package/.torusguard/scripts/audit_runner.py +222 -15
- package/.torusguard/scripts/harden_runner.py +3 -3
- package/.torusguard/scripts/html_reporter.py +2668 -648
- package/.torusguard/scripts/recheck_runner.py +3 -3
- package/.torusguard/scripts/recipes_runner.py +3 -3
- package/.torusguard/scripts/report_sync.py +370 -74
- package/.torusguard/scripts/sarif_exporter.py +48 -8
- package/.torusguard/scripts/stack_detect.py +44 -2
- package/.torusguard/scripts/term_ui.py +1 -1
- package/.torusguard/skills/torusguard/SKILL.md +75 -72
- package/.torusguard/skills/torusguard-apply/SKILL.md +84 -80
- package/.torusguard/skills/torusguard-audit/SKILL.md +87 -83
- package/.torusguard/skills/torusguard-authorize/SKILL.md +7 -2
- package/.torusguard/skills/torusguard-exploit-check/SKILL.md +7 -2
- package/.torusguard/skills/torusguard-full/SKILL.md +7 -2
- package/.torusguard/skills/torusguard-harden/SKILL.md +98 -94
- package/.torusguard/skills/torusguard-init/SKILL.md +68 -64
- package/.torusguard/skills/torusguard-recheck/SKILL.md +81 -77
- package/.torusguard/skills/torusguard-report/SKILL.md +93 -72
- package/.torusguard/skills/torusguard-status/SKILL.md +62 -58
- package/.torusguard/skills/torusguard-verify/SKILL.md +7 -2
- package/.torusguard/skills/torusguard-web-validate/SKILL.md +7 -2
- package/.torusguard/workflows/apply.md +1 -1
- package/.torusguard/workflows/audit.md +1 -1
- package/.torusguard/workflows/authorize.md +1 -1
- package/.torusguard/workflows/exploit-check.md +1 -1
- package/.torusguard/workflows/harden.md +1 -1
- package/.torusguard/workflows/init.md +1 -1
- package/.torusguard/workflows/memory.md +1 -1
- package/.torusguard/workflows/recheck.md +1 -1
- package/.torusguard/workflows/report.md +1 -1
- package/.torusguard/workflows/status.md +1 -1
- package/.torusguard/workflows/verify.md +1 -1
- package/.torusguard/workflows/web-validate.md +1 -1
- package/README.md +14 -8
- package/bin/torusguard.js +56 -20
- package/package.json +1 -1
- package/skills/torusguard/SKILL.md +1 -1
- package/skills/torusguard/bootstrap.py +7 -2
- package/skills/torusguard/payload/.manifest.json +35 -35
- package/skills/torusguard/payload/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/finding_scorer.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/memory_engine.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/recheck_runner.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/rules_sync.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/run_manager.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/stack_detect.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/apply_runner.py +11 -5
- package/skills/torusguard/payload/scripts/audit_runner.py +222 -15
- package/skills/torusguard/payload/scripts/harden_runner.py +3 -3
- package/skills/torusguard/payload/scripts/html_reporter.py +2668 -648
- package/skills/torusguard/payload/scripts/recheck_runner.py +3 -3
- package/skills/torusguard/payload/scripts/recipes_runner.py +3 -3
- package/skills/torusguard/payload/scripts/report_sync.py +370 -74
- package/skills/torusguard/payload/scripts/sarif_exporter.py +48 -8
- package/skills/torusguard/payload/scripts/stack_detect.py +44 -2
- package/skills/torusguard/payload/scripts/term_ui.py +1 -1
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +40 -23
- package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +1 -1
- package/skills/torusguard/payload/workflows/apply.md +1 -1
- package/skills/torusguard/payload/workflows/audit.md +1 -1
- package/skills/torusguard/payload/workflows/authorize.md +1 -1
- package/skills/torusguard/payload/workflows/exploit-check.md +1 -1
- package/skills/torusguard/payload/workflows/harden.md +1 -1
- package/skills/torusguard/payload/workflows/init.md +1 -1
- package/skills/torusguard/payload/workflows/memory.md +1 -1
- package/skills/torusguard/payload/workflows/recheck.md +1 -1
- package/skills/torusguard/payload/workflows/report.md +1 -1
- package/skills/torusguard/payload/workflows/status.md +1 -1
- package/skills/torusguard/payload/workflows/verify.md +1 -1
- package/skills/torusguard/payload/workflows/web-validate.md +1 -1
- package/skills/torusguard-apply/SKILL.md +1 -1
- package/skills/torusguard-audit/SKILL.md +1 -1
- package/skills/torusguard-authorize/SKILL.md +1 -1
- package/skills/torusguard-exploit-check/SKILL.md +1 -1
- package/skills/torusguard-full/SKILL.md +1 -1
- package/skills/torusguard-harden/SKILL.md +1 -1
- package/skills/torusguard-init/SKILL.md +1 -1
- package/skills/torusguard-recheck/SKILL.md +1 -1
- package/skills/torusguard-report/SKILL.md +40 -23
- package/skills/torusguard-status/SKILL.md +1 -1
- package/skills/torusguard-verify/SKILL.md +1 -1
- package/skills/torusguard-web-validate/SKILL.md +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>TorusGuard Security
|
|
6
|
+
<title>TorusGuard Security Report · TorusGuard</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
--bg-dark: #0d1117;
|
|
@@ -16,7 +16,17 @@
|
|
|
16
16
|
--accent-green: #3fb950;
|
|
17
17
|
--accent-yellow: #d29922;
|
|
18
18
|
--accent-red: #f85149;
|
|
19
|
-
--
|
|
19
|
+
--accent-orange: #db6d28;
|
|
20
|
+
--accent-purple: #bc8cff;
|
|
21
|
+
--font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
22
|
+
}
|
|
23
|
+
body.light-theme {
|
|
24
|
+
--bg-dark: #f6f8fa;
|
|
25
|
+
--bg-surface: #ffffff;
|
|
26
|
+
--bg-card: #f0f3f6;
|
|
27
|
+
--border-subtle: #d0d7de;
|
|
28
|
+
--text-main: #1f2328;
|
|
29
|
+
--text-muted: #656d76;
|
|
20
30
|
}
|
|
21
31
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
22
32
|
body {
|
|
@@ -25,8 +35,11 @@
|
|
|
25
35
|
font-family: var(--font-stack);
|
|
26
36
|
line-height: 1.5;
|
|
27
37
|
padding: 24px;
|
|
38
|
+
transition: background 0.2s ease, color 0.2s ease;
|
|
28
39
|
}
|
|
29
|
-
.container { max-width:
|
|
40
|
+
.container { max-width: 1240px; margin: 0 auto; }
|
|
41
|
+
|
|
42
|
+
/* Header */
|
|
30
43
|
header {
|
|
31
44
|
display: flex;
|
|
32
45
|
justify-content: space-between;
|
|
@@ -34,42 +47,63 @@
|
|
|
34
47
|
padding-bottom: 20px;
|
|
35
48
|
border-bottom: 1px solid var(--border-subtle);
|
|
36
49
|
margin-bottom: 24px;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
gap: 12px;
|
|
37
52
|
}
|
|
38
53
|
.logo-group h1 {
|
|
39
|
-
font-size:
|
|
54
|
+
font-size: 22px;
|
|
40
55
|
font-weight: 700;
|
|
41
56
|
display: flex;
|
|
42
57
|
align-items: center;
|
|
43
58
|
gap: 8px;
|
|
44
59
|
}
|
|
45
|
-
.shield-icon { font-size:
|
|
46
|
-
.project-meta { color: var(--text-muted); font-size:
|
|
60
|
+
.shield-icon { font-size: 24px; }
|
|
61
|
+
.project-meta { color: var(--text-muted); font-size: 13px; margin-top: 4px; }
|
|
62
|
+
.header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
47
63
|
.status-badge {
|
|
48
|
-
background: rgba(63, 185, 80, 0.15);
|
|
49
|
-
border: 1px solid var(--accent-green);
|
|
50
|
-
color: var(--accent-green);
|
|
51
64
|
padding: 6px 14px;
|
|
52
65
|
border-radius: 20px;
|
|
53
|
-
font-size:
|
|
66
|
+
font-size: 12px;
|
|
54
67
|
font-weight: 600;
|
|
55
68
|
}
|
|
56
|
-
.
|
|
69
|
+
.status-clean { background: rgba(63, 185, 80, 0.15); border: 1px solid var(--accent-green); color: var(--accent-green); }
|
|
70
|
+
.status-elevated { background: rgba(88, 166, 255, 0.15); border: 1px solid var(--accent-blue); color: var(--accent-blue); }
|
|
71
|
+
.status-warn { background: rgba(210, 153, 34, 0.15); border: 1px solid var(--accent-yellow); color: var(--accent-yellow); }
|
|
72
|
+
.status-critical { background: rgba(248, 81, 73, 0.15); border: 1px solid var(--accent-red); color: var(--accent-red); }
|
|
73
|
+
|
|
74
|
+
.btn {
|
|
75
|
+
background: var(--bg-card);
|
|
76
|
+
border: 1px solid var(--border-subtle);
|
|
77
|
+
color: var(--text-main);
|
|
78
|
+
padding: 6px 12px;
|
|
79
|
+
border-radius: 6px;
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 6px;
|
|
86
|
+
transition: background 0.2s, border-color 0.2s;
|
|
87
|
+
}
|
|
88
|
+
.btn:hover { background: #30363d; border-color: var(--text-muted); }
|
|
89
|
+
body.light-theme .btn:hover { background: #e1e4e8; }
|
|
90
|
+
.btn-sm { padding: 3px 8px; font-size: 11px; }
|
|
91
|
+
|
|
92
|
+
/* KPI Grid */
|
|
93
|
+
.grid-kpi {
|
|
57
94
|
display: grid;
|
|
58
|
-
grid-template-columns:
|
|
59
|
-
gap:
|
|
95
|
+
grid-template-columns: 220px repeat(4, 1fr);
|
|
96
|
+
gap: 14px;
|
|
60
97
|
margin-bottom: 24px;
|
|
61
98
|
}
|
|
62
|
-
@media (max-width:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@media (max-width: 600px) {
|
|
66
|
-
.grid-top { grid-template-columns: 1fr; }
|
|
67
|
-
}
|
|
99
|
+
@media (max-width: 950px) { .grid-kpi { grid-template-columns: 1fr 1fr; } }
|
|
100
|
+
@media (max-width: 600px) { .grid-kpi { grid-template-columns: 1fr; } }
|
|
101
|
+
|
|
68
102
|
.card {
|
|
69
103
|
background: var(--bg-surface);
|
|
70
104
|
border: 1px solid var(--border-subtle);
|
|
71
105
|
border-radius: 8px;
|
|
72
|
-
padding:
|
|
106
|
+
padding: 18px;
|
|
73
107
|
}
|
|
74
108
|
.gauge-card {
|
|
75
109
|
display: flex;
|
|
@@ -78,379 +112,2727 @@
|
|
|
78
112
|
justify-content: center;
|
|
79
113
|
text-align: center;
|
|
80
114
|
}
|
|
81
|
-
.gauge-svg { width:
|
|
115
|
+
.gauge-svg { width: 110px; height: 110px; transform: rotate(-90deg); filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.3)); }
|
|
82
116
|
.gauge-bg { fill: none; stroke: var(--bg-card); stroke-width: 8; }
|
|
83
117
|
.gauge-fill {
|
|
84
118
|
fill: none;
|
|
85
|
-
stroke: #f85149;
|
|
86
119
|
stroke-width: 8;
|
|
87
120
|
stroke-linecap: round;
|
|
88
121
|
stroke-dasharray: 251.2;
|
|
89
|
-
stroke-dashoffset:
|
|
90
|
-
transition: stroke-dashoffset 0.
|
|
122
|
+
stroke-dashoffset: 251.2;
|
|
123
|
+
transition: stroke-dashoffset 0.9s cubic-bezier(0.4, 0, 0.2, 1);
|
|
91
124
|
}
|
|
92
125
|
.gauge-value {
|
|
93
126
|
position: absolute;
|
|
94
|
-
font-size:
|
|
127
|
+
font-size: 30px;
|
|
95
128
|
font-weight: 700;
|
|
96
129
|
color: var(--text-main);
|
|
130
|
+
letter-spacing: -0.5px;
|
|
97
131
|
}
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
.
|
|
101
|
-
.
|
|
102
|
-
|
|
132
|
+
.sev-card { text-align: center; cursor: pointer; transition: transform 0.15s, border-color 0.2s; }
|
|
133
|
+
.sev-card:hover { transform: translateY(-2px); border-color: var(--accent-blue); }
|
|
134
|
+
.sev-card .sev-count { font-size: 34px; font-weight: 800; }
|
|
135
|
+
.sev-card .sev-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-top: 4px; }
|
|
136
|
+
|
|
137
|
+
/* What-If Posture Simulator Card */
|
|
138
|
+
.simulator-card {
|
|
139
|
+
margin-bottom: 24px;
|
|
140
|
+
background: var(--bg-surface);
|
|
141
|
+
border: 1px solid var(--border-subtle);
|
|
142
|
+
border-left: 4px solid var(--accent-blue);
|
|
103
143
|
display: flex;
|
|
104
144
|
justify-content: space-between;
|
|
105
|
-
|
|
106
|
-
|
|
145
|
+
align-items: center;
|
|
146
|
+
flex-wrap: wrap;
|
|
147
|
+
gap: 12px;
|
|
148
|
+
padding: 14px 18px;
|
|
149
|
+
}
|
|
150
|
+
.simulator-clean { border-left-color: var(--accent-green); }
|
|
151
|
+
.sim-stats { display: flex; align-items: center; gap: 10px; font-size: 13px; }
|
|
152
|
+
.sim-actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
153
|
+
.sim-checkbox-label {
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 4px;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
font-size: 11px;
|
|
159
|
+
color: var(--text-muted);
|
|
160
|
+
}
|
|
161
|
+
.sim-fix-chk { cursor: pointer; accent-color: var(--accent-blue); }
|
|
162
|
+
|
|
163
|
+
/* Invariant Matrix */
|
|
164
|
+
.defended-section {
|
|
165
|
+
background: var(--bg-surface);
|
|
166
|
+
border: 1px solid var(--border-subtle);
|
|
167
|
+
border-radius: 8px;
|
|
168
|
+
padding: 18px;
|
|
169
|
+
margin-bottom: 24px;
|
|
107
170
|
}
|
|
108
|
-
.
|
|
171
|
+
.defended-header {
|
|
109
172
|
display: flex;
|
|
110
|
-
|
|
173
|
+
justify-content: space-between;
|
|
111
174
|
align-items: center;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
position: relative;
|
|
115
|
-
z-index: 2;
|
|
175
|
+
margin-bottom: 14px;
|
|
176
|
+
cursor: pointer;
|
|
116
177
|
}
|
|
117
|
-
.
|
|
118
|
-
width: 32px;
|
|
119
|
-
height: 32px;
|
|
120
|
-
border-radius: 50%;
|
|
178
|
+
.coverage-bar-container {
|
|
121
179
|
background: var(--bg-card);
|
|
122
|
-
border:
|
|
180
|
+
border: 1px solid var(--border-subtle);
|
|
181
|
+
border-radius: 6px;
|
|
182
|
+
padding: 10px 14px;
|
|
183
|
+
margin-bottom: 16px;
|
|
184
|
+
}
|
|
185
|
+
.coverage-stats {
|
|
123
186
|
display: flex;
|
|
187
|
+
justify-content: space-between;
|
|
124
188
|
align-items: center;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
color: var(--accent-blue);
|
|
129
|
-
margin-bottom: 8px;
|
|
189
|
+
font-size: 12px;
|
|
190
|
+
color: var(--text-muted);
|
|
191
|
+
margin-bottom: 6px;
|
|
130
192
|
}
|
|
131
|
-
.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
.
|
|
193
|
+
.coverage-progress-bg {
|
|
194
|
+
height: 8px;
|
|
195
|
+
background: rgba(255, 255, 255, 0.05);
|
|
196
|
+
border-radius: 4px;
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
}
|
|
199
|
+
.coverage-progress-fill {
|
|
200
|
+
height: 100%;
|
|
201
|
+
background: var(--accent-green);
|
|
202
|
+
border-radius: 4px;
|
|
203
|
+
transition: width 0.6s ease;
|
|
204
|
+
}
|
|
205
|
+
.inv-search-bar {
|
|
206
|
+
margin-bottom: 16px;
|
|
144
207
|
display: flex;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
margin-top: 10px;
|
|
208
|
+
gap: 10px;
|
|
209
|
+
align-items: center;
|
|
148
210
|
}
|
|
149
|
-
.
|
|
211
|
+
.layers-container {
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
gap: 12px;
|
|
215
|
+
}
|
|
216
|
+
.layer-card {
|
|
150
217
|
background: var(--bg-card);
|
|
151
218
|
border: 1px solid var(--border-subtle);
|
|
152
|
-
border-radius:
|
|
153
|
-
padding:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
219
|
+
border-radius: 6px;
|
|
220
|
+
padding: 12px 14px;
|
|
221
|
+
}
|
|
222
|
+
.layer-header {
|
|
223
|
+
display: flex;
|
|
224
|
+
justify-content: space-between;
|
|
225
|
+
align-items: center;
|
|
226
|
+
margin-bottom: 10px;
|
|
227
|
+
}
|
|
228
|
+
.layer-title-group { display: flex; align-items: center; gap: 8px; }
|
|
229
|
+
.layer-icon { font-size: 16px; }
|
|
230
|
+
.layer-name { font-size: 13px; font-weight: 700; color: var(--text-main); }
|
|
231
|
+
.layer-desc { font-size: 11px; color: var(--text-muted); }
|
|
232
|
+
.layer-badge {
|
|
233
|
+
font-size: 11px;
|
|
234
|
+
font-weight: 600;
|
|
235
|
+
padding: 2px 8px;
|
|
236
|
+
border-radius: 10px;
|
|
237
|
+
}
|
|
238
|
+
.layer-chips-grid {
|
|
239
|
+
display: flex;
|
|
240
|
+
flex-wrap: wrap;
|
|
241
|
+
gap: 6px;
|
|
242
|
+
}
|
|
243
|
+
.inv-chip {
|
|
244
|
+
background: var(--bg-surface);
|
|
245
|
+
border: 1px solid var(--border-subtle);
|
|
246
|
+
border-radius: 4px;
|
|
247
|
+
padding: 4px 8px;
|
|
248
|
+
font-size: 11px;
|
|
157
249
|
display: inline-flex;
|
|
158
250
|
align-items: center;
|
|
159
251
|
gap: 6px;
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
transition: all 0.15s ease;
|
|
160
254
|
}
|
|
161
|
-
.chip
|
|
255
|
+
.inv-chip:hover {
|
|
162
256
|
border-color: var(--accent-blue);
|
|
163
|
-
|
|
164
|
-
background: rgba(88, 166, 255, 0.1);
|
|
257
|
+
transform: translateY(-1px);
|
|
165
258
|
}
|
|
166
|
-
.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
259
|
+
.inv-chip-safe { border-left: 2px solid var(--accent-green); }
|
|
260
|
+
.inv-chip-violated { border-left: 2px solid var(--accent-red); background: rgba(248, 81, 73, 0.08); }
|
|
261
|
+
.inv-dot { width: 6px; height: 6px; border-radius: 50%; }
|
|
262
|
+
.inv-code { font-family: monospace; font-weight: 600; color: var(--accent-blue); font-size: 11px; }
|
|
263
|
+
.inv-title { color: var(--text-main); max-width: 140px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
264
|
+
|
|
265
|
+
/* Modal / Drawer for Invariant Inspection */
|
|
266
|
+
.modal-backdrop {
|
|
267
|
+
position: fixed;
|
|
268
|
+
top: 0;
|
|
269
|
+
left: 0;
|
|
270
|
+
width: 100vw;
|
|
271
|
+
height: 100vh;
|
|
272
|
+
background: rgba(0, 0, 0, 0.7);
|
|
273
|
+
backdrop-filter: blur(2px);
|
|
274
|
+
z-index: 9999;
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
padding: 20px;
|
|
171
279
|
}
|
|
172
|
-
|
|
173
|
-
|
|
280
|
+
.modal-card {
|
|
281
|
+
background: var(--bg-surface);
|
|
282
|
+
border: 1px solid var(--border-subtle);
|
|
283
|
+
border-radius: 8px;
|
|
284
|
+
max-width: 600px;
|
|
285
|
+
width: 100%;
|
|
286
|
+
padding: 22px;
|
|
287
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
174
288
|
}
|
|
175
|
-
.
|
|
289
|
+
.modal-header {
|
|
290
|
+
display: flex;
|
|
291
|
+
justify-content: space-between;
|
|
292
|
+
align-items: flex-start;
|
|
293
|
+
margin-bottom: 14px;
|
|
294
|
+
}
|
|
295
|
+
.modal-close-btn {
|
|
296
|
+
background: transparent;
|
|
297
|
+
border: none;
|
|
298
|
+
color: var(--text-muted);
|
|
176
299
|
font-size: 18px;
|
|
177
|
-
|
|
178
|
-
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
}
|
|
302
|
+
.modal-close-btn:hover { color: var(--text-main); }
|
|
303
|
+
.modal-body { font-size: 13px; color: var(--text-main); display: flex; flex-direction: column; gap: 12px; }
|
|
304
|
+
|
|
305
|
+
/* Filter & Search Bar */
|
|
306
|
+
.filter-bar {
|
|
179
307
|
display: flex;
|
|
308
|
+
justify-content: space-between;
|
|
180
309
|
align-items: center;
|
|
181
|
-
|
|
310
|
+
margin-bottom: 8px;
|
|
311
|
+
flex-wrap: wrap;
|
|
312
|
+
gap: 10px;
|
|
182
313
|
}
|
|
183
|
-
.
|
|
184
|
-
|
|
314
|
+
.filter-pills { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
|
315
|
+
.pill-group-label {
|
|
316
|
+
font-size: 11px;
|
|
317
|
+
font-weight: 600;
|
|
318
|
+
text-transform: uppercase;
|
|
319
|
+
letter-spacing: 0.5px;
|
|
320
|
+
color: var(--text-muted);
|
|
321
|
+
margin-right: 2px;
|
|
322
|
+
}
|
|
323
|
+
.pill {
|
|
324
|
+
background: var(--bg-card);
|
|
325
|
+
border: 1px solid var(--border-subtle);
|
|
326
|
+
color: var(--text-muted);
|
|
327
|
+
padding: 4px 10px;
|
|
328
|
+
border-radius: 14px;
|
|
329
|
+
font-size: 12px;
|
|
330
|
+
font-weight: 500;
|
|
331
|
+
cursor: pointer;
|
|
332
|
+
transition: all 0.15s ease;
|
|
333
|
+
user-select: none;
|
|
334
|
+
}
|
|
335
|
+
.pill:hover { color: var(--text-main); border-color: var(--text-muted); }
|
|
336
|
+
.pill.active {
|
|
337
|
+
background: var(--accent-blue);
|
|
338
|
+
border-color: var(--accent-blue);
|
|
339
|
+
color: #fff;
|
|
340
|
+
}
|
|
341
|
+
.dir-filter-chip {
|
|
342
|
+
display: none;
|
|
343
|
+
background: rgba(88, 166, 255, 0.15);
|
|
344
|
+
border: 1px solid var(--accent-blue);
|
|
345
|
+
color: var(--accent-blue);
|
|
346
|
+
font-size: 11px;
|
|
185
347
|
padding: 3px 8px;
|
|
186
348
|
border-radius: 12px;
|
|
349
|
+
cursor: pointer;
|
|
350
|
+
}
|
|
351
|
+
.btn-reset-filters {
|
|
352
|
+
display: none;
|
|
353
|
+
background: rgba(248, 81, 73, 0.12);
|
|
354
|
+
border: 1px solid rgba(248, 81, 73, 0.35);
|
|
355
|
+
color: var(--accent-red);
|
|
187
356
|
font-size: 11px;
|
|
188
357
|
font-weight: 600;
|
|
189
|
-
|
|
358
|
+
padding: 3px 8px;
|
|
359
|
+
border-radius: 12px;
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
margin-left: 4px;
|
|
362
|
+
transition: all 0.15s;
|
|
190
363
|
}
|
|
191
|
-
.
|
|
192
|
-
.
|
|
193
|
-
|
|
364
|
+
.btn-reset-filters:hover { background: rgba(248, 81, 73, 0.25); }
|
|
365
|
+
.search-wrapper {
|
|
366
|
+
position: relative;
|
|
367
|
+
display: inline-flex;
|
|
368
|
+
align-items: center;
|
|
369
|
+
}
|
|
370
|
+
.search-input {
|
|
194
371
|
background: var(--bg-card);
|
|
195
372
|
border: 1px solid var(--border-subtle);
|
|
373
|
+
color: var(--text-main);
|
|
374
|
+
padding: 7px 28px 7px 12px;
|
|
196
375
|
border-radius: 6px;
|
|
197
|
-
|
|
198
|
-
|
|
376
|
+
font-size: 13px;
|
|
377
|
+
width: 280px;
|
|
378
|
+
outline: none;
|
|
379
|
+
transition: border-color 0.15s;
|
|
380
|
+
}
|
|
381
|
+
.search-input:focus { border-color: var(--accent-blue); }
|
|
382
|
+
.search-clear-btn {
|
|
383
|
+
position: absolute;
|
|
384
|
+
right: 8px;
|
|
385
|
+
background: none;
|
|
386
|
+
border: none;
|
|
387
|
+
color: var(--text-muted);
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
font-size: 14px;
|
|
390
|
+
line-height: 1;
|
|
391
|
+
padding: 2px 4px;
|
|
392
|
+
display: none;
|
|
393
|
+
}
|
|
394
|
+
.search-clear-btn:hover { color: var(--text-main); }
|
|
395
|
+
.filter-stats-bar {
|
|
396
|
+
font-size: 12px;
|
|
397
|
+
color: var(--text-muted);
|
|
398
|
+
margin-bottom: 14px;
|
|
399
|
+
padding-left: 2px;
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
justify-content: space-between;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* Table & Drawers */
|
|
406
|
+
.table-container {
|
|
407
|
+
background: var(--bg-surface);
|
|
408
|
+
border: 1px solid var(--border-subtle);
|
|
409
|
+
border-radius: 8px;
|
|
410
|
+
overflow: hidden;
|
|
411
|
+
margin-bottom: 24px;
|
|
412
|
+
}
|
|
413
|
+
table { width: 100%; border-collapse: collapse; text-align: left; font-size: 13px; }
|
|
414
|
+
th {
|
|
415
|
+
background: var(--bg-card);
|
|
416
|
+
padding: 10px 14px;
|
|
417
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
418
|
+
font-weight: 600;
|
|
419
|
+
color: var(--text-muted);
|
|
420
|
+
}
|
|
421
|
+
td { padding: 12px 14px; border-bottom: 1px solid var(--border-subtle); vertical-align: middle; }
|
|
422
|
+
tr.finding-row { cursor: pointer; transition: background 0.1s; }
|
|
423
|
+
tr.finding-row:hover { background: rgba(255, 255, 255, 0.02); }
|
|
424
|
+
body.light-theme tr.finding-row:hover { background: rgba(0, 0, 0, 0.02); }
|
|
425
|
+
tr.finding-row.expanded { background: rgba(88, 166, 255, 0.05); }
|
|
426
|
+
.drawer-chevron { display: inline-block; transition: transform 0.2s; font-size: 14px; color: var(--text-muted); }
|
|
427
|
+
tr.finding-row.expanded .drawer-chevron { transform: rotate(90deg); color: var(--accent-blue); }
|
|
428
|
+
|
|
429
|
+
/* Badges */
|
|
430
|
+
.badge {
|
|
431
|
+
display: inline-block;
|
|
432
|
+
padding: 2px 8px;
|
|
433
|
+
border-radius: 12px;
|
|
434
|
+
font-size: 11px;
|
|
435
|
+
font-weight: 600;
|
|
436
|
+
text-transform: uppercase;
|
|
437
|
+
letter-spacing: 0.3px;
|
|
199
438
|
}
|
|
200
|
-
.
|
|
201
|
-
.
|
|
202
|
-
.
|
|
203
|
-
.
|
|
439
|
+
.badge-critical { background: rgba(248, 81, 73, 0.2); color: var(--accent-red); border: 1px solid var(--accent-red); }
|
|
440
|
+
.badge-high { background: rgba(219, 109, 40, 0.2); color: var(--accent-orange); border: 1px solid var(--accent-orange); }
|
|
441
|
+
.badge-medium { background: rgba(210, 153, 34, 0.2); color: var(--accent-yellow); border: 1px solid var(--accent-yellow); }
|
|
442
|
+
.badge-success { background: rgba(63, 185, 80, 0.2); color: var(--accent-green); border: 1px solid var(--accent-green); }
|
|
443
|
+
.badge-primary { background: rgba(88, 166, 255, 0.2); color: var(--accent-blue); border: 1px solid var(--accent-blue); }
|
|
444
|
+
.badge-canary { background: rgba(188, 140, 255, 0.2); color: var(--accent-purple); border: 1px solid var(--accent-purple); }
|
|
445
|
+
|
|
446
|
+
.rule-id { color: var(--accent-blue); font-family: monospace; font-weight: 600; }
|
|
447
|
+
.conf-score { color: var(--text-muted); font-size: 12px; }
|
|
448
|
+
|
|
449
|
+
/* Drawer Content */
|
|
450
|
+
.drawer-content {
|
|
451
|
+
padding: 16px;
|
|
204
452
|
background: var(--bg-dark);
|
|
453
|
+
border-left: 3px solid var(--accent-blue);
|
|
454
|
+
margin: 8px 12px;
|
|
455
|
+
border-radius: 4px;
|
|
456
|
+
}
|
|
457
|
+
body.light-theme .drawer-content { background: var(--bg-card); }
|
|
458
|
+
.drawer-header {
|
|
459
|
+
display: flex;
|
|
460
|
+
justify-content: space-between;
|
|
461
|
+
color: var(--text-muted);
|
|
462
|
+
font-size: 12px;
|
|
463
|
+
margin-bottom: 8px;
|
|
464
|
+
}
|
|
465
|
+
.drawer-desc { font-size: 13px; color: var(--text-main); margin-bottom: 12px; }
|
|
466
|
+
.code-preview {
|
|
467
|
+
background: #000;
|
|
205
468
|
border: 1px solid var(--border-subtle);
|
|
206
469
|
border-radius: 4px;
|
|
207
|
-
padding:
|
|
470
|
+
padding: 10px;
|
|
208
471
|
font-family: monospace;
|
|
209
472
|
font-size: 12px;
|
|
210
473
|
overflow-x: auto;
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
474
|
+
margin-bottom: 12px;
|
|
475
|
+
}
|
|
476
|
+
body.light-theme .code-preview { background: #f6f8fa; border-color: var(--border-subtle); }
|
|
477
|
+
.code-preview code { color: #7ee787; font-family: monospace; }
|
|
478
|
+
body.light-theme .code-preview code { color: #1f2328; }
|
|
479
|
+
.drawer-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
480
|
+
.cmd-box {
|
|
481
|
+
background: var(--bg-card);
|
|
482
|
+
border: 1px solid var(--border-subtle);
|
|
483
|
+
padding: 4px 8px;
|
|
484
|
+
border-radius: 4px;
|
|
485
|
+
display: flex;
|
|
486
|
+
align-items: center;
|
|
487
|
+
gap: 8px;
|
|
223
488
|
font-size: 12px;
|
|
224
|
-
|
|
225
|
-
padding-top: 20px;
|
|
489
|
+
font-family: monospace;
|
|
226
490
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<div class="status-badge">✓ Local Governance Active</div>
|
|
237
|
-
</header>
|
|
491
|
+
.btn-copy {
|
|
492
|
+
background: transparent;
|
|
493
|
+
border: none;
|
|
494
|
+
color: var(--accent-blue);
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
font-size: 11px;
|
|
497
|
+
}
|
|
498
|
+
.btn-copy:hover { text-decoration: underline; }
|
|
499
|
+
.btn-copy.copied { color: var(--accent-green) !important; }
|
|
238
500
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
501
|
+
/* Section Titles */
|
|
502
|
+
.section-title {
|
|
503
|
+
font-size: 14px;
|
|
504
|
+
font-weight: 700;
|
|
505
|
+
text-transform: uppercase;
|
|
506
|
+
letter-spacing: 0.5px;
|
|
507
|
+
color: var(--text-muted);
|
|
508
|
+
margin-bottom: 12px;
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: center;
|
|
511
|
+
gap: 8px;
|
|
512
|
+
}
|
|
251
513
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
514
|
+
/* OWASP Grid */
|
|
515
|
+
.grid-owasp {
|
|
516
|
+
display: grid;
|
|
517
|
+
grid-template-columns: repeat(4, 1fr);
|
|
518
|
+
gap: 12px;
|
|
519
|
+
margin-bottom: 24px;
|
|
520
|
+
}
|
|
521
|
+
@media (max-width: 950px) { .grid-owasp { grid-template-columns: repeat(2, 1fr); } }
|
|
522
|
+
@media (max-width: 550px) { .grid-owasp { grid-template-columns: 1fr; } }
|
|
523
|
+
.owasp-card {
|
|
524
|
+
background: var(--bg-surface);
|
|
525
|
+
border: 1px solid var(--border-subtle);
|
|
526
|
+
border-radius: 6px;
|
|
527
|
+
padding: 12px;
|
|
528
|
+
}
|
|
529
|
+
.owasp-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
|
530
|
+
.owasp-title { font-size: 11px; font-weight: 600; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
531
|
+
.owasp-badge { font-size: 10px; font-weight: 700; border: 1px solid; border-radius: 8px; padding: 1px 6px; }
|
|
532
|
+
.owasp-bar-bg { width: 100%; height: 6px; background: var(--bg-card); border-radius: 3px; overflow: hidden; }
|
|
533
|
+
.owasp-bar { height: 100%; border-radius: 3px; }
|
|
257
534
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
535
|
+
/* Compliance Framework Grid */
|
|
536
|
+
.grid-compliance {
|
|
537
|
+
display: grid;
|
|
538
|
+
grid-template-columns: repeat(3, 1fr);
|
|
539
|
+
gap: 14px;
|
|
540
|
+
margin-bottom: 24px;
|
|
541
|
+
}
|
|
542
|
+
@media (max-width: 900px) { .grid-compliance { grid-template-columns: 1fr; } }
|
|
543
|
+
.comp-card { padding: 14px; }
|
|
544
|
+
.comp-card-header {
|
|
545
|
+
display: flex;
|
|
546
|
+
justify-content: space-between;
|
|
547
|
+
align-items: flex-start;
|
|
548
|
+
margin-bottom: 12px;
|
|
549
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
550
|
+
padding-bottom: 10px;
|
|
551
|
+
}
|
|
552
|
+
.comp-title { font-size: 13px; font-weight: 700; color: var(--text-main); }
|
|
553
|
+
.comp-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
|
554
|
+
.comp-ctrl-list { display: flex; flex-direction: column; gap: 8px; }
|
|
555
|
+
.comp-ctrl-row { display: flex; justify-content: space-between; align-items: center; font-size: 11px; }
|
|
556
|
+
.comp-ctrl-code { font-weight: 600; color: var(--text-main); }
|
|
557
|
+
.comp-ctrl-fams { color: var(--text-muted); font-size: 10px; }
|
|
263
558
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
559
|
+
/* Dual Grids */
|
|
560
|
+
.grid-bottom {
|
|
561
|
+
display: grid;
|
|
562
|
+
grid-template-columns: 1fr 1fr;
|
|
563
|
+
gap: 20px;
|
|
564
|
+
margin-bottom: 24px;
|
|
565
|
+
}
|
|
566
|
+
@media (max-width: 850px) { .grid-bottom { grid-template-columns: 1fr; } }
|
|
270
567
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
<span class="chip chip-primary">● TypeScript</span>
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
568
|
+
.cluster-item {
|
|
569
|
+
background: var(--bg-card);
|
|
570
|
+
border: 1px solid var(--border-subtle);
|
|
571
|
+
border-radius: 6px;
|
|
572
|
+
padding: 10px 12px;
|
|
573
|
+
margin-bottom: 8px;
|
|
574
|
+
}
|
|
575
|
+
.cluster-header { display: flex; justify-content: space-between; align-items: center; font-size: 13px; }
|
|
576
|
+
.cluster-sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
|
283
577
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
<div class="step-cmd">audit · /audit</div>
|
|
293
|
-
</div>
|
|
294
|
-
<div class="pipeline-step">
|
|
295
|
-
<div class="step-circle active">2</div>
|
|
296
|
-
<div class="step-name">Score</div>
|
|
297
|
-
<div class="step-cmd">0-100 Score</div>
|
|
298
|
-
</div>
|
|
299
|
-
<div class="pipeline-step">
|
|
300
|
-
<div class="step-circle active">3</div>
|
|
301
|
-
<div class="step-name">Harden</div>
|
|
302
|
-
<div class="step-cmd">harden · /harden</div>
|
|
303
|
-
</div>
|
|
304
|
-
<div class="pipeline-step">
|
|
305
|
-
<div class="step-circle active">4</div>
|
|
306
|
-
<div class="step-name">Authorize</div>
|
|
307
|
-
<div class="step-cmd">Human Gate</div>
|
|
308
|
-
</div>
|
|
309
|
-
<div class="pipeline-step">
|
|
310
|
-
<div class="step-circle active">5</div>
|
|
311
|
-
<div class="step-name">Apply</div>
|
|
312
|
-
<div class="step-cmd">apply · .bak</div>
|
|
313
|
-
</div>
|
|
314
|
-
<div class="pipeline-step">
|
|
315
|
-
<div class="step-circle active">6</div>
|
|
316
|
-
<div class="step-name">Recheck</div>
|
|
317
|
-
<div class="step-cmd">recheck · verify</div>
|
|
318
|
-
</div>
|
|
319
|
-
<div class="pipeline-step">
|
|
320
|
-
<div class="step-circle active">7</div>
|
|
321
|
-
<div class="step-name">Report</div>
|
|
322
|
-
<div class="step-cmd">report · SARIF</div>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
</div>
|
|
578
|
+
/* Heatmap */
|
|
579
|
+
.heatmap-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; cursor: pointer; padding: 4px 6px; border-radius: 4px; }
|
|
580
|
+
.heatmap-row:hover { background: rgba(255, 255, 255, 0.03); }
|
|
581
|
+
body.light-theme .heatmap-row:hover { background: rgba(0, 0, 0, 0.03); }
|
|
582
|
+
.heatmap-label { font-size: 12px; font-family: monospace; color: var(--text-main); min-width: 110px; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
583
|
+
.heatmap-bar-bg { flex: 1; height: 16px; background: var(--bg-card); border-radius: 4px; overflow: hidden; }
|
|
584
|
+
.heatmap-bar { height: 100%; border-radius: 4px; }
|
|
585
|
+
.heatmap-count { font-size: 12px; font-weight: 600; color: var(--text-muted); min-width: 25px; }
|
|
326
586
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
587
|
+
/* Golden Recipes & Diff Engine */
|
|
588
|
+
.recipe-card {
|
|
589
|
+
background: var(--bg-card);
|
|
590
|
+
border: 1px solid var(--border-subtle);
|
|
591
|
+
border-radius: 6px;
|
|
592
|
+
padding: 14px;
|
|
593
|
+
margin-bottom: 12px;
|
|
594
|
+
}
|
|
595
|
+
.recipe-header {
|
|
596
|
+
display: flex;
|
|
597
|
+
justify-content: space-between;
|
|
598
|
+
align-items: center;
|
|
599
|
+
margin-bottom: 8px;
|
|
600
|
+
flex-wrap: wrap;
|
|
601
|
+
gap: 8px;
|
|
602
|
+
font-size: 13px;
|
|
603
|
+
}
|
|
604
|
+
.recipe-meta-badges { display: flex; align-items: center; gap: 6px; }
|
|
605
|
+
.chip-filetype { background: rgba(88, 166, 255, 0.1); border: 1px solid var(--accent-blue); color: var(--accent-blue); font-size: 10px; font-family: monospace; padding: 2px 6px; border-radius: 4px; }
|
|
606
|
+
.ponytail-badge { background: rgba(210, 153, 34, 0.15); border: 1px solid var(--accent-yellow); color: var(--accent-yellow); font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; }
|
|
607
|
+
.verified-badge { background: rgba(63, 185, 80, 0.15); border: 1px solid var(--accent-green); color: var(--accent-green); font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; }
|
|
608
|
+
.recipe-view-switcher {
|
|
609
|
+
display: flex;
|
|
610
|
+
gap: 4px;
|
|
611
|
+
margin-bottom: 8px;
|
|
612
|
+
}
|
|
613
|
+
.recipe-tab-btn {
|
|
614
|
+
background: transparent;
|
|
615
|
+
border: 1px solid var(--border-subtle);
|
|
616
|
+
color: var(--text-muted);
|
|
617
|
+
padding: 2px 8px;
|
|
618
|
+
border-radius: 4px;
|
|
619
|
+
font-size: 11px;
|
|
620
|
+
cursor: pointer;
|
|
621
|
+
}
|
|
622
|
+
.recipe-tab-btn.active {
|
|
623
|
+
background: var(--accent-blue);
|
|
624
|
+
border-color: var(--accent-blue);
|
|
625
|
+
color: #fff;
|
|
626
|
+
}
|
|
627
|
+
.diff-line-header { color: var(--text-muted); font-weight: 600; display: block; }
|
|
628
|
+
.diff-line-hunk { color: var(--accent-blue); background: rgba(88, 166, 255, 0.1); display: block; padding: 1px 4px; border-radius: 2px; }
|
|
629
|
+
.diff-line-add { color: #7ee787; background: rgba(46, 160, 67, 0.18); display: block; padding: 1px 4px; border-radius: 2px; }
|
|
630
|
+
.diff-line-del { color: #ffa198; background: rgba(248, 81, 73, 0.18); display: block; padding: 1px 4px; border-radius: 2px; }
|
|
631
|
+
.diff-line-ctx { color: var(--text-muted); display: block; padding: 1px 4px; }
|
|
632
|
+
body.light-theme .diff-line-add { color: #1a7f37; background: rgba(46, 160, 67, 0.15); }
|
|
633
|
+
body.light-theme .diff-line-del { color: #cf222e; background: rgba(207, 34, 46, 0.15); }
|
|
634
|
+
body.light-theme .diff-line-hunk { color: #0969da; background: rgba(9, 105, 218, 0.1); }
|
|
635
|
+
|
|
636
|
+
.recipe-split-container {
|
|
637
|
+
display: grid;
|
|
638
|
+
grid-template-columns: 1fr 1fr;
|
|
639
|
+
gap: 10px;
|
|
640
|
+
margin-bottom: 12px;
|
|
641
|
+
}
|
|
642
|
+
@media (max-width: 650px) { .recipe-split-container { grid-template-columns: 1fr; } }
|
|
643
|
+
.split-pane .code-preview { margin-bottom: 0; min-height: 70px; }
|
|
644
|
+
.split-label { font-size: 11px; font-weight: 600; margin-bottom: 4px; }
|
|
645
|
+
.split-del { color: var(--accent-red); }
|
|
646
|
+
.split-add { color: var(--accent-green); }
|
|
647
|
+
.recipe-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }
|
|
648
|
+
|
|
649
|
+
/* Advisory Items */
|
|
650
|
+
.advisory-item {
|
|
651
|
+
background: var(--bg-card);
|
|
652
|
+
border: 1px solid var(--border-subtle);
|
|
653
|
+
border-radius: 6px;
|
|
654
|
+
padding: 12px;
|
|
655
|
+
margin-bottom: 10px;
|
|
656
|
+
}
|
|
657
|
+
.advisory-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
|
658
|
+
.advisory-mitigates { font-size: 11px; color: var(--accent-blue); margin-bottom: 4px; }
|
|
659
|
+
.advisory-desc { font-size: 12px; color: var(--text-main); margin-bottom: 4px; }
|
|
660
|
+
.advisory-guarantee { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; }
|
|
661
|
+
|
|
662
|
+
.empty-state { text-align: center; padding: 20px; color: var(--text-muted); font-size: 13px; }
|
|
663
|
+
.empty-cell { text-align: center; color: var(--accent-green); padding: 24px; font-weight: 600; }
|
|
664
|
+
|
|
665
|
+
footer {
|
|
666
|
+
margin-top: 36px;
|
|
667
|
+
text-align: center;
|
|
668
|
+
color: var(--text-muted);
|
|
669
|
+
font-size: 12px;
|
|
670
|
+
border-top: 1px solid var(--border-subtle);
|
|
671
|
+
padding-top: 18px;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/* Print & PDF styling */
|
|
675
|
+
@media print {
|
|
676
|
+
body { background: #fff !important; color: #000 !important; padding: 10px; }
|
|
677
|
+
.card, .owasp-card, .cluster-item, .defended-section, .table-container, .recipe-card, .advisory-item, .comp-card {
|
|
678
|
+
border: 1px solid #ccc !important;
|
|
679
|
+
background: #fff !important;
|
|
680
|
+
color: #000 !important;
|
|
681
|
+
box-shadow: none !important;
|
|
682
|
+
}
|
|
683
|
+
.header-actions, .filter-bar, .btn-copy, footer, .drawer-actions, .simulator-card, .recipe-view-switcher, .recipe-actions { display: none !important; }
|
|
684
|
+
.finding-row { background: #fff !important; color: #000 !important; }
|
|
685
|
+
.finding-title, .rule-id { color: #000 !important; }
|
|
686
|
+
.drawer-row { display: table-row !important; }
|
|
687
|
+
.drawer-content { background: #f6f8fa !important; border-left: 3px solid #000 !important; }
|
|
688
|
+
.code-preview { background: #eee !important; color: #000 !important; }
|
|
689
|
+
.code-preview code { color: #000 !important; }
|
|
690
|
+
.gauge-card { display: none !important; }
|
|
691
|
+
}
|
|
692
|
+
</style>
|
|
693
|
+
</head>
|
|
694
|
+
<body>
|
|
695
|
+
<div class="container">
|
|
696
|
+
<header>
|
|
697
|
+
<div class="logo-group">
|
|
698
|
+
<h1><span class="shield-icon">🛡</span> TorusGuard Security Report</h1>
|
|
699
|
+
<div class="project-meta">
|
|
700
|
+
<strong>TorusGuard</strong> ·
|
|
701
|
+
TypeScript ·
|
|
702
|
+
2026-09-18 16:20:01 IST
|
|
703
|
+
</div>
|
|
704
|
+
</div>
|
|
705
|
+
<div class="header-actions">
|
|
706
|
+
<button class="btn" id="themeToggleBtn" onclick="toggleTheme()">☀️ Light Mode</button>
|
|
707
|
+
<button class="btn" onclick="downloadSarif()">📥 Export SARIF</button>
|
|
708
|
+
<button class="btn" onclick="downloadCsv()">📊 Export CSV</button>
|
|
709
|
+
<button class="btn" onclick="window.print()">🖶 Print / PDF</button>
|
|
710
|
+
<div class="status-badge status-clean">🟢 HARDENED & SECURE</div>
|
|
711
|
+
</div>
|
|
712
|
+
</header>
|
|
713
|
+
|
|
714
|
+
<!-- KPI Cards -->
|
|
715
|
+
<div class="grid-kpi">
|
|
716
|
+
<div class="card gauge-card">
|
|
717
|
+
<div style="position: relative; display: flex; align-items: center; justify-content: center;">
|
|
718
|
+
<svg class="gauge-svg" viewBox="0 0 100 100">
|
|
719
|
+
<defs>
|
|
720
|
+
<linearGradient id="gaugeGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
721
|
+
<stop offset="0%" stop-color="#3fb950" />
|
|
722
|
+
<stop offset="100%" stop-color="#2ea043" />
|
|
723
|
+
</linearGradient>
|
|
724
|
+
<filter id="gaugeGlow" x="-20%" y="-20%" width="140%" height="140%">
|
|
725
|
+
<feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#3fb950" flood-opacity="0.5"/>
|
|
726
|
+
</filter>
|
|
727
|
+
</defs>
|
|
728
|
+
<circle class="gauge-bg" cx="50" cy="50" r="40" />
|
|
729
|
+
<circle class="gauge-fill" id="gaugeCircleFill" cx="50" cy="50" r="40" stroke="url(#gaugeGradient)" filter="url(#gaugeGlow)" />
|
|
730
|
+
</svg>
|
|
731
|
+
<div class="gauge-value" id="postureScoreText">0</div>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="stat-label" style="font-size: 12px; color: var(--text-muted); text-transform: uppercase; margin-top: 10px;">Posture Score</div>
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
<div class="card sev-card" onclick="filterBySeverity('critical')">
|
|
737
|
+
<div class="sev-count" style="color: var(--accent-red);">0</div>
|
|
738
|
+
<div class="sev-label">Critical Exposure</div>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
741
|
+
<div class="card sev-card" onclick="filterBySeverity('high')">
|
|
742
|
+
<div class="sev-count" style="color: var(--accent-orange);">0</div>
|
|
743
|
+
<div class="sev-label">High Exposure</div>
|
|
744
|
+
</div>
|
|
745
|
+
|
|
746
|
+
<div class="card sev-card" onclick="filterBySeverity('medium')">
|
|
747
|
+
<div class="sev-count" style="color: var(--accent-yellow);">0</div>
|
|
748
|
+
<div class="sev-label">Medium / Low</div>
|
|
749
|
+
</div>
|
|
750
|
+
|
|
751
|
+
<div class="card sev-card" onclick="toggleDefendedSection()">
|
|
752
|
+
<div class="sev-count" style="color: var(--accent-green);">74<span style="font-size: 16px; color: var(--text-muted);">/74</span></div>
|
|
753
|
+
<div class="sev-label">Guards Defended</div>
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
<!-- What-If Posture Score Simulator -->
|
|
758
|
+
<div class="card simulator-card simulator-clean">
|
|
759
|
+
<div>
|
|
760
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
761
|
+
<strong style="font-size: 13px;">⚡ Interactive "What-If" Posture Score Simulator</strong>
|
|
762
|
+
<span class="badge badge-primary">Dynamic Recalculation</span>
|
|
763
|
+
</div>
|
|
764
|
+
<div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">
|
|
765
|
+
All 74 security invariants are currently defended. Simulator is ready for live remediation modeling during audit cycles.
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
<div class="sim-stats">
|
|
769
|
+
<div>Current: <strong id="simCurrentVal">100</strong>/100</div>
|
|
770
|
+
<div>→</div>
|
|
771
|
+
<div>Projected: <strong id="simProjectedVal" style="color: var(--accent-blue);">100</strong>/100 <span id="simDeltaBadge" class="badge badge-success">+0 pts</span></div>
|
|
772
|
+
</div>
|
|
773
|
+
<div class="sim-actions">
|
|
774
|
+
<button class="btn btn-sm" onclick="simulateFixAll('Critical')">⚡ Simulate Criticals</button>
|
|
775
|
+
<button class="btn btn-sm" onclick="simulateFixAll('all')">⚡ Simulate All Fixes</button>
|
|
776
|
+
<button class="btn btn-sm" onclick="resetSimulatedFixes()">Reset Simulator</button>
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
|
|
780
|
+
<!-- Active Defenses & Protected Invariants (5-Layer Architectural Invariant Matrix) -->
|
|
781
|
+
<div class="defended-section" id="defendedSection">
|
|
782
|
+
<div class="defended-header" onclick="toggleDefendedVisibility()">
|
|
783
|
+
<div class="section-title" style="margin-bottom: 0;">
|
|
784
|
+
🛡 Active Defenses & Protected Invariants (74 Passing)
|
|
785
|
+
</div>
|
|
786
|
+
<span id="defendedToggleIcon" style="color: var(--text-muted); font-size: 12px;">▼ Collapse Matrix</span>
|
|
787
|
+
</div>
|
|
788
|
+
<div id="defendedMatrixBody">
|
|
789
|
+
<div class="coverage-bar-container">
|
|
790
|
+
<div class="coverage-stats">
|
|
791
|
+
<span><strong>74 of 74 Invariants Enforced</strong> (100% Defense Coverage)</span>
|
|
792
|
+
<span class="badge badge-success">100% Enforced</span>
|
|
793
|
+
</div>
|
|
794
|
+
<div class="coverage-progress-bg">
|
|
795
|
+
<div class="coverage-progress-fill" style="width: 100%;"></div>
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
<div class="inv-search-bar">
|
|
799
|
+
<div class="search-wrapper" style="width: 100%;">
|
|
800
|
+
<input type="text" id="invSearchInput" class="search-input" style="width: 100%;" placeholder="Search 74 invariants by ID, title, layer, or keyword..." oninput="filterInvariants()">
|
|
801
|
+
<button class="search-clear-btn" id="invSearchClearBtn" onclick="clearInvSearch()">×</button>
|
|
802
|
+
</div>
|
|
803
|
+
<div id="invMatchCount" style="font-size: 11px; color: var(--accent-blue); white-space: nowrap;"></div>
|
|
804
|
+
</div>
|
|
805
|
+
<div class="layers-container" id="layersList">
|
|
333
806
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
807
|
+
<div class="layer-card" data-layer-id="layer-app">
|
|
808
|
+
<div class="layer-header">
|
|
809
|
+
<div class="layer-title-group">
|
|
810
|
+
<span class="layer-icon">🛡️</span>
|
|
811
|
+
<div>
|
|
812
|
+
<div class="layer-name">Application & LLM Defense</div>
|
|
813
|
+
<div class="layer-desc">User prompt isolation, inert delimiters, traversal defense, and input escaping.</div>
|
|
338
814
|
</div>
|
|
339
|
-
<p class="recipe-desc">Migrated secret to environment variable</p>
|
|
340
|
-
<pre class="diff-block"><code>+ SECRET = env
|
|
341
|
-
- SECRET = test</code></pre>
|
|
342
815
|
</div>
|
|
816
|
+
<div class="layer-badge status-clean">10/10 Enforced</div>
|
|
817
|
+
</div>
|
|
818
|
+
<div class="layer-chips-grid">
|
|
343
819
|
|
|
344
|
-
<div class="
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
820
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-INPUT-001" data-layer="layer-app" data-search="tg-input-001 missing server-side request validation high defended application & llm defense" onclick="inspectInvariant('TG-INPUT-001')" title="Click to inspect security invariant guarantee for TG-INPUT-001">
|
|
821
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
822
|
+
<code class="inv-code">TG-INPUT-001</code>
|
|
823
|
+
<span class="inv-title">Missing Server-Side Request Validation</span>
|
|
824
|
+
</div>
|
|
825
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-INPUT-002" data-layer="layer-app" data-search="tg-input-002 raw sql concatenation critical defended application & llm defense" onclick="inspectInvariant('TG-INPUT-002')" title="Click to inspect security invariant guarantee for TG-INPUT-002">
|
|
826
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
827
|
+
<code class="inv-code">TG-INPUT-002</code>
|
|
828
|
+
<span class="inv-title">Raw SQL Concatenation</span>
|
|
829
|
+
</div>
|
|
830
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-INPUT-003" data-layer="layer-app" data-search="tg-input-003 unsafe dynamic code or html execution high defended application & llm defense" onclick="inspectInvariant('TG-INPUT-003')" title="Click to inspect security invariant guarantee for TG-INPUT-003">
|
|
831
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
832
|
+
<code class="inv-code">TG-INPUT-003</code>
|
|
833
|
+
<span class="inv-title">Unsafe Dynamic Code or HTML Execution</span>
|
|
834
|
+
</div>
|
|
835
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-INPUT-004" data-layer="layer-app" data-search="tg-input-004 command injection via shell execution high defended application & llm defense" onclick="inspectInvariant('TG-INPUT-004')" title="Click to inspect security invariant guarantee for TG-INPUT-004">
|
|
836
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
837
|
+
<code class="inv-code">TG-INPUT-004</code>
|
|
838
|
+
<span class="inv-title">Command Injection via Shell Execution</span>
|
|
839
|
+
</div>
|
|
840
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-INPUT-005" data-layer="layer-app" data-search="tg-input-005 unsafe template rendering & disabled autoescaping high defended application & llm defense" onclick="inspectInvariant('TG-INPUT-005')" title="Click to inspect security invariant guarantee for TG-INPUT-005">
|
|
841
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
842
|
+
<code class="inv-code">TG-INPUT-005</code>
|
|
843
|
+
<span class="inv-title">Unsafe Template Rendering & Disabled Autoescaping</span>
|
|
844
|
+
</div>
|
|
845
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-INPUT-006" data-layer="layer-app" data-search="tg-input-006 path traversal and unsafe upload storage critical defended application & llm defense" onclick="inspectInvariant('TG-INPUT-006')" title="Click to inspect security invariant guarantee for TG-INPUT-006">
|
|
846
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
847
|
+
<code class="inv-code">TG-INPUT-006</code>
|
|
848
|
+
<span class="inv-title">Path Traversal and Unsafe Upload Storage</span>
|
|
849
|
+
</div>
|
|
850
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AGENT-001" data-layer="layer-app" data-search="tg-agent-001 prompt injection in system context files critical defended application & llm defense" onclick="inspectInvariant('TG-AGENT-001')" title="Click to inspect security invariant guarantee for TG-AGENT-001">
|
|
851
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
852
|
+
<code class="inv-code">TG-AGENT-001</code>
|
|
853
|
+
<span class="inv-title">Prompt Injection in System Context Files</span>
|
|
854
|
+
</div>
|
|
855
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AGENT-002" data-layer="layer-app" data-search="tg-agent-002 unsafe tool dispatch & shell execution without sandboxing critical defended application & llm defense" onclick="inspectInvariant('TG-AGENT-002')" title="Click to inspect security invariant guarantee for TG-AGENT-002">
|
|
856
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
857
|
+
<code class="inv-code">TG-AGENT-002</code>
|
|
858
|
+
<span class="inv-title">Unsafe Tool Dispatch & Shell Execution without Sandboxing</span>
|
|
859
|
+
</div>
|
|
860
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AGENT-003" data-layer="layer-app" data-search="tg-agent-003 overly broad mcp tool scoping & credential access high defended application & llm defense" onclick="inspectInvariant('TG-AGENT-003')" title="Click to inspect security invariant guarantee for TG-AGENT-003">
|
|
861
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
862
|
+
<code class="inv-code">TG-AGENT-003</code>
|
|
863
|
+
<span class="inv-title">Overly Broad MCP Tool Scoping & Credential Access</span>
|
|
864
|
+
</div>
|
|
865
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AGENT-004" data-layer="layer-app" data-search="tg-agent-004 persistent memory & cross-session information leakage high defended application & llm defense" onclick="inspectInvariant('TG-AGENT-004')" title="Click to inspect security invariant guarantee for TG-AGENT-004">
|
|
866
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
867
|
+
<code class="inv-code">TG-AGENT-004</code>
|
|
868
|
+
<span class="inv-title">Persistent Memory & Cross-Session Information Leakage</span>
|
|
869
|
+
</div>
|
|
870
|
+
</div>
|
|
871
|
+
</div>
|
|
872
|
+
<div class="layer-card" data-layer-id="layer-auth">
|
|
873
|
+
<div class="layer-header">
|
|
874
|
+
<div class="layer-title-group">
|
|
875
|
+
<span class="layer-icon">🔑</span>
|
|
876
|
+
<div>
|
|
877
|
+
<div class="layer-name">Identity, Auth & Real-Time</div>
|
|
878
|
+
<div class="layer-desc">Constant-time comparisons, password hashing, algorithm pinning, SameSite cookies, and business flow bounds.</div>
|
|
348
879
|
</div>
|
|
349
|
-
<p class="recipe-desc">Disabled public production source map emission</p>
|
|
350
|
-
<pre class="diff-block"><code>--- a/examples/vulnerable-react-express/client/vite.config.js
|
|
351
|
-
+++ b/examples/vulnerable-react-express/client/vite.config.js
|
|
352
|
-
@@ -5,5 +5,5 @@
|
|
353
|
-
export default defineConfig({
|
|
354
|
-
plugins: [react()],
|
|
355
|
-
- build: { sourcemap: true },
|
|
356
|
-
+ build: { sourcemap: false },
|
|
357
|
-
server: {
|
|
358
|
-
port: 5173,</code></pre>
|
|
359
880
|
</div>
|
|
881
|
+
<div class="layer-badge status-clean">18/18 Enforced</div>
|
|
882
|
+
</div>
|
|
883
|
+
<div class="layer-chips-grid">
|
|
360
884
|
|
|
361
|
-
<div class="
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
885
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-001" data-layer="layer-auth" data-search="tg-auth-001 weak password storage or deprecated hashing critical defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-001')" title="Click to inspect security invariant guarantee for TG-AUTH-001">
|
|
886
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
887
|
+
<code class="inv-code">TG-AUTH-001</code>
|
|
888
|
+
<span class="inv-title">Weak Password Storage or Deprecated Hashing</span>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-002" data-layer="layer-auth" data-search="tg-auth-002 client-only authorization enforcement high defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-002')" title="Click to inspect security invariant guarantee for TG-AUTH-002">
|
|
891
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
892
|
+
<code class="inv-code">TG-AUTH-002</code>
|
|
893
|
+
<span class="inv-title">Client-Only Authorization Enforcement</span>
|
|
894
|
+
</div>
|
|
895
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-003" data-layer="layer-auth" data-search="tg-auth-003 missing object-level authorization (idor) high defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-003')" title="Click to inspect security invariant guarantee for TG-AUTH-003">
|
|
896
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
897
|
+
<code class="inv-code">TG-AUTH-003</code>
|
|
898
|
+
<span class="inv-title">Missing Object-Level Authorization (IDOR)</span>
|
|
899
|
+
</div>
|
|
900
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-004" data-layer="layer-auth" data-search="tg-auth-004 insecure session cookie configuration high defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-004')" title="Click to inspect security invariant guarantee for TG-AUTH-004">
|
|
901
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
902
|
+
<code class="inv-code">TG-AUTH-004</code>
|
|
903
|
+
<span class="inv-title">Insecure Session Cookie Configuration</span>
|
|
904
|
+
</div>
|
|
905
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-005" data-layer="layer-auth" data-search="tg-auth-005 unsafe password reset token generation high defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-005')" title="Click to inspect security invariant guarantee for TG-AUTH-005">
|
|
906
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
907
|
+
<code class="inv-code">TG-AUTH-005</code>
|
|
908
|
+
<span class="inv-title">Unsafe Password Reset Token Generation</span>
|
|
909
|
+
</div>
|
|
910
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-006" data-layer="layer-auth" data-search="tg-auth-006 mass assignment & privilege escalation via payloads critical defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-006')" title="Click to inspect security invariant guarantee for TG-AUTH-006">
|
|
911
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
912
|
+
<code class="inv-code">TG-AUTH-006</code>
|
|
913
|
+
<span class="inv-title">Mass Assignment & Privilege Escalation via Payloads</span>
|
|
914
|
+
</div>
|
|
915
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-007" data-layer="layer-auth" data-search="tg-auth-007 missing object-level and property-level authorization (idor) critical defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-007')" title="Click to inspect security invariant guarantee for TG-AUTH-007">
|
|
916
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
917
|
+
<code class="inv-code">TG-AUTH-007</code>
|
|
918
|
+
<span class="inv-title">Missing Object-Level and Property-Level Authorization (IDOR)</span>
|
|
919
|
+
</div>
|
|
920
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-AUTH-008" data-layer="layer-auth" data-search="tg-auth-008 untrusted role or tenant header injection critical defended identity, auth & real-time" onclick="inspectInvariant('TG-AUTH-008')" title="Click to inspect security invariant guarantee for TG-AUTH-008">
|
|
921
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
922
|
+
<code class="inv-code">TG-AUTH-008</code>
|
|
923
|
+
<span class="inv-title">Untrusted Role or Tenant Header Injection</span>
|
|
924
|
+
</div>
|
|
925
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WS-001" data-layer="layer-auth" data-search="tg-ws-001 unauthenticated websocket handshake high defended identity, auth & real-time" onclick="inspectInvariant('TG-WS-001')" title="Click to inspect security invariant guarantee for TG-WS-001">
|
|
926
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
927
|
+
<code class="inv-code">TG-WS-001</code>
|
|
928
|
+
<span class="inv-title">Unauthenticated WebSocket Handshake</span>
|
|
929
|
+
</div>
|
|
930
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WS-002" data-layer="layer-auth" data-search="tg-ws-002 missing channel-level authorization critical defended identity, auth & real-time" onclick="inspectInvariant('TG-WS-002')" title="Click to inspect security invariant guarantee for TG-WS-002">
|
|
931
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
932
|
+
<code class="inv-code">TG-WS-002</code>
|
|
933
|
+
<span class="inv-title">Missing Channel-Level Authorization</span>
|
|
934
|
+
</div>
|
|
935
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WS-003" data-layer="layer-auth" data-search="tg-ws-003 unvalidated websocket message critical defended identity, auth & real-time" onclick="inspectInvariant('TG-WS-003')" title="Click to inspect security invariant guarantee for TG-WS-003">
|
|
936
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
937
|
+
<code class="inv-code">TG-WS-003</code>
|
|
938
|
+
<span class="inv-title">Unvalidated WebSocket Message</span>
|
|
939
|
+
</div>
|
|
940
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WS-004" data-layer="layer-auth" data-search="tg-ws-004 unbounded websocket connections critical defended identity, auth & real-time" onclick="inspectInvariant('TG-WS-004')" title="Click to inspect security invariant guarantee for TG-WS-004">
|
|
941
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
942
|
+
<code class="inv-code">TG-WS-004</code>
|
|
943
|
+
<span class="inv-title">Unbounded WebSocket Connections</span>
|
|
944
|
+
</div>
|
|
945
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CSRF-001" data-layer="layer-auth" data-search="tg-csrf-001 missing csrf protection critical defended identity, auth & real-time" onclick="inspectInvariant('TG-CSRF-001')" title="Click to inspect security invariant guarantee for TG-CSRF-001">
|
|
946
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
947
|
+
<code class="inv-code">TG-CSRF-001</code>
|
|
948
|
+
<span class="inv-title">Missing CSRF Protection</span>
|
|
949
|
+
</div>
|
|
950
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CSRF-002" data-layer="layer-auth" data-search="tg-csrf-002 unsafe credentialed cross-origin request critical defended identity, auth & real-time" onclick="inspectInvariant('TG-CSRF-002')" title="Click to inspect security invariant guarantee for TG-CSRF-002">
|
|
951
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
952
|
+
<code class="inv-code">TG-CSRF-002</code>
|
|
953
|
+
<span class="inv-title">Unsafe Credentialed Cross-Origin Request</span>
|
|
954
|
+
</div>
|
|
955
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-BIZ-001" data-layer="layer-auth" data-search="tg-biz-001 client-controlled sensitive business value critical defended identity, auth & real-time" onclick="inspectInvariant('TG-BIZ-001')" title="Click to inspect security invariant guarantee for TG-BIZ-001">
|
|
956
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
957
|
+
<code class="inv-code">TG-BIZ-001</code>
|
|
958
|
+
<span class="inv-title">Client-Controlled Sensitive Business Value</span>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-BIZ-002" data-layer="layer-auth" data-search="tg-biz-002 replayable one-time operation critical defended identity, auth & real-time" onclick="inspectInvariant('TG-BIZ-002')" title="Click to inspect security invariant guarantee for TG-BIZ-002">
|
|
961
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
962
|
+
<code class="inv-code">TG-BIZ-002</code>
|
|
963
|
+
<span class="inv-title">Replayable One-Time Operation</span>
|
|
964
|
+
</div>
|
|
965
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-BIZ-003" data-layer="layer-auth" data-search="tg-biz-003 missing workflow-state validation critical defended identity, auth & real-time" onclick="inspectInvariant('TG-BIZ-003')" title="Click to inspect security invariant guarantee for TG-BIZ-003">
|
|
966
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
967
|
+
<code class="inv-code">TG-BIZ-003</code>
|
|
968
|
+
<span class="inv-title">Missing Workflow-State Validation</span>
|
|
969
|
+
</div>
|
|
970
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-BIZ-004" data-layer="layer-auth" data-search="tg-biz-004 unrestricted sensitive business flow critical defended identity, auth & real-time" onclick="inspectInvariant('TG-BIZ-004')" title="Click to inspect security invariant guarantee for TG-BIZ-004">
|
|
971
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
972
|
+
<code class="inv-code">TG-BIZ-004</code>
|
|
973
|
+
<span class="inv-title">Unrestricted Sensitive Business Flow</span>
|
|
974
|
+
</div>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
<div class="layer-card" data-layer-id="layer-db">
|
|
978
|
+
<div class="layer-header">
|
|
979
|
+
<div class="layer-title-group">
|
|
980
|
+
<span class="layer-icon">💾</span>
|
|
981
|
+
<div>
|
|
982
|
+
<div class="layer-name">Database, GraphQL & Cache</div>
|
|
983
|
+
<div class="layer-desc">Parameterized SQL queries, multi-tenant row partitioning, GraphQL query depth limits, and cache timing guards.</div>
|
|
365
984
|
</div>
|
|
366
|
-
<p class="recipe-desc">Replaced hardcoded privileged database credential with process.env.SUPABASE_SERVICE_ROLE_KEY</p>
|
|
367
|
-
<pre class="diff-block"><code>--- a/demo/playground/vulnerable_nextjs/actions.ts
|
|
368
|
-
+++ b/demo/playground/vulnerable_nextjs/actions.ts
|
|
369
|
-
@@ -7,5 +7,5 @@
|
|
370
|
-
|
|
371
|
-
// Finding 1: TG-CLIENT-001 - Backend admin key exposed in client-accessible code
|
|
372
|
-
-export const SUPABASE_SERVICE_ROLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.demo_secret_token_1234567890";
|
|
373
|
-
+export const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
374
|
-
|
|
375
|
-
interface UserRecord {</code></pre>
|
|
376
985
|
</div>
|
|
986
|
+
<div class="layer-badge status-clean">11/11 Enforced</div>
|
|
987
|
+
</div>
|
|
988
|
+
<div class="layer-chips-grid">
|
|
377
989
|
|
|
378
|
-
<div class="
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
990
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DB-001" data-layer="layer-db" data-search="tg-db-001 frontend database query logic high defended database, graphql & cache" onclick="inspectInvariant('TG-DB-001')" title="Click to inspect security invariant guarantee for TG-DB-001">
|
|
991
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
992
|
+
<code class="inv-code">TG-DB-001</code>
|
|
993
|
+
<span class="inv-title">Frontend Database Query Logic</span>
|
|
994
|
+
</div>
|
|
995
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DB-002" data-layer="layer-db" data-search="tg-db-002 privileged database credential in browser context critical defended database, graphql & cache" onclick="inspectInvariant('TG-DB-002')" title="Click to inspect security invariant guarantee for TG-DB-002">
|
|
996
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
997
|
+
<code class="inv-code">TG-DB-002</code>
|
|
998
|
+
<span class="inv-title">Privileged Database Credential in Browser Context</span>
|
|
999
|
+
</div>
|
|
1000
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DB-003" data-layer="layer-db" data-search="tg-db-003 frontend use of admin or server-only database sdks critical defended database, graphql & cache" onclick="inspectInvariant('TG-DB-003')" title="Click to inspect security invariant guarantee for TG-DB-003">
|
|
1001
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1002
|
+
<code class="inv-code">TG-DB-003</code>
|
|
1003
|
+
<span class="inv-title">Frontend Use of Admin or Server-Only Database SDKs</span>
|
|
1004
|
+
</div>
|
|
1005
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DB-004" data-layer="layer-db" data-search="tg-db-004 missing tenant query isolation in multi-tenant models critical defended database, graphql & cache" onclick="inspectInvariant('TG-DB-004')" title="Click to inspect security invariant guarantee for TG-DB-004">
|
|
1006
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1007
|
+
<code class="inv-code">TG-DB-004</code>
|
|
1008
|
+
<span class="inv-title">Missing Tenant Query Isolation in Multi-Tenant Models</span>
|
|
1009
|
+
</div>
|
|
1010
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-GQL-001" data-layer="layer-db" data-search="tg-gql-001 missing query depth or complexity limits high defended database, graphql & cache" onclick="inspectInvariant('TG-GQL-001')" title="Click to inspect security invariant guarantee for TG-GQL-001">
|
|
1011
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1012
|
+
<code class="inv-code">TG-GQL-001</code>
|
|
1013
|
+
<span class="inv-title">Missing Query Depth or Complexity Limits</span>
|
|
1014
|
+
</div>
|
|
1015
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-GQL-002" data-layer="layer-db" data-search="tg-gql-002 resolver missing authorization critical defended database, graphql & cache" onclick="inspectInvariant('TG-GQL-002')" title="Click to inspect security invariant guarantee for TG-GQL-002">
|
|
1016
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1017
|
+
<code class="inv-code">TG-GQL-002</code>
|
|
1018
|
+
<span class="inv-title">Resolver Missing Authorization</span>
|
|
1019
|
+
</div>
|
|
1020
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-GQL-003" data-layer="layer-db" data-search="tg-gql-003 unbounded graphql pagination or batching critical defended database, graphql & cache" onclick="inspectInvariant('TG-GQL-003')" title="Click to inspect security invariant guarantee for TG-GQL-003">
|
|
1021
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1022
|
+
<code class="inv-code">TG-GQL-003</code>
|
|
1023
|
+
<span class="inv-title">Unbounded GraphQL Pagination or Batching</span>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-GQL-004" data-layer="layer-db" data-search="tg-gql-004 unnecessary production introspection critical defended database, graphql & cache" onclick="inspectInvariant('TG-GQL-004')" title="Click to inspect security invariant guarantee for TG-GQL-004">
|
|
1026
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1027
|
+
<code class="inv-code">TG-GQL-004</code>
|
|
1028
|
+
<span class="inv-title">Unnecessary Production Introspection</span>
|
|
1029
|
+
</div>
|
|
1030
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CACHE-001" data-layer="layer-db" data-search="tg-cache-001 sensitive response publicly cacheable critical defended database, graphql & cache" onclick="inspectInvariant('TG-CACHE-001')" title="Click to inspect security invariant guarantee for TG-CACHE-001">
|
|
1031
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1032
|
+
<code class="inv-code">TG-CACHE-001</code>
|
|
1033
|
+
<span class="inv-title">Sensitive Response Publicly Cacheable</span>
|
|
1034
|
+
</div>
|
|
1035
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CACHE-002" data-layer="layer-db" data-search="tg-cache-002 missing user cache isolation critical defended database, graphql & cache" onclick="inspectInvariant('TG-CACHE-002')" title="Click to inspect security invariant guarantee for TG-CACHE-002">
|
|
1036
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1037
|
+
<code class="inv-code">TG-CACHE-002</code>
|
|
1038
|
+
<span class="inv-title">Missing User Cache Isolation</span>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CACHE-003" data-layer="layer-db" data-search="tg-cache-003 sensitive data in url critical defended database, graphql & cache" onclick="inspectInvariant('TG-CACHE-003')" title="Click to inspect security invariant guarantee for TG-CACHE-003">
|
|
1041
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1042
|
+
<code class="inv-code">TG-CACHE-003</code>
|
|
1043
|
+
<span class="inv-title">Sensitive Data in URL</span>
|
|
1044
|
+
</div>
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
<div class="layer-card" data-layer-id="layer-net">
|
|
1048
|
+
<div class="layer-header">
|
|
1049
|
+
<div class="layer-title-group">
|
|
1050
|
+
<span class="layer-icon">🌐</span>
|
|
1051
|
+
<div>
|
|
1052
|
+
<div class="layer-name">Network, SSRF & Webhooks</div>
|
|
1053
|
+
<div class="layer-desc">Private IP / metadata blocking, cryptographic HMAC webhook authentication, and sliding-window rate limiters.</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
<div class="layer-badge status-clean">11/11 Enforced</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
<div class="layer-chips-grid">
|
|
1059
|
+
|
|
1060
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-RATE-001" data-layer="layer-net" data-search="tg-rate-001 unlimited authentication endpoint high defended network, ssrf & webhooks" onclick="inspectInvariant('TG-RATE-001')" title="Click to inspect security invariant guarantee for TG-RATE-001">
|
|
1061
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1062
|
+
<code class="inv-code">TG-RATE-001</code>
|
|
1063
|
+
<span class="inv-title">Unlimited Authentication Endpoint</span>
|
|
1064
|
+
</div>
|
|
1065
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-RATE-002" data-layer="layer-net" data-search="tg-rate-002 unlimited public write endpoint medium defended network, ssrf & webhooks" onclick="inspectInvariant('TG-RATE-002')" title="Click to inspect security invariant guarantee for TG-RATE-002">
|
|
1066
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1067
|
+
<code class="inv-code">TG-RATE-002</code>
|
|
1068
|
+
<span class="inv-title">Unlimited Public Write Endpoint</span>
|
|
1069
|
+
</div>
|
|
1070
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-RATE-003" data-layer="layer-net" data-search="tg-rate-003 unbounded resource consumption high defended network, ssrf & webhooks" onclick="inspectInvariant('TG-RATE-003')" title="Click to inspect security invariant guarantee for TG-RATE-003">
|
|
1071
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1072
|
+
<code class="inv-code">TG-RATE-003</code>
|
|
1073
|
+
<span class="inv-title">Unbounded Resource Consumption</span>
|
|
1074
|
+
</div>
|
|
1075
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SSRF-001" data-layer="layer-net" data-search="tg-ssrf-001 user-controlled server-side url fetch critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-SSRF-001')" title="Click to inspect security invariant guarantee for TG-SSRF-001">
|
|
1076
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1077
|
+
<code class="inv-code">TG-SSRF-001</code>
|
|
1078
|
+
<span class="inv-title">User-Controlled Server-Side URL Fetch</span>
|
|
1079
|
+
</div>
|
|
1080
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SSRF-002" data-layer="layer-net" data-search="tg-ssrf-002 missing internal network protection critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-SSRF-002')" title="Click to inspect security invariant guarantee for TG-SSRF-002">
|
|
1081
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1082
|
+
<code class="inv-code">TG-SSRF-002</code>
|
|
1083
|
+
<span class="inv-title">Missing Internal Network Protection</span>
|
|
1084
|
+
</div>
|
|
1085
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SSRF-003" data-layer="layer-net" data-search="tg-ssrf-003 unsafe redirect following critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-SSRF-003')" title="Click to inspect security invariant guarantee for TG-SSRF-003">
|
|
1086
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1087
|
+
<code class="inv-code">TG-SSRF-003</code>
|
|
1088
|
+
<span class="inv-title">Unsafe Redirect Following</span>
|
|
1089
|
+
</div>
|
|
1090
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SSRF-004" data-layer="layer-net" data-search="tg-ssrf-004 unbounded outbound request without timeout critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-SSRF-004')" title="Click to inspect security invariant guarantee for TG-SSRF-004">
|
|
1091
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1092
|
+
<code class="inv-code">TG-SSRF-004</code>
|
|
1093
|
+
<span class="inv-title">Unbounded Outbound Request Without Timeout</span>
|
|
1094
|
+
</div>
|
|
1095
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WEBHOOK-001" data-layer="layer-net" data-search="tg-webhook-001 missing webhook signature verification critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-WEBHOOK-001')" title="Click to inspect security invariant guarantee for TG-WEBHOOK-001">
|
|
1096
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1097
|
+
<code class="inv-code">TG-WEBHOOK-001</code>
|
|
1098
|
+
<span class="inv-title">Missing Webhook Signature Verification</span>
|
|
1099
|
+
</div>
|
|
1100
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WEBHOOK-002" data-layer="layer-net" data-search="tg-webhook-002 replayable webhook without timestamp validation critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-WEBHOOK-002')" title="Click to inspect security invariant guarantee for TG-WEBHOOK-002">
|
|
1101
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1102
|
+
<code class="inv-code">TG-WEBHOOK-002</code>
|
|
1103
|
+
<span class="inv-title">Replayable Webhook Without Timestamp Validation</span>
|
|
1104
|
+
</div>
|
|
1105
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WEBHOOK-003" data-layer="layer-net" data-search="tg-webhook-003 missing webhook idempotency critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-WEBHOOK-003')" title="Click to inspect security invariant guarantee for TG-WEBHOOK-003">
|
|
1106
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1107
|
+
<code class="inv-code">TG-WEBHOOK-003</code>
|
|
1108
|
+
<span class="inv-title">Missing Webhook Idempotency</span>
|
|
1109
|
+
</div>
|
|
1110
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-WEBHOOK-004" data-layer="layer-net" data-search="tg-webhook-004 unvalidated webhook event type critical defended network, ssrf & webhooks" onclick="inspectInvariant('TG-WEBHOOK-004')" title="Click to inspect security invariant guarantee for TG-WEBHOOK-004">
|
|
1111
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1112
|
+
<code class="inv-code">TG-WEBHOOK-004</code>
|
|
1113
|
+
<span class="inv-title">Unvalidated Webhook Event Type</span>
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
</div>
|
|
1117
|
+
<div class="layer-card" data-layer-id="layer-platform">
|
|
1118
|
+
<div class="layer-header">
|
|
1119
|
+
<div class="layer-title-group">
|
|
1120
|
+
<span class="layer-icon">🏗️</span>
|
|
1121
|
+
<div>
|
|
1122
|
+
<div class="layer-name">Platform, Supply Chain & Secrets</div>
|
|
1123
|
+
<div class="layer-desc">Security headers, dependency lockfile integrity, zero client bundle secrets, serverless limits, and Ponytail bounds.</div>
|
|
382
1124
|
</div>
|
|
383
|
-
<p class="recipe-desc">Added defensive request body payload validation before destructuring</p>
|
|
384
|
-
<pre class="diff-block"><code>--- a/examples/vulnerable-react-express/server/index.js
|
|
385
|
-
+++ b/examples/vulnerable-react-express/server/index.js
|
|
386
|
-
@@ -25,5 +25,5 @@
|
|
387
|
-
// TG-AUTH-004: Insecure cookie (no httpOnly/Secure/SameSite)
|
|
388
|
-
app.post('/api/login', (req, res) => {
|
|
389
|
-
- const { email, password } = req.body;
|
|
390
|
-
+ if (!req.body || typeof req.body !== "object") return res.status(400).json({ error: "Invalid payload" });
|
|
391
|
-
const { email, password } = req.body;
|
|
392
|
-
console.log('Login attempt:', { email, password }); // TG-SEC-004
|
|
393
|
-
const user = users.find((u) => u.email === email && u.password === password);</code></pre>
|
|
394
1125
|
</div>
|
|
1126
|
+
<div class="layer-badge status-clean">24/24 Enforced</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
<div class="layer-chips-grid">
|
|
1129
|
+
|
|
1130
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-001" data-layer="layer-platform" data-search="tg-sec-001 hardcoded secret or api key in tracked source critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-001')" title="Click to inspect security invariant guarantee for TG-SEC-001">
|
|
1131
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1132
|
+
<code class="inv-code">TG-SEC-001</code>
|
|
1133
|
+
<span class="inv-title">Hardcoded Secret or API Key in Tracked Source</span>
|
|
1134
|
+
</div>
|
|
1135
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-002" data-layer="layer-platform" data-search="tg-sec-002 public client environment variable secret exposure critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-002')" title="Click to inspect security invariant guarantee for TG-SEC-002">
|
|
1136
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1137
|
+
<code class="inv-code">TG-SEC-002</code>
|
|
1138
|
+
<span class="inv-title">Public Client Environment Variable Secret Exposure</span>
|
|
1139
|
+
</div>
|
|
1140
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-003" data-layer="layer-platform" data-search="tg-sec-003 tracked environment file high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-003')" title="Click to inspect security invariant guarantee for TG-SEC-003">
|
|
1141
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1142
|
+
<code class="inv-code">TG-SEC-003</code>
|
|
1143
|
+
<span class="inv-title">Tracked Environment File</span>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-004" data-layer="layer-platform" data-search="tg-sec-004 sensitive information in logs medium defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-004')" title="Click to inspect security invariant guarantee for TG-SEC-004">
|
|
1146
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1147
|
+
<code class="inv-code">TG-SEC-004</code>
|
|
1148
|
+
<span class="inv-title">Sensitive Information in Logs</span>
|
|
1149
|
+
</div>
|
|
1150
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-005" data-layer="layer-platform" data-search="tg-sec-005 secret in git history or query url critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-005')" title="Click to inspect security invariant guarantee for TG-SEC-005">
|
|
1151
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1152
|
+
<code class="inv-code">TG-SEC-005</code>
|
|
1153
|
+
<span class="inv-title">Secret in Git History or Query URL</span>
|
|
1154
|
+
</div>
|
|
1155
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-006" data-layer="layer-platform" data-search="tg-sec-006 secret in build artifact or container layer critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-006')" title="Click to inspect security invariant guarantee for TG-SEC-006">
|
|
1156
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1157
|
+
<code class="inv-code">TG-SEC-006</code>
|
|
1158
|
+
<span class="inv-title">Secret in Build Artifact or Container Layer</span>
|
|
1159
|
+
</div>
|
|
1160
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SEC-007" data-layer="layer-platform" data-search="tg-sec-007 secret in test or example fixture without mock marker critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SEC-007')" title="Click to inspect security invariant guarantee for TG-SEC-007">
|
|
1161
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1162
|
+
<code class="inv-code">TG-SEC-007</code>
|
|
1163
|
+
<span class="inv-title">Secret in Test or Example Fixture Without Mock Marker</span>
|
|
1164
|
+
</div>
|
|
1165
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SUPPLY-001" data-layer="layer-platform" data-search="tg-supply-001 missing or ignored dependency lockfile critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SUPPLY-001')" title="Click to inspect security invariant guarantee for TG-SUPPLY-001">
|
|
1166
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1167
|
+
<code class="inv-code">TG-SUPPLY-001</code>
|
|
1168
|
+
<span class="inv-title">Missing or Ignored Dependency Lockfile</span>
|
|
1169
|
+
</div>
|
|
1170
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SUPPLY-002" data-layer="layer-platform" data-search="tg-supply-002 vulnerable dependency review missing critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SUPPLY-002')" title="Click to inspect security invariant guarantee for TG-SUPPLY-002">
|
|
1171
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1172
|
+
<code class="inv-code">TG-SUPPLY-002</code>
|
|
1173
|
+
<span class="inv-title">Vulnerable Dependency Review Missing</span>
|
|
1174
|
+
</div>
|
|
1175
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SUPPLY-003" data-layer="layer-platform" data-search="tg-supply-003 unsafe ci/cd secret exposure critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SUPPLY-003')" title="Click to inspect security invariant guarantee for TG-SUPPLY-003">
|
|
1176
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1177
|
+
<code class="inv-code">TG-SUPPLY-003</code>
|
|
1178
|
+
<span class="inv-title">Unsafe CI/CD Secret Exposure</span>
|
|
1179
|
+
</div>
|
|
1180
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SUPPLY-004" data-layer="layer-platform" data-search="tg-supply-004 unpinned ci action critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SUPPLY-004')" title="Click to inspect security invariant guarantee for TG-SUPPLY-004">
|
|
1181
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1182
|
+
<code class="inv-code">TG-SUPPLY-004</code>
|
|
1183
|
+
<span class="inv-title">Unpinned CI Action</span>
|
|
1184
|
+
</div>
|
|
1185
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SUPPLY-005" data-layer="layer-platform" data-search="tg-supply-005 unsafe dependency install script critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SUPPLY-005')" title="Click to inspect security invariant guarantee for TG-SUPPLY-005">
|
|
1186
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1187
|
+
<code class="inv-code">TG-SUPPLY-005</code>
|
|
1188
|
+
<span class="inv-title">Unsafe Dependency Install Script</span>
|
|
1189
|
+
</div>
|
|
1190
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-SUPPLY-006" data-layer="layer-platform" data-search="tg-supply-006 container build secret persistence high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-SUPPLY-006')" title="Click to inspect security invariant guarantee for TG-SUPPLY-006">
|
|
1191
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1192
|
+
<code class="inv-code">TG-SUPPLY-006</code>
|
|
1193
|
+
<span class="inv-title">Container Build Secret Persistence</span>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CLIENT-001" data-layer="layer-platform" data-search="tg-client-001 public production source maps medium defended platform, supply chain & secrets" onclick="inspectInvariant('TG-CLIENT-001')" title="Click to inspect security invariant guarantee for TG-CLIENT-001">
|
|
1196
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1197
|
+
<code class="inv-code">TG-CLIENT-001</code>
|
|
1198
|
+
<span class="inv-title">Public Production Source Maps</span>
|
|
1199
|
+
</div>
|
|
1200
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-CLIENT-002" data-layer="layer-platform" data-search="tg-client-002 sensitive client bundle content high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-CLIENT-002')" title="Click to inspect security invariant guarantee for TG-CLIENT-002">
|
|
1201
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1202
|
+
<code class="inv-code">TG-CLIENT-002</code>
|
|
1203
|
+
<span class="inv-title">Sensitive Client Bundle Content</span>
|
|
1204
|
+
</div>
|
|
1205
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-PLATFORM-001" data-layer="layer-platform" data-search="tg-platform-001 wildcard cors with credentials high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-PLATFORM-001')" title="Click to inspect security invariant guarantee for TG-PLATFORM-001">
|
|
1206
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1207
|
+
<code class="inv-code">TG-PLATFORM-001</code>
|
|
1208
|
+
<span class="inv-title">Wildcard CORS With Credentials</span>
|
|
1209
|
+
</div>
|
|
1210
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-PLATFORM-002" data-layer="layer-platform" data-search="tg-platform-002 missing security headers medium defended platform, supply chain & secrets" onclick="inspectInvariant('TG-PLATFORM-002')" title="Click to inspect security invariant guarantee for TG-PLATFORM-002">
|
|
1211
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1212
|
+
<code class="inv-code">TG-PLATFORM-002</code>
|
|
1213
|
+
<span class="inv-title">Missing Security Headers</span>
|
|
1214
|
+
</div>
|
|
1215
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-PLATFORM-003" data-layer="layer-platform" data-search="tg-platform-003 production stack trace exposure medium defended platform, supply chain & secrets" onclick="inspectInvariant('TG-PLATFORM-003')" title="Click to inspect security invariant guarantee for TG-PLATFORM-003">
|
|
1216
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1217
|
+
<code class="inv-code">TG-PLATFORM-003</code>
|
|
1218
|
+
<span class="inv-title">Production Stack Trace Exposure</span>
|
|
1219
|
+
</div>
|
|
1220
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-PLATFORM-004" data-layer="layer-platform" data-search="tg-platform-004 missing request size limits medium defended platform, supply chain & secrets" onclick="inspectInvariant('TG-PLATFORM-004')" title="Click to inspect security invariant guarantee for TG-PLATFORM-004">
|
|
1221
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1222
|
+
<code class="inv-code">TG-PLATFORM-004</code>
|
|
1223
|
+
<span class="inv-title">Missing Request Size Limits</span>
|
|
1224
|
+
</div>
|
|
1225
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DIFF-001" data-layer="layer-platform" data-search="tg-diff-001 accidental security check bypass in patch additions critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-DIFF-001')" title="Click to inspect security invariant guarantee for TG-DIFF-001">
|
|
1226
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1227
|
+
<code class="inv-code">TG-DIFF-001</code>
|
|
1228
|
+
<span class="inv-title">Accidental Security Check Bypass in Patch Additions</span>
|
|
1229
|
+
</div>
|
|
1230
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DIFF-002" data-layer="layer-platform" data-search="tg-diff-002 secret and token ingestion in patch additions critical defended platform, supply chain & secrets" onclick="inspectInvariant('TG-DIFF-002')" title="Click to inspect security invariant guarantee for TG-DIFF-002">
|
|
1231
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1232
|
+
<code class="inv-code">TG-DIFF-002</code>
|
|
1233
|
+
<span class="inv-title">Secret and Token Ingestion in Patch Additions</span>
|
|
1234
|
+
</div>
|
|
1235
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-DIFF-003" data-layer="layer-platform" data-search="tg-diff-003 unintentional removal of tenant filter in patch deletions high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-DIFF-003')" title="Click to inspect security invariant guarantee for TG-DIFF-003">
|
|
1236
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1237
|
+
<code class="inv-code">TG-DIFF-003</code>
|
|
1238
|
+
<span class="inv-title">Unintentional Removal of Tenant Filter in Patch Deletions</span>
|
|
1239
|
+
</div>
|
|
1240
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-EDGE-001" data-layer="layer-platform" data-search="tg-edge-001 serverless & edge global state memory leakage high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-EDGE-001')" title="Click to inspect security invariant guarantee for TG-EDGE-001">
|
|
1241
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1242
|
+
<code class="inv-code">TG-EDGE-001</code>
|
|
1243
|
+
<span class="inv-title">Serverless & Edge Global State Memory Leakage</span>
|
|
1244
|
+
</div>
|
|
1245
|
+
<div class="inv-chip inv-chip-safe" data-rule="TG-EDGE-003" data-layer="layer-platform" data-search="tg-edge-003 aws lambda ephemeral execution & cold start security high defended platform, supply chain & secrets" onclick="inspectInvariant('TG-EDGE-003')" title="Click to inspect security invariant guarantee for TG-EDGE-003">
|
|
1246
|
+
<span class="inv-dot" style="background: var(--accent-green);"></span>
|
|
1247
|
+
<code class="inv-code">TG-EDGE-003</code>
|
|
1248
|
+
<span class="inv-title">AWS Lambda Ephemeral Execution & Cold Start Security</span>
|
|
1249
|
+
</div>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
</div>
|
|
1255
|
+
|
|
1256
|
+
<!-- Filter & Search Bar for Findings -->
|
|
1257
|
+
<div class="filter-bar">
|
|
1258
|
+
<div class="filter-pills">
|
|
1259
|
+
<span class="pill-group-label">Severity:</span>
|
|
1260
|
+
<span class="pill active" data-sev-filter="all" onclick="filterBySeverity('all')">All</span>
|
|
1261
|
+
<span class="pill" data-sev-filter="critical" onclick="filterBySeverity('critical')">🔴 Critical (0)</span>
|
|
1262
|
+
<span class="pill" data-sev-filter="high" onclick="filterBySeverity('high')">🟠 High (0)</span>
|
|
1263
|
+
<span class="pill" data-sev-filter="medium" onclick="filterBySeverity('medium')">🟡 Med/Low (0)</span>
|
|
1264
|
+
|
|
1265
|
+
<span class="pill-group-label" style="margin-left: 8px;">Status:</span>
|
|
1266
|
+
<span class="pill active" data-status-filter="all" onclick="filterByStatus('all')">All</span>
|
|
1267
|
+
<span class="pill" data-status-filter="open" onclick="filterByStatus('open')">⚠ Open (0)</span>
|
|
1268
|
+
<span class="pill" data-status-filter="resolved" onclick="filterByStatus('resolved')">✔ Resolved (0)</span>
|
|
1269
|
+
|
|
1270
|
+
<span class="dir-filter-chip" id="dirFilterChip" onclick="clearDirectoryFilter()">
|
|
1271
|
+
Folder: <strong id="dirFilterName"></strong> ×
|
|
1272
|
+
</span>
|
|
1273
|
+
<button class="btn-reset-filters" id="btnResetFilters" onclick="resetAllFilters()">× Reset Filters</button>
|
|
1274
|
+
</div>
|
|
1275
|
+
<div class="search-wrapper">
|
|
1276
|
+
<input type="text" id="searchInput" class="search-input" placeholder="Search rules, titles, files..." oninput="searchFindings()">
|
|
1277
|
+
<button class="search-clear-btn" id="searchClearBtn" onclick="clearSearch()">×</button>
|
|
1278
|
+
</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
<div class="filter-stats-bar">
|
|
1281
|
+
<div>Showing <span id="visibleCount" style="font-weight: 700; color: var(--text-main);">0</span> of <span id="totalCount" style="font-weight: 700; color: var(--text-main);">0</span> cataloged findings</div>
|
|
1282
|
+
<div id="activeFilterSummary" style="font-size: 11px; color: var(--accent-blue);"></div>
|
|
1283
|
+
</div>
|
|
1284
|
+
|
|
1285
|
+
<!-- Findings Table -->
|
|
1286
|
+
<div class="table-container">
|
|
1287
|
+
<table>
|
|
1288
|
+
<thead>
|
|
1289
|
+
<tr>
|
|
1290
|
+
<th style="width: 70px; text-align: center;">Simulate</th>
|
|
1291
|
+
<th style="width: 110px;">Severity</th>
|
|
1292
|
+
<th style="width: 130px;">Rule ID</th>
|
|
1293
|
+
<th>Finding Title</th>
|
|
1294
|
+
<th style="width: 250px;">File & Location</th>
|
|
1295
|
+
<th style="width: 90px;">Confidence</th>
|
|
1296
|
+
<th style="width: 30px;"></th>
|
|
1297
|
+
</tr>
|
|
1298
|
+
</thead>
|
|
1299
|
+
<tbody id="findingsTableBody">
|
|
1300
|
+
<tr><td colspan="7" class="empty-cell">✔ Zero security invariant violations detected. Posture is 100% clean and defended.</td></tr>
|
|
1301
|
+
</tbody>
|
|
1302
|
+
</table>
|
|
1303
|
+
</div>
|
|
1304
|
+
|
|
1305
|
+
<!-- OWASP Top 10 Compliance Radar -->
|
|
1306
|
+
<div class="section-title">🛡 OWASP Top 10 (2021/2026) Compliance Matrix</div>
|
|
1307
|
+
<div class="grid-owasp">
|
|
1308
|
+
|
|
1309
|
+
<div class="owasp-card">
|
|
1310
|
+
<div class="owasp-top">
|
|
1311
|
+
<span class="owasp-title">A01: Broken Access Control</span>
|
|
1312
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1313
|
+
</div>
|
|
1314
|
+
<div class="owasp-bar-bg">
|
|
1315
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
<div class="owasp-card">
|
|
1319
|
+
<div class="owasp-top">
|
|
1320
|
+
<span class="owasp-title">A02: Cryptographic Failures</span>
|
|
1321
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1322
|
+
</div>
|
|
1323
|
+
<div class="owasp-bar-bg">
|
|
1324
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1325
|
+
</div>
|
|
1326
|
+
</div>
|
|
1327
|
+
<div class="owasp-card">
|
|
1328
|
+
<div class="owasp-top">
|
|
1329
|
+
<span class="owasp-title">A03: Injection Defense</span>
|
|
1330
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1331
|
+
</div>
|
|
1332
|
+
<div class="owasp-bar-bg">
|
|
1333
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1334
|
+
</div>
|
|
1335
|
+
</div>
|
|
1336
|
+
<div class="owasp-card">
|
|
1337
|
+
<div class="owasp-top">
|
|
1338
|
+
<span class="owasp-title">A05: Security Configuration</span>
|
|
1339
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1340
|
+
</div>
|
|
1341
|
+
<div class="owasp-bar-bg">
|
|
1342
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1343
|
+
</div>
|
|
1344
|
+
</div>
|
|
1345
|
+
<div class="owasp-card">
|
|
1346
|
+
<div class="owasp-top">
|
|
1347
|
+
<span class="owasp-title">A06: Supply Chain Health</span>
|
|
1348
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1349
|
+
</div>
|
|
1350
|
+
<div class="owasp-bar-bg">
|
|
1351
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1352
|
+
</div>
|
|
1353
|
+
</div>
|
|
1354
|
+
<div class="owasp-card">
|
|
1355
|
+
<div class="owasp-top">
|
|
1356
|
+
<span class="owasp-title">A07: Identity & Auth</span>
|
|
1357
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1358
|
+
</div>
|
|
1359
|
+
<div class="owasp-bar-bg">
|
|
1360
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1361
|
+
</div>
|
|
1362
|
+
</div>
|
|
1363
|
+
<div class="owasp-card">
|
|
1364
|
+
<div class="owasp-top">
|
|
1365
|
+
<span class="owasp-title">A08: Software Integrity</span>
|
|
1366
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1367
|
+
</div>
|
|
1368
|
+
<div class="owasp-bar-bg">
|
|
1369
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1370
|
+
</div>
|
|
1371
|
+
</div>
|
|
1372
|
+
<div class="owasp-card">
|
|
1373
|
+
<div class="owasp-top">
|
|
1374
|
+
<span class="owasp-title">A10: SSRF Defense</span>
|
|
1375
|
+
<span class="owasp-badge" style="color: var(--accent-green); border-color: var(--accent-green);">100% Compliant</span>
|
|
1376
|
+
</div>
|
|
1377
|
+
<div class="owasp-bar-bg">
|
|
1378
|
+
<div class="owasp-bar" style="width: 100%; background: var(--accent-green);"></div>
|
|
1379
|
+
</div>
|
|
1380
|
+
</div>
|
|
1381
|
+
</div>
|
|
1382
|
+
|
|
1383
|
+
<!-- Enterprise Compliance Framework Mapping -->
|
|
1384
|
+
<div class="section-title">🏛 Enterprise Compliance Framework Mapping (SOC 2 · ISO 27001 · HIPAA)</div>
|
|
1385
|
+
<div class="grid-compliance">
|
|
1386
|
+
|
|
1387
|
+
<div class="card comp-card">
|
|
1388
|
+
<div class="comp-card-header">
|
|
1389
|
+
<div>
|
|
1390
|
+
<div class="comp-title">SOC 2 Type II</div>
|
|
1391
|
+
<div class="comp-sub">AICPA Trust Services Criteria (Security & Boundary)</div>
|
|
1392
|
+
</div>
|
|
1393
|
+
<span class="badge badge-success">100% Met</span>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="comp-ctrl-list">
|
|
395
1396
|
|
|
396
|
-
<div class="
|
|
397
|
-
<div
|
|
398
|
-
<
|
|
399
|
-
<
|
|
1397
|
+
<div class="comp-ctrl-row">
|
|
1398
|
+
<div>
|
|
1399
|
+
<div class="comp-ctrl-code">CC6.1 Logical Access</div>
|
|
1400
|
+
<div class="comp-ctrl-fams">TG-AUTH, TG-CLIENT</div>
|
|
1401
|
+
</div>
|
|
1402
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1403
|
+
</div>
|
|
1404
|
+
<div class="comp-ctrl-row">
|
|
1405
|
+
<div>
|
|
1406
|
+
<div class="comp-ctrl-code">CC6.6 Network Boundary</div>
|
|
1407
|
+
<div class="comp-ctrl-fams">TG-SSRF, TG-RATE, TG-WS</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1410
|
+
</div>
|
|
1411
|
+
<div class="comp-ctrl-row">
|
|
1412
|
+
<div>
|
|
1413
|
+
<div class="comp-ctrl-code">CC6.7 Transmission Security</div>
|
|
1414
|
+
<div class="comp-ctrl-fams">TG-PLATFORM, TG-SEC</div>
|
|
400
1415
|
</div>
|
|
401
|
-
<
|
|
402
|
-
<pre class="diff-block"><code>--- a/examples/vulnerable-react-express/client/src/App.jsx
|
|
403
|
-
+++ b/examples/vulnerable-react-express/client/src/App.jsx
|
|
404
|
-
@@ -51,5 +51,5 @@
|
|
405
|
-
|
|
406
|
-
{/* TG-INPUT-003: Unsafe HTML rendering */}
|
|
407
|
-
- <div dangerouslySetInnerHTML={{ __html: bio }} />
|
|
408
|
-
+ <div>{bio}</div>
|
|
409
|
-
|
|
410
|
-
<button onClick={fetchUser}>Fetch user (IDOR demo)</button></code></pre>
|
|
1416
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
411
1417
|
</div>
|
|
1418
|
+
<div class="comp-ctrl-row">
|
|
1419
|
+
<div>
|
|
1420
|
+
<div class="comp-ctrl-code">CC7.1 Vulnerability & Churn</div>
|
|
1421
|
+
<div class="comp-ctrl-fams">TG-DIFF, TG-SUPPLY</div>
|
|
1422
|
+
</div>
|
|
1423
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1424
|
+
</div>
|
|
1425
|
+
</div>
|
|
1426
|
+
</div>
|
|
1427
|
+
<div class="card comp-card">
|
|
1428
|
+
<div class="comp-card-header">
|
|
1429
|
+
<div>
|
|
1430
|
+
<div class="comp-title">ISO/IEC 27001:2022</div>
|
|
1431
|
+
<div class="comp-sub">Annex A Controls for Technological & Application Security</div>
|
|
1432
|
+
</div>
|
|
1433
|
+
<span class="badge badge-success">100% Met</span>
|
|
1434
|
+
</div>
|
|
1435
|
+
<div class="comp-ctrl-list">
|
|
412
1436
|
|
|
413
|
-
<div class="
|
|
414
|
-
<div
|
|
415
|
-
<
|
|
416
|
-
<
|
|
1437
|
+
<div class="comp-ctrl-row">
|
|
1438
|
+
<div>
|
|
1439
|
+
<div class="comp-ctrl-code">A.8.20 Network Security</div>
|
|
1440
|
+
<div class="comp-ctrl-fams">TG-SSRF, TG-WEBHOOK, TG-RATE</div>
|
|
417
1441
|
</div>
|
|
418
|
-
<
|
|
419
|
-
<pre class="diff-block"><code>--- a/examples/vulnerable-react-express/server/index.js
|
|
420
|
-
+++ b/examples/vulnerable-react-express/server/index.js
|
|
421
|
-
@@ -10,5 +10,5 @@
|
|
422
|
-
|
|
423
|
-
// TG-PLATFORM-001: Wildcard CORS with credentials
|
|
424
|
-
-app.use(cors({ origin: '*', credentials: true }));
|
|
425
|
-
+app.use(cors({ origin: process.env.ALLOWED_ORIGIN || 'http://localhost:5173', credentials: true }));
|
|
426
|
-
|
|
427
|
-
// TG-PLATFORM-004: No explicit JSON body size limit</code></pre>
|
|
1442
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
428
1443
|
</div>
|
|
1444
|
+
<div class="comp-ctrl-row">
|
|
1445
|
+
<div>
|
|
1446
|
+
<div class="comp-ctrl-code">A.8.24 Cryptography Usage</div>
|
|
1447
|
+
<div class="comp-ctrl-fams">TG-SEC, TG-AUTH</div>
|
|
1448
|
+
</div>
|
|
1449
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1450
|
+
</div>
|
|
1451
|
+
<div class="comp-ctrl-row">
|
|
1452
|
+
<div>
|
|
1453
|
+
<div class="comp-ctrl-code">A.8.28 Secure Coding</div>
|
|
1454
|
+
<div class="comp-ctrl-fams">TG-INPUT, TG-DB, TG-GQL, TG-AGENT</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1457
|
+
</div>
|
|
1458
|
+
</div>
|
|
1459
|
+
</div>
|
|
1460
|
+
<div class="card comp-card">
|
|
1461
|
+
<div class="comp-card-header">
|
|
1462
|
+
<div>
|
|
1463
|
+
<div class="comp-title">HIPAA Security Rule</div>
|
|
1464
|
+
<div class="comp-sub">45 CFR Part 164 Technical Safeguards for ePHI</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
<span class="badge badge-success">100% Met</span>
|
|
1467
|
+
</div>
|
|
1468
|
+
<div class="comp-ctrl-list">
|
|
429
1469
|
|
|
1470
|
+
<div class="comp-ctrl-row">
|
|
1471
|
+
<div>
|
|
1472
|
+
<div class="comp-ctrl-code">§ 164.312(a)(1) Access Control</div>
|
|
1473
|
+
<div class="comp-ctrl-fams">TG-AUTH, TG-DB (Tenant Isolation)</div>
|
|
1474
|
+
</div>
|
|
1475
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1476
|
+
</div>
|
|
1477
|
+
<div class="comp-ctrl-row">
|
|
1478
|
+
<div>
|
|
1479
|
+
<div class="comp-ctrl-code">§ 164.312(c)(1) ePHI Integrity</div>
|
|
1480
|
+
<div class="comp-ctrl-fams">TG-WEBHOOK, TG-BIZ, TG-DIFF</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1483
|
+
</div>
|
|
1484
|
+
<div class="comp-ctrl-row">
|
|
1485
|
+
<div>
|
|
1486
|
+
<div class="comp-ctrl-code">§ 164.312(e)(1) Transmission Security</div>
|
|
1487
|
+
<div class="comp-ctrl-fams">TG-PLATFORM, TG-SEC, TG-CLIENT</div>
|
|
1488
|
+
</div>
|
|
1489
|
+
<div><span class="badge badge-success">✔ Passing</span></div>
|
|
1490
|
+
</div>
|
|
1491
|
+
</div>
|
|
1492
|
+
</div>
|
|
1493
|
+
</div>
|
|
1494
|
+
|
|
1495
|
+
<!-- Middle Grid: Heatmap + Clusters -->
|
|
1496
|
+
<div class="grid-bottom">
|
|
1497
|
+
<div class="card">
|
|
1498
|
+
<div class="section-title">🔥 Directory Attack Surface Heatmap</div>
|
|
1499
|
+
<div style="font-size: 12px; color: var(--text-muted); margin-bottom: 12px;">Click a folder to filter findings for that directory:</div>
|
|
1500
|
+
<div class="empty-state">✔ Zero directory surface exposure detected across repository modules.</div>
|
|
1501
|
+
</div>
|
|
1502
|
+
|
|
1503
|
+
<div class="card">
|
|
1504
|
+
<div class="section-title">🛠 Root-Cause Architectural Clusters</div>
|
|
1505
|
+
<div class="empty-state">✔ No architectural vulnerability clusters identified. Systemic invariants enforced.</div>
|
|
1506
|
+
</div>
|
|
1507
|
+
</div>
|
|
1508
|
+
|
|
1509
|
+
<!-- Bottom Grid: Golden Recipes + Next Best Defenses -->
|
|
1510
|
+
<div class="grid-bottom">
|
|
1511
|
+
<div class="card">
|
|
1512
|
+
<div class="section-title">✨ Golden Fix Recipes (15)</div>
|
|
1513
|
+
<div style="font-size: 12px; color: var(--text-muted); margin-bottom: 10px;">Verified remediation patterns distilled from applied patches:</div>
|
|
1514
|
+
<div class="filter-pills" style="margin-bottom: 12px;">
|
|
1515
|
+
<span class="pill active" data-recipe-cat-filter="all" onclick="filterRecipeCategory('all')">All (15)</span><span class="pill" data-recipe-cat-filter="client" onclick="filterRecipeCategory('client')">Client (1)</span><span class="pill" data-recipe-cat-filter="database" onclick="filterRecipeCategory('database')">Database (1)</span><span class="pill" data-recipe-cat-filter="input" onclick="filterRecipeCategory('input')">Input (2)</span><span class="pill" data-recipe-cat-filter="platform" onclick="filterRecipeCategory('platform')">Platform (3)</span><span class="pill" data-recipe-cat-filter="rate-limiting" onclick="filterRecipeCategory('rate-limiting')">Rate Limiting (2)</span><span class="pill" data-recipe-cat-filter="ssrf" onclick="filterRecipeCategory('ssrf')">SSRF (1)</span><span class="pill" data-recipe-cat-filter="secrets" onclick="filterRecipeCategory('secrets')">Secrets (5)</span>
|
|
1516
|
+
</div>
|
|
1517
|
+
<div id="recipeContainer">
|
|
1518
|
+
|
|
1519
|
+
<div class="recipe-card" data-category="secrets">
|
|
1520
|
+
<div class="recipe-header">
|
|
1521
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1522
|
+
<span class="badge badge-success">TG-SEC-001</span>
|
|
1523
|
+
<strong>Migrated secret to environment variable</strong>
|
|
1524
|
+
</div>
|
|
1525
|
+
<div class="recipe-meta-badges">
|
|
1526
|
+
<span class="chip chip-filetype">.py</span>
|
|
1527
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1528
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1529
|
+
</div>
|
|
1530
|
+
</div>
|
|
1531
|
+
<div class="recipe-view-switcher">
|
|
1532
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-SEC-001-6dd70f8d" onclick="setRecipeView('recipe-TG-SEC-001-6dd70f8d', 'diff')">Unified Diff</button>
|
|
1533
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-SEC-001-6dd70f8d" onclick="setRecipeView('recipe-TG-SEC-001-6dd70f8d', 'split')">Before / After</button>
|
|
1534
|
+
</div>
|
|
1535
|
+
<div id="recipe-view-diff-recipe-TG-SEC-001-6dd70f8d" class="code-preview recipe-diff-container">
|
|
1536
|
+
<pre><code><span class="diff-line-add">+ SECRET = env</span>
|
|
1537
|
+
<span class="diff-line-del">- SECRET = test</span></code></pre>
|
|
1538
|
+
</div>
|
|
1539
|
+
<div id="recipe-view-split-recipe-TG-SEC-001-6dd70f8d" class="recipe-split-container" style="display: none;">
|
|
1540
|
+
<div class="split-pane">
|
|
1541
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1542
|
+
<div class="code-preview"><pre><code>SECRET = test</code></pre></div>
|
|
1543
|
+
</div>
|
|
1544
|
+
<div class="split-pane">
|
|
1545
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1546
|
+
<div class="code-preview"><pre><code>SECRET = env</code></pre></div>
|
|
1547
|
+
</div>
|
|
1548
|
+
</div>
|
|
1549
|
+
<div class="recipe-actions">
|
|
1550
|
+
<div class="cmd-box">
|
|
1551
|
+
<code>npx torusguard harden --rule TG-SEC-001</code>
|
|
1552
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SEC-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1553
|
+
</div>
|
|
1554
|
+
<button class="btn btn-sm" onclick="copyText('SECRET = env', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1555
|
+
</div>
|
|
1556
|
+
</div>
|
|
1557
|
+
<div class="recipe-card" data-category="client">
|
|
1558
|
+
<div class="recipe-header">
|
|
1559
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1560
|
+
<span class="badge badge-success">TG-CLIENT-001</span>
|
|
1561
|
+
<strong>Disabled public production source map emission</strong>
|
|
1562
|
+
</div>
|
|
1563
|
+
<div class="recipe-meta-badges">
|
|
1564
|
+
<span class="chip chip-filetype">.js</span>
|
|
1565
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1566
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1567
|
+
</div>
|
|
1568
|
+
</div>
|
|
1569
|
+
<div class="recipe-view-switcher">
|
|
1570
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-CLIENT-001-dea07bb1" onclick="setRecipeView('recipe-TG-CLIENT-001-dea07bb1', 'diff')">Unified Diff</button>
|
|
1571
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-CLIENT-001-dea07bb1" onclick="setRecipeView('recipe-TG-CLIENT-001-dea07bb1', 'split')">Before / After</button>
|
|
1572
|
+
</div>
|
|
1573
|
+
<div id="recipe-view-diff-recipe-TG-CLIENT-001-dea07bb1" class="code-preview recipe-diff-container">
|
|
1574
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/client/vite.config.js</span>
|
|
1575
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/client/vite.config.js</span>
|
|
1576
|
+
<span class="diff-line-hunk">@@ -5,5 +5,5 @@</span>
|
|
1577
|
+
<span class="diff-line-ctx"> export default defineConfig({</span>
|
|
1578
|
+
<span class="diff-line-ctx"> plugins: [react()],</span>
|
|
1579
|
+
<span class="diff-line-del">- build: { sourcemap: true },</span>
|
|
1580
|
+
<span class="diff-line-add">+ build: { sourcemap: false },</span>
|
|
1581
|
+
<span class="diff-line-ctx"> server: {</span>
|
|
1582
|
+
<span class="diff-line-ctx"> port: 5173,</span></code></pre>
|
|
1583
|
+
</div>
|
|
1584
|
+
<div id="recipe-view-split-recipe-TG-CLIENT-001-dea07bb1" class="recipe-split-container" style="display: none;">
|
|
1585
|
+
<div class="split-pane">
|
|
1586
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1587
|
+
<div class="code-preview"><pre><code>build: { sourcemap: false },</code></pre></div>
|
|
1588
|
+
</div>
|
|
1589
|
+
<div class="split-pane">
|
|
1590
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1591
|
+
<div class="code-preview"><pre><code>Disabled public production source map emission</code></pre></div>
|
|
1592
|
+
</div>
|
|
1593
|
+
</div>
|
|
1594
|
+
<div class="recipe-actions">
|
|
1595
|
+
<div class="cmd-box">
|
|
1596
|
+
<code>npx torusguard harden --rule TG-CLIENT-001</code>
|
|
1597
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-CLIENT-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1598
|
+
</div>
|
|
1599
|
+
<button class="btn btn-sm" onclick="copyText('Disabled public production source map emission', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1600
|
+
</div>
|
|
1601
|
+
</div>
|
|
1602
|
+
<div class="recipe-card" data-category="database">
|
|
1603
|
+
<div class="recipe-header">
|
|
1604
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1605
|
+
<span class="badge badge-success">TG-DB-002</span>
|
|
1606
|
+
<strong>Replaced hardcoded privileged database credential with process.env.SUPABASE_SERVICE_ROLE_KEY</strong>
|
|
1607
|
+
</div>
|
|
1608
|
+
<div class="recipe-meta-badges">
|
|
1609
|
+
<span class="chip chip-filetype">.ts</span>
|
|
1610
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1611
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1612
|
+
</div>
|
|
1613
|
+
</div>
|
|
1614
|
+
<div class="recipe-view-switcher">
|
|
1615
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-DB-002-9c0df9f8" onclick="setRecipeView('recipe-TG-DB-002-9c0df9f8', 'diff')">Unified Diff</button>
|
|
1616
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-DB-002-9c0df9f8" onclick="setRecipeView('recipe-TG-DB-002-9c0df9f8', 'split')">Before / After</button>
|
|
1617
|
+
</div>
|
|
1618
|
+
<div id="recipe-view-diff-recipe-TG-DB-002-9c0df9f8" class="code-preview recipe-diff-container">
|
|
1619
|
+
<pre><code><span class="diff-line-header">--- a/demo/playground/vulnerable_nextjs/actions.ts</span>
|
|
1620
|
+
<span class="diff-line-header">+++ b/demo/playground/vulnerable_nextjs/actions.ts</span>
|
|
1621
|
+
<span class="diff-line-hunk">@@ -7,5 +7,5 @@</span>
|
|
1622
|
+
<span class="diff-line-ctx"> </span>
|
|
1623
|
+
<span class="diff-line-ctx"> // Finding 1: TG-CLIENT-001 - Backend admin key exposed in client-accessible code</span>
|
|
1624
|
+
<span class="diff-line-del">-export const SUPABASE_SERVICE_ROLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.demo_secret_token_1234567890";</span>
|
|
1625
|
+
<span class="diff-line-add">+export const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || "";</span>
|
|
1626
|
+
<span class="diff-line-ctx"> </span>
|
|
1627
|
+
<span class="diff-line-ctx"> interface UserRecord {</span></code></pre>
|
|
1628
|
+
</div>
|
|
1629
|
+
<div id="recipe-view-split-recipe-TG-DB-002-9c0df9f8" class="recipe-split-container" style="display: none;">
|
|
1630
|
+
<div class="split-pane">
|
|
1631
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1632
|
+
<div class="code-preview"><pre><code>export const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || "";</code></pre></div>
|
|
1633
|
+
</div>
|
|
1634
|
+
<div class="split-pane">
|
|
1635
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1636
|
+
<div class="code-preview"><pre><code>Replaced hardcoded privileged database credential with process.env.SUPABASE_SERVICE_ROLE_KEY</code></pre></div>
|
|
1637
|
+
</div>
|
|
1638
|
+
</div>
|
|
1639
|
+
<div class="recipe-actions">
|
|
1640
|
+
<div class="cmd-box">
|
|
1641
|
+
<code>npx torusguard harden --rule TG-DB-002</code>
|
|
1642
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-DB-002', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1643
|
+
</div>
|
|
1644
|
+
<button class="btn btn-sm" onclick="copyText('Replaced hardcoded privileged database credential with process.env.SUPABASE_SERVICE_ROLE_KEY', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1645
|
+
</div>
|
|
1646
|
+
</div>
|
|
1647
|
+
<div class="recipe-card" data-category="input">
|
|
1648
|
+
<div class="recipe-header">
|
|
1649
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1650
|
+
<span class="badge badge-success">TG-INPUT-001</span>
|
|
1651
|
+
<strong>Added defensive request body payload validation before destructuring</strong>
|
|
1652
|
+
</div>
|
|
1653
|
+
<div class="recipe-meta-badges">
|
|
1654
|
+
<span class="chip chip-filetype">.js</span>
|
|
1655
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1656
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1657
|
+
</div>
|
|
1658
|
+
</div>
|
|
1659
|
+
<div class="recipe-view-switcher">
|
|
1660
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-INPUT-001-9bd39e1a" onclick="setRecipeView('recipe-TG-INPUT-001-9bd39e1a', 'diff')">Unified Diff</button>
|
|
1661
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-INPUT-001-9bd39e1a" onclick="setRecipeView('recipe-TG-INPUT-001-9bd39e1a', 'split')">Before / After</button>
|
|
1662
|
+
</div>
|
|
1663
|
+
<div id="recipe-view-diff-recipe-TG-INPUT-001-9bd39e1a" class="code-preview recipe-diff-container">
|
|
1664
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
1665
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
1666
|
+
<span class="diff-line-hunk">@@ -25,5 +25,5 @@</span>
|
|
1667
|
+
<span class="diff-line-ctx"> // TG-AUTH-004: Insecure cookie (no httpOnly/Secure/SameSite)</span>
|
|
1668
|
+
<span class="diff-line-ctx"> app.post('/api/login', (req, res) => {</span>
|
|
1669
|
+
<span class="diff-line-del">- const { email, password } = req.body;</span>
|
|
1670
|
+
<span class="diff-line-add">+ if (!req.body || typeof req.body !== "object") return res.status(400).json({ error: "Invalid payload" });</span>
|
|
1671
|
+
<span class="diff-line-ctx"> const { email, password } = req.body;</span>
|
|
1672
|
+
<span class="diff-line-ctx"> console.log('Login attempt:', { email, password }); // TG-SEC-004</span>
|
|
1673
|
+
<span class="diff-line-ctx"> const user = users.find((u) => u.email === email && u.password === password);</span></code></pre>
|
|
1674
|
+
</div>
|
|
1675
|
+
<div id="recipe-view-split-recipe-TG-INPUT-001-9bd39e1a" class="recipe-split-container" style="display: none;">
|
|
1676
|
+
<div class="split-pane">
|
|
1677
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1678
|
+
<div class="code-preview"><pre><code>if (!req.body || typeof req.body !== "object") return res.status(400).json({ error: "Invalid payload" });</code></pre></div>
|
|
1679
|
+
</div>
|
|
1680
|
+
<div class="split-pane">
|
|
1681
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1682
|
+
<div class="code-preview"><pre><code>Added defensive request body payload validation before destructuring</code></pre></div>
|
|
1683
|
+
</div>
|
|
1684
|
+
</div>
|
|
1685
|
+
<div class="recipe-actions">
|
|
1686
|
+
<div class="cmd-box">
|
|
1687
|
+
<code>npx torusguard harden --rule TG-INPUT-001</code>
|
|
1688
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-INPUT-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1689
|
+
</div>
|
|
1690
|
+
<button class="btn btn-sm" onclick="copyText('Added defensive request body payload validation before destructuring', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1691
|
+
</div>
|
|
1692
|
+
</div>
|
|
1693
|
+
<div class="recipe-card" data-category="input">
|
|
1694
|
+
<div class="recipe-header">
|
|
1695
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1696
|
+
<span class="badge badge-success">TG-INPUT-003</span>
|
|
1697
|
+
<strong>Replaced raw dangerouslySetInnerHTML injection with safe React text interpolation</strong>
|
|
1698
|
+
</div>
|
|
1699
|
+
<div class="recipe-meta-badges">
|
|
1700
|
+
<span class="chip chip-filetype">.jsx</span>
|
|
1701
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1702
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1703
|
+
</div>
|
|
1704
|
+
</div>
|
|
1705
|
+
<div class="recipe-view-switcher">
|
|
1706
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-INPUT-003-5845c0ed" onclick="setRecipeView('recipe-TG-INPUT-003-5845c0ed', 'diff')">Unified Diff</button>
|
|
1707
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-INPUT-003-5845c0ed" onclick="setRecipeView('recipe-TG-INPUT-003-5845c0ed', 'split')">Before / After</button>
|
|
1708
|
+
</div>
|
|
1709
|
+
<div id="recipe-view-diff-recipe-TG-INPUT-003-5845c0ed" class="code-preview recipe-diff-container">
|
|
1710
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/client/src/App.jsx</span>
|
|
1711
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/client/src/App.jsx</span>
|
|
1712
|
+
<span class="diff-line-hunk">@@ -51,5 +51,5 @@</span>
|
|
1713
|
+
<span class="diff-line-ctx"> </span>
|
|
1714
|
+
<span class="diff-line-ctx"> {/* TG-INPUT-003: Unsafe HTML rendering */}</span>
|
|
1715
|
+
<span class="diff-line-del">- <div dangerouslySetInnerHTML={{ __html: bio }} /></span>
|
|
1716
|
+
<span class="diff-line-add">+ <div>{bio}</div></span>
|
|
1717
|
+
<span class="diff-line-ctx"> </span>
|
|
1718
|
+
<span class="diff-line-ctx"> <button onClick={fetchUser}>Fetch user (IDOR demo)</button></span></code></pre>
|
|
1719
|
+
</div>
|
|
1720
|
+
<div id="recipe-view-split-recipe-TG-INPUT-003-5845c0ed" class="recipe-split-container" style="display: none;">
|
|
1721
|
+
<div class="split-pane">
|
|
1722
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1723
|
+
<div class="code-preview"><pre><code><div>{bio}</div></code></pre></div>
|
|
1724
|
+
</div>
|
|
1725
|
+
<div class="split-pane">
|
|
1726
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1727
|
+
<div class="code-preview"><pre><code>Replaced raw dangerouslySetInnerHTML injection with safe React text interpolation</code></pre></div>
|
|
1728
|
+
</div>
|
|
1729
|
+
</div>
|
|
1730
|
+
<div class="recipe-actions">
|
|
1731
|
+
<div class="cmd-box">
|
|
1732
|
+
<code>npx torusguard harden --rule TG-INPUT-003</code>
|
|
1733
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-INPUT-003', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1734
|
+
</div>
|
|
1735
|
+
<button class="btn btn-sm" onclick="copyText('Replaced raw dangerouslySetInnerHTML injection with safe React text interpolation', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1736
|
+
</div>
|
|
1737
|
+
</div>
|
|
1738
|
+
<div class="recipe-card" data-category="platform">
|
|
1739
|
+
<div class="recipe-header">
|
|
1740
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1741
|
+
<span class="badge badge-success">TG-PLATFORM-001</span>
|
|
1742
|
+
<strong>Constrained wildcard CORS origin to process.env.ALLOWED_ORIGIN with secure fallback</strong>
|
|
1743
|
+
</div>
|
|
1744
|
+
<div class="recipe-meta-badges">
|
|
1745
|
+
<span class="chip chip-filetype">.js</span>
|
|
1746
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1747
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1748
|
+
</div>
|
|
1749
|
+
</div>
|
|
1750
|
+
<div class="recipe-view-switcher">
|
|
1751
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-PLATFORM-001-4b517a2b" onclick="setRecipeView('recipe-TG-PLATFORM-001-4b517a2b', 'diff')">Unified Diff</button>
|
|
1752
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-PLATFORM-001-4b517a2b" onclick="setRecipeView('recipe-TG-PLATFORM-001-4b517a2b', 'split')">Before / After</button>
|
|
1753
|
+
</div>
|
|
1754
|
+
<div id="recipe-view-diff-recipe-TG-PLATFORM-001-4b517a2b" class="code-preview recipe-diff-container">
|
|
1755
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
1756
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
1757
|
+
<span class="diff-line-hunk">@@ -10,5 +10,5 @@</span>
|
|
1758
|
+
<span class="diff-line-ctx"> </span>
|
|
1759
|
+
<span class="diff-line-ctx"> // TG-PLATFORM-001: Wildcard CORS with credentials</span>
|
|
1760
|
+
<span class="diff-line-del">-app.use(cors({ origin: '*', credentials: true }));</span>
|
|
1761
|
+
<span class="diff-line-add">+app.use(cors({ origin: process.env.ALLOWED_ORIGIN || 'http://localhost:5173', credentials: true }));</span>
|
|
1762
|
+
<span class="diff-line-ctx"> </span>
|
|
1763
|
+
<span class="diff-line-ctx"> // TG-PLATFORM-004: No explicit JSON body size limit</span></code></pre>
|
|
1764
|
+
</div>
|
|
1765
|
+
<div id="recipe-view-split-recipe-TG-PLATFORM-001-4b517a2b" class="recipe-split-container" style="display: none;">
|
|
1766
|
+
<div class="split-pane">
|
|
1767
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1768
|
+
<div class="code-preview"><pre><code>app.use(cors({ origin: process.env.ALLOWED_ORIGIN || 'http://localhost:5173', credentials: true }));</code></pre></div>
|
|
1769
|
+
</div>
|
|
1770
|
+
<div class="split-pane">
|
|
1771
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1772
|
+
<div class="code-preview"><pre><code>Constrained wildcard CORS origin to process.env.ALLOWED_ORIGIN with secure fallback</code></pre></div>
|
|
1773
|
+
</div>
|
|
1774
|
+
</div>
|
|
1775
|
+
<div class="recipe-actions">
|
|
1776
|
+
<div class="cmd-box">
|
|
1777
|
+
<code>npx torusguard harden --rule TG-PLATFORM-001</code>
|
|
1778
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-PLATFORM-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1779
|
+
</div>
|
|
1780
|
+
<button class="btn btn-sm" onclick="copyText('Constrained wildcard CORS origin to process.env.ALLOWED_ORIGIN with secure fallback', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1781
|
+
</div>
|
|
1782
|
+
</div>
|
|
1783
|
+
<div class="recipe-card" data-category="platform">
|
|
1784
|
+
<div class="recipe-header">
|
|
1785
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1786
|
+
<span class="badge badge-success">TG-PLATFORM-002</span>
|
|
1787
|
+
<strong>Injected helmet security headers middleware into Express application</strong>
|
|
1788
|
+
</div>
|
|
1789
|
+
<div class="recipe-meta-badges">
|
|
1790
|
+
<span class="chip chip-filetype">.js</span>
|
|
1791
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1792
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1793
|
+
</div>
|
|
1794
|
+
</div>
|
|
1795
|
+
<div class="recipe-view-switcher">
|
|
1796
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-PLATFORM-002-232b12a1" onclick="setRecipeView('recipe-TG-PLATFORM-002-232b12a1', 'diff')">Unified Diff</button>
|
|
1797
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-PLATFORM-002-232b12a1" onclick="setRecipeView('recipe-TG-PLATFORM-002-232b12a1', 'split')">Before / After</button>
|
|
1798
|
+
</div>
|
|
1799
|
+
<div id="recipe-view-diff-recipe-TG-PLATFORM-002-232b12a1" class="code-preview recipe-diff-container">
|
|
1800
|
+
<pre><code><span class="diff-line-header">--- a/examples/hardened-react-express/server/index.js</span>
|
|
1801
|
+
<span class="diff-line-header">+++ b/examples/hardened-react-express/server/index.js</span>
|
|
1802
|
+
<span class="diff-line-hunk">@@ -15,5 +15,5 @@</span>
|
|
1803
|
+
<span class="diff-line-ctx"> }</span>
|
|
1804
|
+
<span class="diff-line-ctx"> </span>
|
|
1805
|
+
<span class="diff-line-del">-const app = express();</span>
|
|
1806
|
+
<span class="diff-line-add">+const helmet = require("helmet");</span>
|
|
1807
|
+
<span class="diff-line-ctx">const app = express();</span>
|
|
1808
|
+
<span class="diff-line-ctx">app.use(helmet());</span>
|
|
1809
|
+
<span class="diff-line-ctx"> const PORT = 3001;</span>
|
|
1810
|
+
<span class="diff-line-ctx"> const isProd = process.env.NODE_ENV === 'production';</span></code></pre>
|
|
1811
|
+
</div>
|
|
1812
|
+
<div id="recipe-view-split-recipe-TG-PLATFORM-002-232b12a1" class="recipe-split-container" style="display: none;">
|
|
1813
|
+
<div class="split-pane">
|
|
1814
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1815
|
+
<div class="code-preview"><pre><code>const helmet = require("helmet");</code></pre></div>
|
|
1816
|
+
</div>
|
|
1817
|
+
<div class="split-pane">
|
|
1818
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1819
|
+
<div class="code-preview"><pre><code>Injected helmet security headers middleware into Express application</code></pre></div>
|
|
1820
|
+
</div>
|
|
1821
|
+
</div>
|
|
1822
|
+
<div class="recipe-actions">
|
|
1823
|
+
<div class="cmd-box">
|
|
1824
|
+
<code>npx torusguard harden --rule TG-PLATFORM-002</code>
|
|
1825
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-PLATFORM-002', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1826
|
+
</div>
|
|
1827
|
+
<button class="btn btn-sm" onclick="copyText('Injected helmet security headers middleware into Express application', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1828
|
+
</div>
|
|
1829
|
+
</div>
|
|
1830
|
+
<div class="recipe-card" data-category="platform">
|
|
1831
|
+
<div class="recipe-header">
|
|
1832
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1833
|
+
<span class="badge badge-success">TG-PLATFORM-002</span>
|
|
1834
|
+
<strong>Injected helmet security headers middleware into Express application</strong>
|
|
1835
|
+
</div>
|
|
1836
|
+
<div class="recipe-meta-badges">
|
|
1837
|
+
<span class="chip chip-filetype">.js</span>
|
|
1838
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1839
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1840
|
+
</div>
|
|
1841
|
+
</div>
|
|
1842
|
+
<div class="recipe-view-switcher">
|
|
1843
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-PLATFORM-002-2b23c6e4" onclick="setRecipeView('recipe-TG-PLATFORM-002-2b23c6e4', 'diff')">Unified Diff</button>
|
|
1844
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-PLATFORM-002-2b23c6e4" onclick="setRecipeView('recipe-TG-PLATFORM-002-2b23c6e4', 'split')">Before / After</button>
|
|
1845
|
+
</div>
|
|
1846
|
+
<div id="recipe-view-diff-recipe-TG-PLATFORM-002-2b23c6e4" class="code-preview recipe-diff-container">
|
|
1847
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
1848
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
1849
|
+
<span class="diff-line-hunk">@@ -3,5 +3,5 @@</span>
|
|
1850
|
+
<span class="diff-line-ctx"> const jwt = require('jsonwebtoken');</span>
|
|
1851
|
+
<span class="diff-line-ctx"> </span>
|
|
1852
|
+
<span class="diff-line-del">-const app = express();</span>
|
|
1853
|
+
<span class="diff-line-add">+const helmet = require("helmet");</span>
|
|
1854
|
+
<span class="diff-line-ctx">const app = express();</span>
|
|
1855
|
+
<span class="diff-line-ctx">app.use(helmet());</span>
|
|
1856
|
+
<span class="diff-line-ctx"> const PORT = 3001;</span></code></pre>
|
|
1857
|
+
</div>
|
|
1858
|
+
<div id="recipe-view-split-recipe-TG-PLATFORM-002-2b23c6e4" class="recipe-split-container" style="display: none;">
|
|
1859
|
+
<div class="split-pane">
|
|
1860
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1861
|
+
<div class="code-preview"><pre><code>const helmet = require("helmet");</code></pre></div>
|
|
1862
|
+
</div>
|
|
1863
|
+
<div class="split-pane">
|
|
1864
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1865
|
+
<div class="code-preview"><pre><code>Injected helmet security headers middleware into Express application</code></pre></div>
|
|
1866
|
+
</div>
|
|
1867
|
+
</div>
|
|
1868
|
+
<div class="recipe-actions">
|
|
1869
|
+
<div class="cmd-box">
|
|
1870
|
+
<code>npx torusguard harden --rule TG-PLATFORM-002</code>
|
|
1871
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-PLATFORM-002', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1872
|
+
</div>
|
|
1873
|
+
<button class="btn btn-sm" onclick="copyText('Injected helmet security headers middleware into Express application', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1874
|
+
</div>
|
|
1875
|
+
</div>
|
|
1876
|
+
<div class="recipe-card" data-category="rate-limiting">
|
|
1877
|
+
<div class="recipe-header">
|
|
1878
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1879
|
+
<span class="badge badge-success">TG-RATE-001</span>
|
|
1880
|
+
<strong>Injected authLimiter rate-limiting middleware to protect authentication endpoint from brute force</strong>
|
|
1881
|
+
</div>
|
|
1882
|
+
<div class="recipe-meta-badges">
|
|
1883
|
+
<span class="chip chip-filetype">.js</span>
|
|
1884
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1885
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1886
|
+
</div>
|
|
1887
|
+
</div>
|
|
1888
|
+
<div class="recipe-view-switcher">
|
|
1889
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-RATE-001-418c3c0b" onclick="setRecipeView('recipe-TG-RATE-001-418c3c0b', 'diff')">Unified Diff</button>
|
|
1890
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-RATE-001-418c3c0b" onclick="setRecipeView('recipe-TG-RATE-001-418c3c0b', 'split')">Before / After</button>
|
|
1891
|
+
</div>
|
|
1892
|
+
<div id="recipe-view-diff-recipe-TG-RATE-001-418c3c0b" class="code-preview recipe-diff-container">
|
|
1893
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
1894
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
1895
|
+
<span class="diff-line-hunk">@@ -24,5 +24,5 @@</span>
|
|
1896
|
+
<span class="diff-line-ctx"> // TG-SEC-004: Logs sensitive data</span>
|
|
1897
|
+
<span class="diff-line-ctx"> // TG-AUTH-004: Insecure cookie (no httpOnly/Secure/SameSite)</span>
|
|
1898
|
+
<span class="diff-line-del">-app.post('/api/login', (req, res) => {</span>
|
|
1899
|
+
<span class="diff-line-add">+app.post('/api/login', authLimiter, (req, res) => {</span>
|
|
1900
|
+
<span class="diff-line-ctx"> const { email, password } = req.body;</span>
|
|
1901
|
+
<span class="diff-line-ctx"> console.log('Login attempt:', { email, password }); // TG-SEC-004</span></code></pre>
|
|
1902
|
+
</div>
|
|
1903
|
+
<div id="recipe-view-split-recipe-TG-RATE-001-418c3c0b" class="recipe-split-container" style="display: none;">
|
|
1904
|
+
<div class="split-pane">
|
|
1905
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1906
|
+
<div class="code-preview"><pre><code>// TG-SEC-004: Logs sensitive data</code></pre></div>
|
|
1907
|
+
</div>
|
|
1908
|
+
<div class="split-pane">
|
|
1909
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1910
|
+
<div class="code-preview"><pre><code>Injected authLimiter rate-limiting middleware to protect authentication endpoint from brute force</code></pre></div>
|
|
1911
|
+
</div>
|
|
1912
|
+
</div>
|
|
1913
|
+
<div class="recipe-actions">
|
|
1914
|
+
<div class="cmd-box">
|
|
1915
|
+
<code>npx torusguard harden --rule TG-RATE-001</code>
|
|
1916
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-RATE-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1917
|
+
</div>
|
|
1918
|
+
<button class="btn btn-sm" onclick="copyText('Injected authLimiter rate-limiting middleware to protect authentication endpoint from brute force', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1919
|
+
</div>
|
|
1920
|
+
</div>
|
|
1921
|
+
<div class="recipe-card" data-category="rate-limiting">
|
|
1922
|
+
<div class="recipe-header">
|
|
1923
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1924
|
+
<span class="badge badge-success">TG-RATE-002</span>
|
|
1925
|
+
<strong>Injected writeLimiter rate-limiting middleware to public write endpoint</strong>
|
|
1926
|
+
</div>
|
|
1927
|
+
<div class="recipe-meta-badges">
|
|
1928
|
+
<span class="chip chip-filetype">.js</span>
|
|
1929
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1930
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1931
|
+
</div>
|
|
1932
|
+
</div>
|
|
1933
|
+
<div class="recipe-view-switcher">
|
|
1934
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-RATE-002-ef97e8da" onclick="setRecipeView('recipe-TG-RATE-002-ef97e8da', 'diff')">Unified Diff</button>
|
|
1935
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-RATE-002-ef97e8da" onclick="setRecipeView('recipe-TG-RATE-002-ef97e8da', 'split')">Before / After</button>
|
|
1936
|
+
</div>
|
|
1937
|
+
<div id="recipe-view-diff-recipe-TG-RATE-002-ef97e8da" class="code-preview recipe-diff-container">
|
|
1938
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
1939
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
1940
|
+
<span class="diff-line-hunk">@@ -52,5 +52,5 @@</span>
|
|
1941
|
+
<span class="diff-line-ctx"> // TG-INPUT-001: No validation</span>
|
|
1942
|
+
<span class="diff-line-ctx"> // TG-RATE-002: Unlimited contact endpoint</span>
|
|
1943
|
+
<span class="diff-line-del">-app.post('/api/contact', (req, res) => {</span>
|
|
1944
|
+
<span class="diff-line-add">+app.post('/api/contact', writeLimiter, (req, res) => {</span>
|
|
1945
|
+
<span class="diff-line-ctx"> res.json({ received: req.body });</span>
|
|
1946
|
+
<span class="diff-line-ctx"> });</span></code></pre>
|
|
1947
|
+
</div>
|
|
1948
|
+
<div id="recipe-view-split-recipe-TG-RATE-002-ef97e8da" class="recipe-split-container" style="display: none;">
|
|
1949
|
+
<div class="split-pane">
|
|
1950
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1951
|
+
<div class="code-preview"><pre><code>// Vulnerable snippet</code></pre></div>
|
|
1952
|
+
</div>
|
|
1953
|
+
<div class="split-pane">
|
|
1954
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
1955
|
+
<div class="code-preview"><pre><code>Injected writeLimiter rate-limiting middleware to public write endpoint</code></pre></div>
|
|
1956
|
+
</div>
|
|
1957
|
+
</div>
|
|
1958
|
+
<div class="recipe-actions">
|
|
1959
|
+
<div class="cmd-box">
|
|
1960
|
+
<code>npx torusguard harden --rule TG-RATE-002</code>
|
|
1961
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-RATE-002', this); event.stopPropagation();">📋 Copy Command</button>
|
|
1962
|
+
</div>
|
|
1963
|
+
<button class="btn btn-sm" onclick="copyText('Injected writeLimiter rate-limiting middleware to public write endpoint', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
1964
|
+
</div>
|
|
1965
|
+
</div>
|
|
1966
|
+
<div class="recipe-card" data-category="secrets">
|
|
1967
|
+
<div class="recipe-header">
|
|
1968
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
1969
|
+
<span class="badge badge-success">TG-SEC-001</span>
|
|
1970
|
+
<strong>Replaced hardcoded password with environment variable process.env.DEMO_USER_PASSWORD</strong>
|
|
1971
|
+
</div>
|
|
1972
|
+
<div class="recipe-meta-badges">
|
|
1973
|
+
<span class="chip chip-filetype">.js</span>
|
|
1974
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
1975
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
1976
|
+
</div>
|
|
1977
|
+
</div>
|
|
1978
|
+
<div class="recipe-view-switcher">
|
|
1979
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-SEC-001-0bdfafb6" onclick="setRecipeView('recipe-TG-SEC-001-0bdfafb6', 'diff')">Unified Diff</button>
|
|
1980
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-SEC-001-0bdfafb6" onclick="setRecipeView('recipe-TG-SEC-001-0bdfafb6', 'split')">Before / After</button>
|
|
1981
|
+
</div>
|
|
1982
|
+
<div id="recipe-view-diff-recipe-TG-SEC-001-0bdfafb6" class="code-preview recipe-diff-container">
|
|
1983
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
1984
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
1985
|
+
<span class="diff-line-hunk">@@ -16,5 +16,5 @@</span>
|
|
1986
|
+
<span class="diff-line-ctx"> </span>
|
|
1987
|
+
<span class="diff-line-ctx"> const users = [</span>
|
|
1988
|
+
<span class="diff-line-del">- { id: '1', email: 'alice@demo.local', password: 'password123', name: 'Alice' },</span>
|
|
1989
|
+
<span class="diff-line-add">+ { id: '1', email: 'alice@demo.local', password: process.env.DEMO_USER_PASSWORD || "REDACTED_PASSWORD", name: 'Alice' },</span>
|
|
1990
|
+
<span class="diff-line-ctx"> { id: '2', email: 'bob@demo.local', password: 'secret456', name: 'Bob' },</span>
|
|
1991
|
+
<span class="diff-line-ctx"> ];</span></code></pre>
|
|
1992
|
+
</div>
|
|
1993
|
+
<div id="recipe-view-split-recipe-TG-SEC-001-0bdfafb6" class="recipe-split-container" style="display: none;">
|
|
1994
|
+
<div class="split-pane">
|
|
1995
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
1996
|
+
<div class="code-preview"><pre><code>// Vulnerable snippet</code></pre></div>
|
|
1997
|
+
</div>
|
|
1998
|
+
<div class="split-pane">
|
|
1999
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
2000
|
+
<div class="code-preview"><pre><code>Replaced hardcoded password with environment variable process.env.DEMO_USER_PASSWORD</code></pre></div>
|
|
2001
|
+
</div>
|
|
2002
|
+
</div>
|
|
2003
|
+
<div class="recipe-actions">
|
|
2004
|
+
<div class="cmd-box">
|
|
2005
|
+
<code>npx torusguard harden --rule TG-SEC-001</code>
|
|
2006
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SEC-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
2007
|
+
</div>
|
|
2008
|
+
<button class="btn btn-sm" onclick="copyText('Replaced hardcoded password with environment variable process.env.DEMO_USER_PASSWORD', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
2009
|
+
</div>
|
|
2010
|
+
</div>
|
|
2011
|
+
<div class="recipe-card" data-category="secrets">
|
|
2012
|
+
<div class="recipe-header">
|
|
2013
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2014
|
+
<span class="badge badge-success">TG-SEC-001</span>
|
|
2015
|
+
<strong>Migrated hardcoded JWT secret to environment variable process.env.JWT_SECRET</strong>
|
|
2016
|
+
</div>
|
|
2017
|
+
<div class="recipe-meta-badges">
|
|
2018
|
+
<span class="chip chip-filetype">.js</span>
|
|
2019
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
2020
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
2021
|
+
</div>
|
|
2022
|
+
</div>
|
|
2023
|
+
<div class="recipe-view-switcher">
|
|
2024
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-SEC-001-c561e05e" onclick="setRecipeView('recipe-TG-SEC-001-c561e05e', 'diff')">Unified Diff</button>
|
|
2025
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-SEC-001-c561e05e" onclick="setRecipeView('recipe-TG-SEC-001-c561e05e', 'split')">Before / After</button>
|
|
2026
|
+
</div>
|
|
2027
|
+
<div id="recipe-view-diff-recipe-TG-SEC-001-c561e05e" class="code-preview recipe-diff-container">
|
|
2028
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
2029
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
2030
|
+
<span class="diff-line-hunk">@@ -7,5 +7,5 @@</span>
|
|
2031
|
+
<span class="diff-line-ctx"> </span>
|
|
2032
|
+
<span class="diff-line-ctx"> // TG-SEC-001: Hardcoded JWT secret</span>
|
|
2033
|
+
<span class="diff-line-del">-const JWT_SECRET = 'FAKE_DEMO_JWT_SECRET_NOT_FOR_PRODUCTION';</span>
|
|
2034
|
+
<span class="diff-line-add">+const JWT_SECRET = process.env.JWT_SECRET || "";</span>
|
|
2035
|
+
<span class="diff-line-ctx"> </span>
|
|
2036
|
+
<span class="diff-line-ctx"> // TG-PLATFORM-001: Wildcard CORS with credentials</span></code></pre>
|
|
2037
|
+
</div>
|
|
2038
|
+
<div id="recipe-view-split-recipe-TG-SEC-001-c561e05e" class="recipe-split-container" style="display: none;">
|
|
2039
|
+
<div class="split-pane">
|
|
2040
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
2041
|
+
<div class="code-preview"><pre><code>// Vulnerable snippet</code></pre></div>
|
|
2042
|
+
</div>
|
|
2043
|
+
<div class="split-pane">
|
|
2044
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
2045
|
+
<div class="code-preview"><pre><code>Migrated hardcoded JWT secret to environment variable process.env.JWT_SECRET</code></pre></div>
|
|
2046
|
+
</div>
|
|
2047
|
+
</div>
|
|
2048
|
+
<div class="recipe-actions">
|
|
2049
|
+
<div class="cmd-box">
|
|
2050
|
+
<code>npx torusguard harden --rule TG-SEC-001</code>
|
|
2051
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SEC-001', this); event.stopPropagation();">📋 Copy Command</button>
|
|
2052
|
+
</div>
|
|
2053
|
+
<button class="btn btn-sm" onclick="copyText('Migrated hardcoded JWT secret to environment variable process.env.JWT_SECRET', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
2054
|
+
</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
<div class="recipe-card" data-category="secrets">
|
|
2057
|
+
<div class="recipe-header">
|
|
2058
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2059
|
+
<span class="badge badge-success">TG-SEC-004</span>
|
|
2060
|
+
<strong>Redacted console logging of sensitive token or credential</strong>
|
|
2061
|
+
</div>
|
|
2062
|
+
<div class="recipe-meta-badges">
|
|
2063
|
+
<span class="chip chip-filetype">.js</span>
|
|
2064
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
2065
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
2066
|
+
</div>
|
|
2067
|
+
</div>
|
|
2068
|
+
<div class="recipe-view-switcher">
|
|
2069
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-SEC-004-1f517bc3" onclick="setRecipeView('recipe-TG-SEC-004-1f517bc3', 'diff')">Unified Diff</button>
|
|
2070
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-SEC-004-1f517bc3" onclick="setRecipeView('recipe-TG-SEC-004-1f517bc3', 'split')">Before / After</button>
|
|
2071
|
+
</div>
|
|
2072
|
+
<div id="recipe-view-diff-recipe-TG-SEC-004-1f517bc3" class="code-preview recipe-diff-container">
|
|
2073
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/server/index.js</span>
|
|
2074
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/server/index.js</span>
|
|
2075
|
+
<span class="diff-line-hunk">@@ -26,5 +26,5 @@</span>
|
|
2076
|
+
<span class="diff-line-ctx"> app.post('/api/login', (req, res) => {</span>
|
|
2077
|
+
<span class="diff-line-ctx"> const { email, password } = req.body;</span>
|
|
2078
|
+
<span class="diff-line-del">- console.log('Login attempt:', { email, password }); // TG-SEC-004</span>
|
|
2079
|
+
<span class="diff-line-add">+ // Security: Sensitive credential log removed per TG-SEC-004</span>
|
|
2080
|
+
<span class="diff-line-ctx"> const user = users.find((u) => u.email === email && u.password === password);</span>
|
|
2081
|
+
<span class="diff-line-ctx"> if (!user) return res.status(401).json({ error: 'User not found' }); // enumeration</span></code></pre>
|
|
2082
|
+
</div>
|
|
2083
|
+
<div id="recipe-view-split-recipe-TG-SEC-004-1f517bc3" class="recipe-split-container" style="display: none;">
|
|
2084
|
+
<div class="split-pane">
|
|
2085
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
2086
|
+
<div class="code-preview"><pre><code>app.post('/api/login', authLimiter, (req, res) => {</code></pre></div>
|
|
2087
|
+
</div>
|
|
2088
|
+
<div class="split-pane">
|
|
2089
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
2090
|
+
<div class="code-preview"><pre><code>Redacted console logging of sensitive token or credential</code></pre></div>
|
|
2091
|
+
</div>
|
|
2092
|
+
</div>
|
|
2093
|
+
<div class="recipe-actions">
|
|
2094
|
+
<div class="cmd-box">
|
|
2095
|
+
<code>npx torusguard harden --rule TG-SEC-004</code>
|
|
2096
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SEC-004', this); event.stopPropagation();">📋 Copy Command</button>
|
|
2097
|
+
</div>
|
|
2098
|
+
<button class="btn btn-sm" onclick="copyText('Redacted console logging of sensitive token or credential', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
2099
|
+
</div>
|
|
2100
|
+
</div>
|
|
2101
|
+
<div class="recipe-card" data-category="secrets">
|
|
2102
|
+
<div class="recipe-header">
|
|
2103
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2104
|
+
<span class="badge badge-success">TG-SEC-004</span>
|
|
2105
|
+
<strong>Redacted console logging of sensitive token or credential</strong>
|
|
2106
|
+
</div>
|
|
2107
|
+
<div class="recipe-meta-badges">
|
|
2108
|
+
<span class="chip chip-filetype">.jsx</span>
|
|
2109
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
2110
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
2111
|
+
</div>
|
|
2112
|
+
</div>
|
|
2113
|
+
<div class="recipe-view-switcher">
|
|
2114
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-SEC-004-91213da3" onclick="setRecipeView('recipe-TG-SEC-004-91213da3', 'diff')">Unified Diff</button>
|
|
2115
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-SEC-004-91213da3" onclick="setRecipeView('recipe-TG-SEC-004-91213da3', 'split')">Before / After</button>
|
|
2116
|
+
</div>
|
|
2117
|
+
<div id="recipe-view-diff-recipe-TG-SEC-004-91213da3" class="code-preview recipe-diff-container">
|
|
2118
|
+
<pre><code><span class="diff-line-header">--- a/examples/vulnerable-react-express/client/src/App.jsx</span>
|
|
2119
|
+
<span class="diff-line-header">+++ b/examples/vulnerable-react-express/client/src/App.jsx</span>
|
|
2120
|
+
<span class="diff-line-hunk">@@ -28,5 +28,5 @@</span>
|
|
2121
|
+
<span class="diff-line-ctx"> const data = await res.json();</span>
|
|
2122
|
+
<span class="diff-line-ctx"> // TG-CLIENT-002: Sensitive console logging</span>
|
|
2123
|
+
<span class="diff-line-del">- console.log('Login token:', data.token, 'config:', clientConfig);</span>
|
|
2124
|
+
<span class="diff-line-add">+ // Security: Sensitive credential log removed per TG-SEC-004</span>
|
|
2125
|
+
<span class="diff-line-ctx"> setMessage(JSON.stringify(data));</span>
|
|
2126
|
+
<span class="diff-line-ctx"> }</span></code></pre>
|
|
2127
|
+
</div>
|
|
2128
|
+
<div id="recipe-view-split-recipe-TG-SEC-004-91213da3" class="recipe-split-container" style="display: none;">
|
|
2129
|
+
<div class="split-pane">
|
|
2130
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
2131
|
+
<div class="code-preview"><pre><code>// Security: Sensitive credential log removed per TG-SEC-004</code></pre></div>
|
|
2132
|
+
</div>
|
|
2133
|
+
<div class="split-pane">
|
|
2134
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
2135
|
+
<div class="code-preview"><pre><code>Redacted console logging of sensitive token or credential</code></pre></div>
|
|
2136
|
+
</div>
|
|
2137
|
+
</div>
|
|
2138
|
+
<div class="recipe-actions">
|
|
2139
|
+
<div class="cmd-box">
|
|
2140
|
+
<code>npx torusguard harden --rule TG-SEC-004</code>
|
|
2141
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SEC-004', this); event.stopPropagation();">📋 Copy Command</button>
|
|
2142
|
+
</div>
|
|
2143
|
+
<button class="btn btn-sm" onclick="copyText('Redacted console logging of sensitive token or credential', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
2144
|
+
</div>
|
|
2145
|
+
</div>
|
|
2146
|
+
<div class="recipe-card" data-category="ssrf">
|
|
2147
|
+
<div class="recipe-header">
|
|
2148
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2149
|
+
<span class="badge badge-success">TG-SSRF-004</span>
|
|
2150
|
+
<strong>Injected explicit 10-second request timeout to prevent resource exhaustion</strong>
|
|
2151
|
+
</div>
|
|
2152
|
+
<div class="recipe-meta-badges">
|
|
2153
|
+
<span class="chip chip-filetype">.py</span>
|
|
2154
|
+
<span class="ponytail-badge" title="Ponytail line churn budget (≤ 35 additions, ≤ 25 deletions)">⚡ +1 / -1 lines</span>
|
|
2155
|
+
<span class="verified-badge">✔ Verified 1x</span>
|
|
2156
|
+
</div>
|
|
2157
|
+
</div>
|
|
2158
|
+
<div class="recipe-view-switcher">
|
|
2159
|
+
<button class="recipe-tab-btn active" id="btn-view-diff-recipe-TG-SSRF-004-34c422d8" onclick="setRecipeView('recipe-TG-SSRF-004-34c422d8', 'diff')">Unified Diff</button>
|
|
2160
|
+
<button class="recipe-tab-btn" id="btn-view-split-recipe-TG-SSRF-004-34c422d8" onclick="setRecipeView('recipe-TG-SSRF-004-34c422d8', 'split')">Before / After</button>
|
|
2161
|
+
</div>
|
|
2162
|
+
<div id="recipe-view-diff-recipe-TG-SSRF-004-34c422d8" class="code-preview recipe-diff-container">
|
|
2163
|
+
<pre><code><span class="diff-line-header">--- a/examples/python/fastapi-vuln/main.py</span>
|
|
2164
|
+
<span class="diff-line-header">+++ b/examples/python/fastapi-vuln/main.py</span>
|
|
2165
|
+
<span class="diff-line-hunk">@@ -7,5 +7,5 @@</span>
|
|
2166
|
+
<span class="diff-line-ctx"> @app.get("/fetch")</span>
|
|
2167
|
+
<span class="diff-line-ctx"> def fetch_url(url: str):</span>
|
|
2168
|
+
<span class="diff-line-del">- response = requests.get(url)</span>
|
|
2169
|
+
<span class="diff-line-add">+ response = requests.get(url, timeout=10)</span>
|
|
2170
|
+
<span class="diff-line-ctx"> return {"status": response.status_code, "content": response.text}</span></code></pre>
|
|
2171
|
+
</div>
|
|
2172
|
+
<div id="recipe-view-split-recipe-TG-SSRF-004-34c422d8" class="recipe-split-container" style="display: none;">
|
|
2173
|
+
<div class="split-pane">
|
|
2174
|
+
<div class="split-label split-del">✘ Before (Vulnerable)</div>
|
|
2175
|
+
<div class="code-preview"><pre><code>response = requests.get(url, timeout=10)</code></pre></div>
|
|
2176
|
+
</div>
|
|
2177
|
+
<div class="split-pane">
|
|
2178
|
+
<div class="split-label split-add">✔ After (Hardened)</div>
|
|
2179
|
+
<div class="code-preview"><pre><code>Injected explicit 10-second request timeout to prevent resource exhaustion</code></pre></div>
|
|
2180
|
+
</div>
|
|
2181
|
+
</div>
|
|
2182
|
+
<div class="recipe-actions">
|
|
2183
|
+
<div class="cmd-box">
|
|
2184
|
+
<code>npx torusguard harden --rule TG-SSRF-004</code>
|
|
2185
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SSRF-004', this); event.stopPropagation();">📋 Copy Command</button>
|
|
2186
|
+
</div>
|
|
2187
|
+
<button class="btn btn-sm" onclick="copyText('Injected explicit 10-second request timeout to prevent resource exhaustion', this); event.stopPropagation();">📋 Copy Pattern</button>
|
|
2188
|
+
</div>
|
|
2189
|
+
</div>
|
|
430
2190
|
</div>
|
|
431
2191
|
</div>
|
|
432
2192
|
|
|
433
|
-
<!-- Regression Watches & False Positives -->
|
|
434
2193
|
<div class="card">
|
|
435
|
-
<div class="section-title">&#
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
<
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
<
|
|
446
|
-
</
|
|
447
|
-
|
|
2194
|
+
<div class="section-title">💡 Next Best Defenses Advisory</div>
|
|
2195
|
+
<div style="font-size: 12px; color: var(--text-muted); margin-bottom: 12px;">Stack-tailored recommendations to achieve hardened posture:</div>
|
|
2196
|
+
|
|
2197
|
+
<div class="advisory-item">
|
|
2198
|
+
<div class="advisory-top">
|
|
2199
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2200
|
+
<span class="badge badge-critical">P0 Critical</span>
|
|
2201
|
+
<span class="badge badge-primary">TG-PLATFORM-002</span>
|
|
2202
|
+
<strong>Enforce HTTP Security Headers (CSP, HSTS, X-Frame-Options)</strong>
|
|
2203
|
+
</div>
|
|
2204
|
+
<span class="chip chip-filetype">TypeScript</span>
|
|
2205
|
+
</div>
|
|
2206
|
+
<div class="advisory-mitigates"><strong>Mitigates:</strong> OWASP A05: Security Misconfiguration</div>
|
|
2207
|
+
<div class="advisory-desc">Protects against clickjacking, MIME-confusion, and cross-site scripting attacks.</div>
|
|
2208
|
+
<div class="advisory-guarantee"><strong>Invariant Guarantee:</strong> Zero unhardened HTTP endpoints. Requires HSTS max-age >= 31536000 and CSP nonce isolation.</div>
|
|
2209
|
+
<div class="cmd-box" style="margin-top: 8px;">
|
|
2210
|
+
<code>npx torusguard harden --rule TG-PLATFORM-002</code>
|
|
2211
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-PLATFORM-002', this); event.stopPropagation();">📋 Copy</button>
|
|
2212
|
+
</div>
|
|
2213
|
+
</div>
|
|
2214
|
+
<div class="advisory-item">
|
|
2215
|
+
<div class="advisory-top">
|
|
2216
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2217
|
+
<span class="badge badge-critical">P0 Critical</span>
|
|
2218
|
+
<span class="badge badge-primary">TG-RATE-001</span>
|
|
2219
|
+
<strong>Auth Endpoint Rate Limiting & Resource Throttling</strong>
|
|
2220
|
+
</div>
|
|
2221
|
+
<span class="chip chip-filetype">TypeScript</span>
|
|
2222
|
+
</div>
|
|
2223
|
+
<div class="advisory-mitigates"><strong>Mitigates:</strong> OWASP A07: Identification and Authentication Failures</div>
|
|
2224
|
+
<div class="advisory-desc">Prevents brute-force credential stuffing, password spraying, and resource exhaustion.</div>
|
|
2225
|
+
<div class="advisory-guarantee"><strong>Invariant Guarantee:</strong> Max 5 failed attempts per IP window on auth routes with exponential backoff.</div>
|
|
2226
|
+
<div class="cmd-box" style="margin-top: 8px;">
|
|
2227
|
+
<code>npx torusguard harden --rule TG-RATE-001</code>
|
|
2228
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-RATE-001', this); event.stopPropagation();">📋 Copy</button>
|
|
2229
|
+
</div>
|
|
2230
|
+
</div>
|
|
2231
|
+
<div class="advisory-item">
|
|
2232
|
+
<div class="advisory-top">
|
|
2233
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2234
|
+
<span class="badge badge-high">P1 Recommended</span>
|
|
2235
|
+
<span class="badge badge-primary">TG-AUTH-002</span>
|
|
2236
|
+
<strong>Timing-Safe Secret & Signature Verification</strong>
|
|
2237
|
+
</div>
|
|
2238
|
+
<span class="chip chip-filetype">TypeScript</span>
|
|
2239
|
+
</div>
|
|
2240
|
+
<div class="advisory-mitigates"><strong>Mitigates:</strong> OWASP A02: Cryptographic Failures</div>
|
|
2241
|
+
<div class="advisory-desc">Eliminates side-channel timing attacks on API keys, HMAC signatures, and tokens.</div>
|
|
2242
|
+
<div class="advisory-guarantee"><strong>Invariant Guarantee:</strong> Constant-time evaluation across all cryptographic comparison call-sites.</div>
|
|
2243
|
+
<div class="cmd-box" style="margin-top: 8px;">
|
|
2244
|
+
<code>npx torusguard harden --rule TG-AUTH-002</code>
|
|
2245
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-AUTH-002', this); event.stopPropagation();">📋 Copy</button>
|
|
2246
|
+
</div>
|
|
2247
|
+
</div>
|
|
2248
|
+
<div class="advisory-item">
|
|
2249
|
+
<div class="advisory-top">
|
|
2250
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2251
|
+
<span class="badge badge-critical">P0 Critical</span>
|
|
2252
|
+
<span class="badge badge-primary">TG-DB-004</span>
|
|
2253
|
+
<strong>Enforce Multi-Tenant Database Query Partition Scoping</strong>
|
|
2254
|
+
</div>
|
|
2255
|
+
<span class="chip chip-filetype">TypeScript</span>
|
|
2256
|
+
</div>
|
|
2257
|
+
<div class="advisory-mitigates"><strong>Mitigates:</strong> OWASP A01: Broken Access Control</div>
|
|
2258
|
+
<div class="advisory-desc">Guarantees cross-tenant data isolation and eliminates BOLA/IDOR vulnerabilities.</div>
|
|
2259
|
+
<div class="advisory-guarantee"><strong>Invariant Guarantee:</strong> All database queries MUST be explicitly scoped by authenticated tenant ID.</div>
|
|
2260
|
+
<div class="cmd-box" style="margin-top: 8px;">
|
|
2261
|
+
<code>npx torusguard harden --rule TG-DB-004</code>
|
|
2262
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-DB-004', this); event.stopPropagation();">📋 Copy</button>
|
|
2263
|
+
</div>
|
|
2264
|
+
</div>
|
|
2265
|
+
<div class="advisory-item">
|
|
2266
|
+
<div class="advisory-top">
|
|
2267
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
|
2268
|
+
<span class="badge badge-high">P1 Recommended</span>
|
|
2269
|
+
<span class="badge badge-primary">TG-SSRF-002</span>
|
|
2270
|
+
<strong>Block Outbound Requests to Cloud Metadata (169.254.169.254)</strong>
|
|
2271
|
+
</div>
|
|
2272
|
+
<span class="chip chip-filetype">TypeScript</span>
|
|
2273
|
+
</div>
|
|
2274
|
+
<div class="advisory-mitigates"><strong>Mitigates:</strong> OWASP A10: Server-Side Request Forgery</div>
|
|
2275
|
+
<div class="advisory-desc">Stops attackers from querying internal cloud metadata instances or private VPC ranges.</div>
|
|
2276
|
+
<div class="advisory-guarantee"><strong>Invariant Guarantee:</strong> Strict hostname resolution with CIDR blocking for 169.254.169.254 and RFC1918 subnets.</div>
|
|
2277
|
+
<div class="cmd-box" style="margin-top: 8px;">
|
|
2278
|
+
<code>npx torusguard harden --rule TG-SSRF-002</code>
|
|
2279
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-SSRF-002', this); event.stopPropagation();">📋 Copy</button>
|
|
2280
|
+
</div>
|
|
2281
|
+
</div>
|
|
448
2282
|
</div>
|
|
449
2283
|
</div>
|
|
450
2284
|
|
|
451
2285
|
<footer>
|
|
452
|
-
TorusGuard v1.3.
|
|
2286
|
+
TorusGuard v1.3.6 · Autonomous Security Engine for AI-Built Applications · 100% Local-First · Zero Cloud Telemetry
|
|
453
2287
|
</footer>
|
|
454
2288
|
</div>
|
|
2289
|
+
|
|
2290
|
+
<!-- Invariant Inspection Modal -->
|
|
2291
|
+
<div id="invariantModal" class="modal-backdrop" style="display: none;" onclick="closeInvariantModal(event)">
|
|
2292
|
+
<div class="modal-card" onclick="event.stopPropagation()">
|
|
2293
|
+
<div class="modal-header">
|
|
2294
|
+
<div>
|
|
2295
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2296
|
+
<span id="modalInvSev" class="badge badge-medium">Medium</span>
|
|
2297
|
+
<code id="modalInvRule" class="rule-id" style="font-size: 14px;">TG-GEN</code>
|
|
2298
|
+
<span id="modalInvStatus" class="badge badge-success">Defended</span>
|
|
2299
|
+
</div>
|
|
2300
|
+
<h3 id="modalInvTitle" style="font-size: 16px; margin-top: 6px; color: var(--text-main);">Security Invariant</h3>
|
|
2301
|
+
</div>
|
|
2302
|
+
<button class="modal-close-btn" onclick="closeInvariantModal(event)">×</button>
|
|
2303
|
+
</div>
|
|
2304
|
+
<div class="modal-body">
|
|
2305
|
+
<div>
|
|
2306
|
+
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 600;">Architectural Layer & Category</div>
|
|
2307
|
+
<div id="modalInvLayer" style="margin-top: 2px; font-weight: 500;">Application & LLM Defense</div>
|
|
2308
|
+
</div>
|
|
2309
|
+
<div>
|
|
2310
|
+
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 600;">Invariant Guarantee</div>
|
|
2311
|
+
<div id="modalInvGuarantee" style="margin-top: 4px; line-height: 1.4; color: var(--text-main);">
|
|
2312
|
+
Enforces deterministic verification without bypasses.
|
|
2313
|
+
</div>
|
|
2314
|
+
</div>
|
|
2315
|
+
<div>
|
|
2316
|
+
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 600;">Ponytail Churn Invariant</div>
|
|
2317
|
+
<div style="margin-top: 2px; color: var(--accent-yellow); font-size: 12px;">
|
|
2318
|
+
≤ 35 additions, ≤ 25 deletions per surgical patch bundle.
|
|
2319
|
+
</div>
|
|
2320
|
+
</div>
|
|
2321
|
+
<div>
|
|
2322
|
+
<div style="font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 600; margin-bottom: 4px;">Governed CLI Command</div>
|
|
2323
|
+
<div class="cmd-box" id="modalInvCmd">
|
|
2324
|
+
<code>npx torusguard harden --rule TG-GEN</code>
|
|
2325
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule TG-GEN', this)">📋 Copy</button>
|
|
2326
|
+
</div>
|
|
2327
|
+
</div>
|
|
2328
|
+
</div>
|
|
2329
|
+
</div>
|
|
2330
|
+
</div>
|
|
2331
|
+
|
|
2332
|
+
<script>
|
|
2333
|
+
const RAW_FINDINGS = [];
|
|
2334
|
+
const SARIF_RULES = [{"id": "TG-SEC-001", "shortDescription": {"text": "Hardcoded Secret or API Key in Tracked Source"}}, {"id": "TG-SEC-002", "shortDescription": {"text": "Public Client Environment Variable Secret Exposure"}}, {"id": "TG-SEC-003", "shortDescription": {"text": "Tracked Environment File"}}, {"id": "TG-SEC-004", "shortDescription": {"text": "Sensitive Information in Logs"}}, {"id": "TG-SEC-005", "shortDescription": {"text": "Secret in Git History or Query URL"}}, {"id": "TG-SEC-006", "shortDescription": {"text": "Secret in Build Artifact or Container Layer"}}, {"id": "TG-SEC-007", "shortDescription": {"text": "Secret in Test or Example Fixture Without Mock Marker"}}, {"id": "TG-AUTH-001", "shortDescription": {"text": "Weak Password Storage or Deprecated Hashing"}}, {"id": "TG-AUTH-002", "shortDescription": {"text": "Client-Only Authorization Enforcement"}}, {"id": "TG-AUTH-003", "shortDescription": {"text": "Missing Object-Level Authorization (IDOR)"}}, {"id": "TG-AUTH-004", "shortDescription": {"text": "Insecure Session Cookie Configuration"}}, {"id": "TG-AUTH-005", "shortDescription": {"text": "Unsafe Password Reset Token Generation"}}, {"id": "TG-AUTH-006", "shortDescription": {"text": "Mass Assignment & Privilege Escalation via Payloads"}}, {"id": "TG-AUTH-007", "shortDescription": {"text": "Missing Object-Level and Property-Level Authorization (IDOR)"}}, {"id": "TG-AUTH-008", "shortDescription": {"text": "Untrusted Role or Tenant Header Injection"}}, {"id": "TG-DB-001", "shortDescription": {"text": "Frontend Database Query Logic"}}, {"id": "TG-DB-002", "shortDescription": {"text": "Privileged Database Credential in Browser Context"}}, {"id": "TG-DB-003", "shortDescription": {"text": "Frontend Use of Admin or Server-Only Database SDKs"}}, {"id": "TG-DB-004", "shortDescription": {"text": "Missing Tenant Query Isolation in Multi-Tenant Models"}}, {"id": "TG-INPUT-001", "shortDescription": {"text": "Missing Server-Side Request Validation"}}, {"id": "TG-INPUT-002", "shortDescription": {"text": "Raw SQL Concatenation"}}, {"id": "TG-INPUT-003", "shortDescription": {"text": "Unsafe Dynamic Code or HTML Execution"}}, {"id": "TG-INPUT-004", "shortDescription": {"text": "Command Injection via Shell Execution"}}, {"id": "TG-INPUT-005", "shortDescription": {"text": "Unsafe Template Rendering & Disabled Autoescaping"}}, {"id": "TG-INPUT-006", "shortDescription": {"text": "Path Traversal and Unsafe Upload Storage"}}, {"id": "TG-RATE-001", "shortDescription": {"text": "Unlimited Authentication Endpoint"}}, {"id": "TG-RATE-002", "shortDescription": {"text": "Unlimited Public Write Endpoint"}}, {"id": "TG-RATE-003", "shortDescription": {"text": "Unbounded Resource Consumption"}}, {"id": "TG-AGENT-001", "shortDescription": {"text": "Prompt Injection in System Context Files"}}, {"id": "TG-AGENT-002", "shortDescription": {"text": "Unsafe Tool Dispatch & Shell Execution without Sandboxing"}}, {"id": "TG-AGENT-003", "shortDescription": {"text": "Overly Broad MCP Tool Scoping & Credential Access"}}, {"id": "TG-AGENT-004", "shortDescription": {"text": "Persistent Memory & Cross-Session Information Leakage"}}, {"id": "TG-SSRF-001", "shortDescription": {"text": "User-Controlled Server-Side URL Fetch"}}, {"id": "TG-SSRF-002", "shortDescription": {"text": "Missing Internal Network Protection"}}, {"id": "TG-SSRF-003", "shortDescription": {"text": "Unsafe Redirect Following"}}, {"id": "TG-SSRF-004", "shortDescription": {"text": "Unbounded Outbound Request Without Timeout"}}, {"id": "TG-WEBHOOK-001", "shortDescription": {"text": "Missing Webhook Signature Verification"}}, {"id": "TG-WEBHOOK-002", "shortDescription": {"text": "Replayable Webhook Without Timestamp Validation"}}, {"id": "TG-WEBHOOK-003", "shortDescription": {"text": "Missing Webhook Idempotency"}}, {"id": "TG-WEBHOOK-004", "shortDescription": {"text": "Unvalidated Webhook Event Type"}}, {"id": "TG-WS-001", "shortDescription": {"text": "Unauthenticated WebSocket Handshake"}}, {"id": "TG-WS-002", "shortDescription": {"text": "Missing Channel-Level Authorization"}}, {"id": "TG-WS-003", "shortDescription": {"text": "Unvalidated WebSocket Message"}}, {"id": "TG-WS-004", "shortDescription": {"text": "Unbounded WebSocket Connections"}}, {"id": "TG-CSRF-001", "shortDescription": {"text": "Missing CSRF Protection"}}, {"id": "TG-CSRF-002", "shortDescription": {"text": "Unsafe Credentialed Cross-Origin Request"}}, {"id": "TG-GQL-001", "shortDescription": {"text": "Missing Query Depth or Complexity Limits"}}, {"id": "TG-GQL-002", "shortDescription": {"text": "Resolver Missing Authorization"}}, {"id": "TG-GQL-003", "shortDescription": {"text": "Unbounded GraphQL Pagination or Batching"}}, {"id": "TG-GQL-004", "shortDescription": {"text": "Unnecessary Production Introspection"}}, {"id": "TG-SUPPLY-001", "shortDescription": {"text": "Missing or Ignored Dependency Lockfile"}}, {"id": "TG-SUPPLY-002", "shortDescription": {"text": "Vulnerable Dependency Review Missing"}}, {"id": "TG-SUPPLY-003", "shortDescription": {"text": "Unsafe CI/CD Secret Exposure"}}, {"id": "TG-SUPPLY-004", "shortDescription": {"text": "Unpinned CI Action"}}, {"id": "TG-SUPPLY-005", "shortDescription": {"text": "Unsafe Dependency Install Script"}}, {"id": "TG-SUPPLY-006", "shortDescription": {"text": "Container Build Secret Persistence"}}, {"id": "TG-BIZ-001", "shortDescription": {"text": "Client-Controlled Sensitive Business Value"}}, {"id": "TG-BIZ-002", "shortDescription": {"text": "Replayable One-Time Operation"}}, {"id": "TG-BIZ-003", "shortDescription": {"text": "Missing Workflow-State Validation"}}, {"id": "TG-BIZ-004", "shortDescription": {"text": "Unrestricted Sensitive Business Flow"}}, {"id": "TG-CACHE-001", "shortDescription": {"text": "Sensitive Response Publicly Cacheable"}}, {"id": "TG-CACHE-002", "shortDescription": {"text": "Missing User Cache Isolation"}}, {"id": "TG-CACHE-003", "shortDescription": {"text": "Sensitive Data in URL"}}, {"id": "TG-CLIENT-001", "shortDescription": {"text": "Public Production Source Maps"}}, {"id": "TG-CLIENT-002", "shortDescription": {"text": "Sensitive Client Bundle Content"}}, {"id": "TG-PLATFORM-001", "shortDescription": {"text": "Wildcard CORS With Credentials"}}, {"id": "TG-PLATFORM-002", "shortDescription": {"text": "Missing Security Headers"}}, {"id": "TG-PLATFORM-003", "shortDescription": {"text": "Production Stack Trace Exposure"}}, {"id": "TG-PLATFORM-004", "shortDescription": {"text": "Missing Request Size Limits"}}, {"id": "TG-DIFF-001", "shortDescription": {"text": "Accidental Security Check Bypass in Patch Additions"}}, {"id": "TG-DIFF-002", "shortDescription": {"text": "Secret and Token Ingestion in Patch Additions"}}, {"id": "TG-DIFF-003", "shortDescription": {"text": "Unintentional Removal of Tenant Filter in Patch Deletions"}}, {"id": "TG-EDGE-001", "shortDescription": {"text": "Serverless & Edge Global State Memory Leakage"}}, {"id": "TG-EDGE-003", "shortDescription": {"text": "AWS Lambda Ephemeral Execution & Cold Start Security"}}];
|
|
2335
|
+
const INVARIANT_CATALOG = {"TG-SEC-001": {"rule_id": "TG-SEC-001", "title": "Hardcoded Secret or API Key in Tracked Source", "severity": "Critical", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-SEC-002": {"rule_id": "TG-SEC-002", "title": "Public Client Environment Variable Secret Exposure", "severity": "Critical", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-SEC-003": {"rule_id": "TG-SEC-003", "title": "Tracked Environment File", "severity": "High", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-SEC-004": {"rule_id": "TG-SEC-004", "title": "Sensitive Information in Logs", "severity": "Medium", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-SEC-005": {"rule_id": "TG-SEC-005", "title": "Secret in Git History or Query URL", "severity": "Critical", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-SEC-006": {"rule_id": "TG-SEC-006", "title": "Secret in Build Artifact or Container Layer", "severity": "Critical", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-SEC-007": {"rule_id": "TG-SEC-007", "title": "Secret in Test or Example Fixture Without Mock Marker", "severity": "Critical", "category": "secrets", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero hardcoded API keys, private certificates, AWS/GCP/Stripe tokens, or JWT secrets in tracked source code. Mandatory externalized environment configuration.", "owasp": "OWASP A02:2021 - Cryptographic Failures", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-AUTH-001": {"rule_id": "TG-AUTH-001", "title": "Weak Password Storage or Deprecated Hashing", "severity": "Critical", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-002": {"rule_id": "TG-AUTH-002", "title": "Client-Only Authorization Enforcement", "severity": "High", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-003": {"rule_id": "TG-AUTH-003", "title": "Missing Object-Level Authorization (IDOR)", "severity": "High", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-004": {"rule_id": "TG-AUTH-004", "title": "Insecure Session Cookie Configuration", "severity": "High", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-005": {"rule_id": "TG-AUTH-005", "title": "Unsafe Password Reset Token Generation", "severity": "High", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-006": {"rule_id": "TG-AUTH-006", "title": "Mass Assignment & Privilege Escalation via Payloads", "severity": "Critical", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-007": {"rule_id": "TG-AUTH-007", "title": "Missing Object-Level and Property-Level Authorization (IDOR)", "severity": "Critical", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-AUTH-008": {"rule_id": "TG-AUTH-008", "title": "Untrusted Role or Tenant Header Injection", "severity": "Critical", "category": "auth", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Timing-safe secret compares, strong argon2id/bcrypt password hashing, explicit JWT algorithm pinning, and secure cookie session attributes.", "owasp": "OWASP A07:2021 - Identification & Authentication Failures", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(a)(1)"}, "TG-DB-001": {"rule_id": "TG-DB-001", "title": "Frontend Database Query Logic", "severity": "High", "category": "database", "cluster": "cluster-client-leak", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Zero unparameterized SQL concatenation. Enforces prepared statements with bound parameters and mandatory tenant partition scoping.", "owasp": "OWASP A03:2021 - Injection / A01: Broken Access Control", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(a)(1)"}, "TG-DB-002": {"rule_id": "TG-DB-002", "title": "Privileged Database Credential in Browser Context", "severity": "Critical", "category": "database", "cluster": "cluster-client-leak", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Zero unparameterized SQL concatenation. Enforces prepared statements with bound parameters and mandatory tenant partition scoping.", "owasp": "OWASP A03:2021 - Injection / A01: Broken Access Control", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(a)(1)"}, "TG-DB-003": {"rule_id": "TG-DB-003", "title": "Frontend Use of Admin or Server-Only Database SDKs", "severity": "Critical", "category": "database", "cluster": "cluster-client-leak", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Zero unparameterized SQL concatenation. Enforces prepared statements with bound parameters and mandatory tenant partition scoping.", "owasp": "OWASP A03:2021 - Injection / A01: Broken Access Control", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(a)(1)"}, "TG-DB-004": {"rule_id": "TG-DB-004", "title": "Missing Tenant Query Isolation in Multi-Tenant Models", "severity": "Critical", "category": "database", "cluster": "cluster-tenant-isolation", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Zero unparameterized SQL concatenation. Enforces prepared statements with bound parameters and mandatory tenant partition scoping.", "owasp": "OWASP A03:2021 - Injection / A01: Broken Access Control", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(a)(1)"}, "TG-INPUT-001": {"rule_id": "TG-INPUT-001", "title": "Missing Server-Side Request Validation", "severity": "High", "category": "injection", "cluster": "cluster-injection", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Strict path normalization, command argument escaping, and safe DOM assignment. Prevents path traversal, OS command injection, and Reflected/DOM XSS.", "owasp": "OWASP A03:2021 - Injection", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-INPUT-002": {"rule_id": "TG-INPUT-002", "title": "Raw SQL Concatenation", "severity": "Critical", "category": "injection", "cluster": "cluster-injection", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Strict path normalization, command argument escaping, and safe DOM assignment. Prevents path traversal, OS command injection, and Reflected/DOM XSS.", "owasp": "OWASP A03:2021 - Injection", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-INPUT-003": {"rule_id": "TG-INPUT-003", "title": "Unsafe Dynamic Code or HTML Execution", "severity": "High", "category": "injection", "cluster": "cluster-injection", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Strict path normalization, command argument escaping, and safe DOM assignment. Prevents path traversal, OS command injection, and Reflected/DOM XSS.", "owasp": "OWASP A03:2021 - Injection", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-INPUT-004": {"rule_id": "TG-INPUT-004", "title": "Command Injection via Shell Execution", "severity": "High", "category": "injection", "cluster": "cluster-injection", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Strict path normalization, command argument escaping, and safe DOM assignment. Prevents path traversal, OS command injection, and Reflected/DOM XSS.", "owasp": "OWASP A03:2021 - Injection", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-INPUT-005": {"rule_id": "TG-INPUT-005", "title": "Unsafe Template Rendering & Disabled Autoescaping", "severity": "High", "category": "injection", "cluster": "cluster-injection", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Strict path normalization, command argument escaping, and safe DOM assignment. Prevents path traversal, OS command injection, and Reflected/DOM XSS.", "owasp": "OWASP A03:2021 - Injection", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-INPUT-006": {"rule_id": "TG-INPUT-006", "title": "Path Traversal and Unsafe Upload Storage", "severity": "Critical", "category": "filesystem", "cluster": "cluster-injection", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Strict path normalization, command argument escaping, and safe DOM assignment. Prevents path traversal, OS command injection, and Reflected/DOM XSS.", "owasp": "OWASP A03:2021 - Injection", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-RATE-001": {"rule_id": "TG-RATE-001", "title": "Unlimited Authentication Endpoint", "severity": "High", "category": "rate", "cluster": "cluster-rate-limiting", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Sliding-window IP rate limiting on auth and write endpoints, bounded JSON payload parsers (<= 1MB), and slowloris connection timeouts.", "owasp": "OWASP A04:2021 - Insecure Design / A07: Auth Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-RATE-002": {"rule_id": "TG-RATE-002", "title": "Unlimited Public Write Endpoint", "severity": "Medium", "category": "rate", "cluster": "cluster-rate-limiting", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Sliding-window IP rate limiting on auth and write endpoints, bounded JSON payload parsers (<= 1MB), and slowloris connection timeouts.", "owasp": "OWASP A04:2021 - Insecure Design / A07: Auth Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-RATE-003": {"rule_id": "TG-RATE-003", "title": "Unbounded Resource Consumption", "severity": "High", "category": "rate", "cluster": "cluster-rate-limiting", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Sliding-window IP rate limiting on auth and write endpoints, bounded JSON payload parsers (<= 1MB), and slowloris connection timeouts.", "owasp": "OWASP A04:2021 - Insecure Design / A07: Auth Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-AGENT-001": {"rule_id": "TG-AGENT-001", "title": "Prompt Injection in System Context Files", "severity": "Critical", "category": "agent", "cluster": "cluster-agent-security", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Structural user prompt isolation, inert delimiters, and tool call schema validation. Prevents direct and indirect prompt injection attacks.", "owasp": "OWASP LLM01: Prompt Injection", "compliance": "ISO 27001 A.8.28 (Secure Coding)"}, "TG-AGENT-002": {"rule_id": "TG-AGENT-002", "title": "Unsafe Tool Dispatch & Shell Execution without Sandboxing", "severity": "Critical", "category": "agent", "cluster": "cluster-agent-security", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Structural user prompt isolation, inert delimiters, and tool call schema validation. Prevents direct and indirect prompt injection attacks.", "owasp": "OWASP LLM01: Prompt Injection", "compliance": "ISO 27001 A.8.28 (Secure Coding)"}, "TG-AGENT-003": {"rule_id": "TG-AGENT-003", "title": "Overly Broad MCP Tool Scoping & Credential Access", "severity": "High", "category": "agent", "cluster": "cluster-agent-security", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Structural user prompt isolation, inert delimiters, and tool call schema validation. Prevents direct and indirect prompt injection attacks.", "owasp": "OWASP LLM01: Prompt Injection", "compliance": "ISO 27001 A.8.28 (Secure Coding)"}, "TG-AGENT-004": {"rule_id": "TG-AGENT-004", "title": "Persistent Memory & Cross-Session Information Leakage", "severity": "High", "category": "agent", "cluster": "cluster-agent-security", "layer_id": "layer-app", "layer_name": "Application & LLM Defense", "guarantee": "Structural user prompt isolation, inert delimiters, and tool call schema validation. Prevents direct and indirect prompt injection attacks.", "owasp": "OWASP LLM01: Prompt Injection", "compliance": "ISO 27001 A.8.28 (Secure Coding)"}, "TG-SSRF-001": {"rule_id": "TG-SSRF-001", "title": "User-Controlled Server-Side URL Fetch", "severity": "Critical", "category": "ssrf", "cluster": "cluster-ssrf", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Outbound URL hostname whitelisting, RFC1918 private IP range blocking (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1) and cloud metadata blocking (169.254.169.254).", "owasp": "OWASP A10:2021 - Server-Side Request Forgery (SSRF)", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-SSRF-002": {"rule_id": "TG-SSRF-002", "title": "Missing Internal Network Protection", "severity": "Critical", "category": "ssrf", "cluster": "cluster-ssrf", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Outbound URL hostname whitelisting, RFC1918 private IP range blocking (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1) and cloud metadata blocking (169.254.169.254).", "owasp": "OWASP A10:2021 - Server-Side Request Forgery (SSRF)", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-SSRF-003": {"rule_id": "TG-SSRF-003", "title": "Unsafe Redirect Following", "severity": "Critical", "category": "ssrf", "cluster": "cluster-ssrf", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Outbound URL hostname whitelisting, RFC1918 private IP range blocking (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1) and cloud metadata blocking (169.254.169.254).", "owasp": "OWASP A10:2021 - Server-Side Request Forgery (SSRF)", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-SSRF-004": {"rule_id": "TG-SSRF-004", "title": "Unbounded Outbound Request Without Timeout", "severity": "Critical", "category": "ssrf", "cluster": "cluster-ssrf", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Outbound URL hostname whitelisting, RFC1918 private IP range blocking (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1) and cloud metadata blocking (169.254.169.254).", "owasp": "OWASP A10:2021 - Server-Side Request Forgery (SSRF)", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-WEBHOOK-001": {"rule_id": "TG-WEBHOOK-001", "title": "Missing Webhook Signature Verification", "severity": "Critical", "category": "webhook", "cluster": "cluster-webhook", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Mandatory cryptographic HMAC-SHA256 signature verification over raw request buffers and replay attack protection with <= 300s timestamp limits.", "owasp": "OWASP A08:2021 - Software and Data Integrity Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(c)(1)"}, "TG-WEBHOOK-002": {"rule_id": "TG-WEBHOOK-002", "title": "Replayable Webhook Without Timestamp Validation", "severity": "Critical", "category": "webhook", "cluster": "cluster-webhook", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Mandatory cryptographic HMAC-SHA256 signature verification over raw request buffers and replay attack protection with <= 300s timestamp limits.", "owasp": "OWASP A08:2021 - Software and Data Integrity Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(c)(1)"}, "TG-WEBHOOK-003": {"rule_id": "TG-WEBHOOK-003", "title": "Missing Webhook Idempotency", "severity": "Critical", "category": "webhook", "cluster": "cluster-webhook", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Mandatory cryptographic HMAC-SHA256 signature verification over raw request buffers and replay attack protection with <= 300s timestamp limits.", "owasp": "OWASP A08:2021 - Software and Data Integrity Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(c)(1)"}, "TG-WEBHOOK-004": {"rule_id": "TG-WEBHOOK-004", "title": "Unvalidated Webhook Event Type", "severity": "Critical", "category": "webhook", "cluster": "cluster-webhook", "layer_id": "layer-net", "layer_name": "Network, SSRF & Webhooks", "guarantee": "Mandatory cryptographic HMAC-SHA256 signature verification over raw request buffers and replay attack protection with <= 300s timestamp limits.", "owasp": "OWASP A08:2021 - Software and Data Integrity Failures", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(c)(1)"}, "TG-WS-001": {"rule_id": "TG-WS-001", "title": "Unauthenticated WebSocket Handshake", "severity": "High", "category": "ws", "cluster": "cluster-websocket", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Handshake origin validation, connection ticket verification, and inbound frame size bounds to eliminate CSWSH and socket exhaustion.", "owasp": "OWASP A01:2021 - Broken Access Control / A05: Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-WS-002": {"rule_id": "TG-WS-002", "title": "Missing Channel-Level Authorization", "severity": "Critical", "category": "ws", "cluster": "cluster-websocket", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Handshake origin validation, connection ticket verification, and inbound frame size bounds to eliminate CSWSH and socket exhaustion.", "owasp": "OWASP A01:2021 - Broken Access Control / A05: Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-WS-003": {"rule_id": "TG-WS-003", "title": "Unvalidated WebSocket Message", "severity": "Critical", "category": "ws", "cluster": "cluster-websocket", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Handshake origin validation, connection ticket verification, and inbound frame size bounds to eliminate CSWSH and socket exhaustion.", "owasp": "OWASP A01:2021 - Broken Access Control / A05: Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-WS-004": {"rule_id": "TG-WS-004", "title": "Unbounded WebSocket Connections", "severity": "Critical", "category": "ws", "cluster": "cluster-websocket", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Handshake origin validation, connection ticket verification, and inbound frame size bounds to eliminate CSWSH and socket exhaustion.", "owasp": "OWASP A01:2021 - Broken Access Control / A05: Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-CSRF-001": {"rule_id": "TG-CSRF-001", "title": "Missing CSRF Protection", "severity": "Critical", "category": "csrf", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Cryptographic anti-CSRF token verification on state-changing requests and SameSite=Lax/Strict cookie isolation.", "owasp": "OWASP A01:2021 - Broken Access Control", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.28"}, "TG-CSRF-002": {"rule_id": "TG-CSRF-002", "title": "Unsafe Credentialed Cross-Origin Request", "severity": "Critical", "category": "csrf", "cluster": "cluster-auth-bypass", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Cryptographic anti-CSRF token verification on state-changing requests and SameSite=Lax/Strict cookie isolation.", "owasp": "OWASP A01:2021 - Broken Access Control", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.28"}, "TG-GQL-001": {"rule_id": "TG-GQL-001", "title": "Missing Query Depth or Complexity Limits", "severity": "High", "category": "gql", "cluster": "cluster-graphql", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Query depth limiting (max depth <= 7), circular reference blocking, field complexity cost analysis, and production schema introspection suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-GQL-002": {"rule_id": "TG-GQL-002", "title": "Resolver Missing Authorization", "severity": "Critical", "category": "gql", "cluster": "cluster-graphql", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Query depth limiting (max depth <= 7), circular reference blocking, field complexity cost analysis, and production schema introspection suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-GQL-003": {"rule_id": "TG-GQL-003", "title": "Unbounded GraphQL Pagination or Batching", "severity": "Critical", "category": "gql", "cluster": "cluster-graphql", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Query depth limiting (max depth <= 7), circular reference blocking, field complexity cost analysis, and production schema introspection suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-GQL-004": {"rule_id": "TG-GQL-004", "title": "Unnecessary Production Introspection", "severity": "Critical", "category": "gql", "cluster": "cluster-graphql", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Query depth limiting (max depth <= 7), circular reference blocking, field complexity cost analysis, and production schema introspection suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.28"}, "TG-SUPPLY-001": {"rule_id": "TG-SUPPLY-001", "title": "Missing or Ignored Dependency Lockfile", "severity": "Critical", "category": "supply", "cluster": "cluster-supply-chain", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Lockfile cryptographic integrity checks, known high/critical CVE build gates, verified registry origins, and build script execution bounds.", "owasp": "OWASP A06:2021 - Vulnerable and Outdated Components", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28"}, "TG-SUPPLY-002": {"rule_id": "TG-SUPPLY-002", "title": "Vulnerable Dependency Review Missing", "severity": "Critical", "category": "supply", "cluster": "cluster-supply-chain", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Lockfile cryptographic integrity checks, known high/critical CVE build gates, verified registry origins, and build script execution bounds.", "owasp": "OWASP A06:2021 - Vulnerable and Outdated Components", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28"}, "TG-SUPPLY-003": {"rule_id": "TG-SUPPLY-003", "title": "Unsafe CI/CD Secret Exposure", "severity": "Critical", "category": "supply", "cluster": "cluster-supply-chain", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Lockfile cryptographic integrity checks, known high/critical CVE build gates, verified registry origins, and build script execution bounds.", "owasp": "OWASP A06:2021 - Vulnerable and Outdated Components", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28"}, "TG-SUPPLY-004": {"rule_id": "TG-SUPPLY-004", "title": "Unpinned CI Action", "severity": "Critical", "category": "supply", "cluster": "cluster-supply-chain", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Lockfile cryptographic integrity checks, known high/critical CVE build gates, verified registry origins, and build script execution bounds.", "owasp": "OWASP A06:2021 - Vulnerable and Outdated Components", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28"}, "TG-SUPPLY-005": {"rule_id": "TG-SUPPLY-005", "title": "Unsafe Dependency Install Script", "severity": "Critical", "category": "supply", "cluster": "cluster-supply-chain", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Lockfile cryptographic integrity checks, known high/critical CVE build gates, verified registry origins, and build script execution bounds.", "owasp": "OWASP A06:2021 - Vulnerable and Outdated Components", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28"}, "TG-SUPPLY-006": {"rule_id": "TG-SUPPLY-006", "title": "Container Build Secret Persistence", "severity": "High", "category": "supply", "cluster": "cluster-supply-chain", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Lockfile cryptographic integrity checks, known high/critical CVE build gates, verified registry origins, and build script execution bounds.", "owasp": "OWASP A06:2021 - Vulnerable and Outdated Components", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28"}, "TG-BIZ-001": {"rule_id": "TG-BIZ-001", "title": "Client-Controlled Sensitive Business Value", "severity": "Critical", "category": "biz", "cluster": "cluster-biz-logic", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Strict positive financial amount constraints, atomic database transaction locks, and coupon stacking bounds to prevent race conditions.", "owasp": "OWASP A04:2021 - Insecure Design", "compliance": "HIPAA \u00a7 164.312(c)(1) Data Integrity / SOC 2 CC7.1"}, "TG-BIZ-002": {"rule_id": "TG-BIZ-002", "title": "Replayable One-Time Operation", "severity": "Critical", "category": "biz", "cluster": "cluster-biz-logic", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Strict positive financial amount constraints, atomic database transaction locks, and coupon stacking bounds to prevent race conditions.", "owasp": "OWASP A04:2021 - Insecure Design", "compliance": "HIPAA \u00a7 164.312(c)(1) Data Integrity / SOC 2 CC7.1"}, "TG-BIZ-003": {"rule_id": "TG-BIZ-003", "title": "Missing Workflow-State Validation", "severity": "Critical", "category": "biz", "cluster": "cluster-biz-logic", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Strict positive financial amount constraints, atomic database transaction locks, and coupon stacking bounds to prevent race conditions.", "owasp": "OWASP A04:2021 - Insecure Design", "compliance": "HIPAA \u00a7 164.312(c)(1) Data Integrity / SOC 2 CC7.1"}, "TG-BIZ-004": {"rule_id": "TG-BIZ-004", "title": "Unrestricted Sensitive Business Flow", "severity": "Critical", "category": "biz", "cluster": "cluster-biz-logic", "layer_id": "layer-auth", "layer_name": "Identity, Auth & Real-Time", "guarantee": "Strict positive financial amount constraints, atomic database transaction locks, and coupon stacking bounds to prevent race conditions.", "owasp": "OWASP A04:2021 - Insecure Design", "compliance": "HIPAA \u00a7 164.312(c)(1) Data Integrity / SOC 2 CC7.1"}, "TG-CACHE-001": {"rule_id": "TG-CACHE-001", "title": "Sensitive Response Publicly Cacheable", "severity": "Critical", "category": "cache", "cluster": "cluster-cache", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Mandatory Cache-Control: no-store on authenticated sensitive responses and unkeyed HTTP header sanitization to prevent web cache poisoning.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20"}, "TG-CACHE-002": {"rule_id": "TG-CACHE-002", "title": "Missing User Cache Isolation", "severity": "Critical", "category": "cache", "cluster": "cluster-cache", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Mandatory Cache-Control: no-store on authenticated sensitive responses and unkeyed HTTP header sanitization to prevent web cache poisoning.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20"}, "TG-CACHE-003": {"rule_id": "TG-CACHE-003", "title": "Sensitive Data in URL", "severity": "Critical", "category": "cache", "cluster": "cluster-cache", "layer_id": "layer-db", "layer_name": "Database, GraphQL & Cache", "guarantee": "Mandatory Cache-Control: no-store on authenticated sensitive responses and unkeyed HTTP header sanitization to prevent web cache poisoning.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20"}, "TG-CLIENT-001": {"rule_id": "TG-CLIENT-001", "title": "Public Production Source Maps", "severity": "Medium", "category": "client", "cluster": "cluster-client-leak", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero server role keys, database master credentials, or private API secrets leaked into frontend bundles or public environment variables.", "owasp": "OWASP A01:2021 - Broken Access Control / A05: Misconfiguration", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-CLIENT-002": {"rule_id": "TG-CLIENT-002", "title": "Sensitive Client Bundle Content", "severity": "High", "category": "client", "cluster": "cluster-client-leak", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Zero server role keys, database master credentials, or private API secrets leaked into frontend bundles or public environment variables.", "owasp": "OWASP A01:2021 - Broken Access Control / A05: Misconfiguration", "compliance": "SOC 2 CC6.1 / ISO 27001 A.8.24 / HIPAA \u00a7 164.312(e)(1)"}, "TG-PLATFORM-001": {"rule_id": "TG-PLATFORM-001", "title": "Wildcard CORS With Credentials", "severity": "High", "category": "platform", "cluster": "cluster-auth-bypass", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Mandatory production security headers (CSP, HSTS max-age>=31536000, X-Content-Type-Options: nosniff, X-Frame-Options: DENY) and debug mode suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(e)(1)"}, "TG-PLATFORM-002": {"rule_id": "TG-PLATFORM-002", "title": "Missing Security Headers", "severity": "Medium", "category": "platform", "cluster": "cluster-platform", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Mandatory production security headers (CSP, HSTS max-age>=31536000, X-Content-Type-Options: nosniff, X-Frame-Options: DENY) and debug mode suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(e)(1)"}, "TG-PLATFORM-003": {"rule_id": "TG-PLATFORM-003", "title": "Production Stack Trace Exposure", "severity": "Medium", "category": "platform", "cluster": "cluster-platform", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Mandatory production security headers (CSP, HSTS max-age>=31536000, X-Content-Type-Options: nosniff, X-Frame-Options: DENY) and debug mode suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(e)(1)"}, "TG-PLATFORM-004": {"rule_id": "TG-PLATFORM-004", "title": "Missing Request Size Limits", "severity": "Medium", "category": "platform", "cluster": "cluster-platform", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Mandatory production security headers (CSP, HSTS max-age>=31536000, X-Content-Type-Options: nosniff, X-Frame-Options: DENY) and debug mode suppression.", "owasp": "OWASP A05:2021 - Security Misconfiguration", "compliance": "SOC 2 CC6.7 / ISO 27001 A.8.20 / HIPAA \u00a7 164.312(e)(1)"}, "TG-DIFF-001": {"rule_id": "TG-DIFF-001", "title": "Accidental Security Check Bypass in Patch Additions", "severity": "Critical", "category": "transport", "cluster": "cluster-transport-security", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Strict Ponytail Protocol enforcement (<= 35 additions, <= 25 deletions per patch) and zero security suppression pragmas (nosec pragmas, InsecureSkipVerify).", "owasp": "OWASP A08:2021 - Software Integrity", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(c)(1)"}, "TG-DIFF-002": {"rule_id": "TG-DIFF-002", "title": "Secret and Token Ingestion in Patch Additions", "severity": "Critical", "category": "diff", "cluster": "cluster-credentials-exposure", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Strict Ponytail Protocol enforcement (<= 35 additions, <= 25 deletions per patch) and zero security suppression pragmas (nosec pragmas, InsecureSkipVerify).", "owasp": "OWASP A08:2021 - Software Integrity", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(c)(1)"}, "TG-DIFF-003": {"rule_id": "TG-DIFF-003", "title": "Unintentional Removal of Tenant Filter in Patch Deletions", "severity": "High", "category": "diff", "cluster": "cluster-tenant-isolation", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Strict Ponytail Protocol enforcement (<= 35 additions, <= 25 deletions per patch) and zero security suppression pragmas (nosec pragmas, InsecureSkipVerify).", "owasp": "OWASP A08:2021 - Software Integrity", "compliance": "SOC 2 CC7.1 / ISO 27001 A.8.28 / HIPAA \u00a7 164.312(c)(1)"}, "TG-EDGE-001": {"rule_id": "TG-EDGE-001", "title": "Serverless & Edge Global State Memory Leakage", "severity": "High", "category": "edge", "cluster": "cluster-edge", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Subrequest fan-out limits (<= 50 per invocation), serverless wall-clock execution timeouts (<= 30s), and memory bounds for edge functions.", "owasp": "OWASP A04:2021 - Insecure Design", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}, "TG-EDGE-003": {"rule_id": "TG-EDGE-003", "title": "AWS Lambda Ephemeral Execution & Cold Start Security", "severity": "High", "category": "edge", "cluster": "cluster-edge", "layer_id": "layer-platform", "layer_name": "Platform, Supply Chain & Secrets", "guarantee": "Subrequest fan-out limits (<= 50 per invocation), serverless wall-clock execution timeouts (<= 30s), and memory bounds for edge functions.", "owasp": "OWASP A04:2021 - Insecure Design", "compliance": "SOC 2 CC6.6 / ISO 27001 A.8.20"}};
|
|
2336
|
+
const VIOLATED_RULE_IDS = [];
|
|
2337
|
+
|
|
2338
|
+
let currentSeverity = 'all';
|
|
2339
|
+
let currentStatus = 'all';
|
|
2340
|
+
let currentDirectory = '';
|
|
2341
|
+
let currentRecipeCategory = 'all';
|
|
2342
|
+
let BASE_POSTURE_SCORE = 100;
|
|
2343
|
+
|
|
2344
|
+
// ── Theme Switcher ──
|
|
2345
|
+
function initTheme() {
|
|
2346
|
+
const saved = localStorage.getItem('tg_theme');
|
|
2347
|
+
const btn = document.getElementById('themeToggleBtn');
|
|
2348
|
+
if (saved === 'light') {
|
|
2349
|
+
document.body.classList.add('light-theme');
|
|
2350
|
+
if (btn) btn.innerHTML = '🌙 Dark Mode';
|
|
2351
|
+
} else {
|
|
2352
|
+
document.body.classList.remove('light-theme');
|
|
2353
|
+
if (btn) btn.innerHTML = '☀️ Light Mode';
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function toggleTheme() {
|
|
2358
|
+
const isLight = document.body.classList.toggle('light-theme');
|
|
2359
|
+
localStorage.setItem('tg_theme', isLight ? 'light' : 'dark');
|
|
2360
|
+
const btn = document.getElementById('themeToggleBtn');
|
|
2361
|
+
if (btn) btn.innerHTML = isLight ? '🌙 Dark Mode' : '☀️ Light Mode';
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
document.addEventListener('DOMContentLoaded', initTheme);
|
|
2365
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
2366
|
+
initTheme();
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// ── What-If Posture Score Simulator ──
|
|
2370
|
+
function updateSimulatedScore() {
|
|
2371
|
+
const checkboxes = document.querySelectorAll('.sim-fix-chk');
|
|
2372
|
+
const fixedIds = new Set();
|
|
2373
|
+
checkboxes.forEach(cb => {
|
|
2374
|
+
if (cb.checked) {
|
|
2375
|
+
fixedIds.add(cb.getAttribute('data-finding-id'));
|
|
2376
|
+
}
|
|
2377
|
+
});
|
|
2378
|
+
|
|
2379
|
+
let remCrit = 0, remHigh = 0, remMed = 0, remLow = 0;
|
|
2380
|
+
RAW_FINDINGS.forEach(f => {
|
|
2381
|
+
if (f.is_canary) return;
|
|
2382
|
+
if (fixedIds.has(f.finding_id)) return; // Simulated as fixed
|
|
2383
|
+
const s = f.severity;
|
|
2384
|
+
if (s === 'Critical') remCrit++;
|
|
2385
|
+
else if (s === 'High') remHigh++;
|
|
2386
|
+
else if (s === 'Medium') remMed++;
|
|
2387
|
+
else if (s === 'Low') remLow++;
|
|
2388
|
+
});
|
|
2389
|
+
|
|
2390
|
+
const penalty = (remCrit * 25) + (remHigh * 15) + (remMed * 5) + (remLow * 2);
|
|
2391
|
+
const newScore = Math.max(0, Math.min(100, 100 - penalty));
|
|
2392
|
+
|
|
2393
|
+
// Update projected UI labels
|
|
2394
|
+
const projEl = document.getElementById('simProjectedVal');
|
|
2395
|
+
const deltaEl = document.getElementById('simDeltaBadge');
|
|
2396
|
+
if (projEl) projEl.textContent = newScore;
|
|
2397
|
+
const delta = newScore - BASE_POSTURE_SCORE;
|
|
2398
|
+
if (deltaEl) {
|
|
2399
|
+
deltaEl.textContent = (delta >= 0 ? '+' : '') + delta + ' pts';
|
|
2400
|
+
deltaEl.className = 'badge ' + (delta > 0 ? 'badge-success' : 'badge-primary');
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// Animate SVG gauge to simulated score
|
|
2404
|
+
animateGaugeTo(newScore);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
function simulateFixAll(sev) {
|
|
2408
|
+
const checkboxes = document.querySelectorAll('.sim-fix-chk');
|
|
2409
|
+
checkboxes.forEach(cb => {
|
|
2410
|
+
const cbSev = cb.getAttribute('data-severity');
|
|
2411
|
+
if (sev === 'all' || cbSev === sev) {
|
|
2412
|
+
cb.checked = true;
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
updateSimulatedScore();
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
function resetSimulatedFixes() {
|
|
2419
|
+
const checkboxes = document.querySelectorAll('.sim-fix-chk');
|
|
2420
|
+
checkboxes.forEach(cb => { cb.checked = false; });
|
|
2421
|
+
updateSimulatedScore();
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
function animateGaugeTo(targetScore) {
|
|
2425
|
+
const circumference = 251.2;
|
|
2426
|
+
const targetOffset = circumference - (circumference * (targetScore / 100.0));
|
|
2427
|
+
const gaugeEl = document.getElementById('gaugeCircleFill');
|
|
2428
|
+
const textEl = document.getElementById('postureScoreText');
|
|
2429
|
+
|
|
2430
|
+
if (gaugeEl) {
|
|
2431
|
+
gaugeEl.style.strokeDashoffset = targetOffset;
|
|
2432
|
+
}
|
|
2433
|
+
if (textEl) {
|
|
2434
|
+
textEl.textContent = targetScore;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// ── Invariant Matrix Filters & Inspection ──
|
|
2439
|
+
function filterInvariants() {
|
|
2440
|
+
const input = document.getElementById('invSearchInput');
|
|
2441
|
+
const clearBtn = document.getElementById('invSearchClearBtn');
|
|
2442
|
+
const q = (input ? input.value : '').toLowerCase().trim();
|
|
2443
|
+
if (clearBtn) clearBtn.style.display = q ? 'block' : 'none';
|
|
2444
|
+
|
|
2445
|
+
const chips = document.querySelectorAll('.inv-chip');
|
|
2446
|
+
let matchCount = 0;
|
|
2447
|
+
chips.forEach(chip => {
|
|
2448
|
+
const s = chip.getAttribute('data-search') || '';
|
|
2449
|
+
const visible = !q || s.includes(q);
|
|
2450
|
+
chip.style.display = visible ? 'inline-flex' : 'none';
|
|
2451
|
+
if (visible) matchCount++;
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
const countEl = document.getElementById('invMatchCount');
|
|
2455
|
+
if (countEl) {
|
|
2456
|
+
countEl.textContent = q ? '(' + matchCount + ' matching)' : '';
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
function clearInvSearch() {
|
|
2461
|
+
const input = document.getElementById('invSearchInput');
|
|
2462
|
+
if (input) input.value = '';
|
|
2463
|
+
filterInvariants();
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
function inspectInvariant(ruleId) {
|
|
2467
|
+
const item = INVARIANT_CATALOG[ruleId];
|
|
2468
|
+
if (!item) return;
|
|
2469
|
+
const modal = document.getElementById('invariantModal');
|
|
2470
|
+
const isViolated = VIOLATED_RULE_IDS.includes(ruleId);
|
|
2471
|
+
|
|
2472
|
+
document.getElementById('modalInvRule').textContent = item.rule_id;
|
|
2473
|
+
document.getElementById('modalInvTitle').textContent = item.title;
|
|
2474
|
+
document.getElementById('modalInvLayer').textContent = item.layer_name + ' · ' + item.category;
|
|
2475
|
+
document.getElementById('modalInvGuarantee').textContent = item.guarantee;
|
|
2476
|
+
|
|
2477
|
+
const sevEl = document.getElementById('modalInvSev');
|
|
2478
|
+
sevEl.textContent = item.severity;
|
|
2479
|
+
sevEl.className = 'badge ' + (item.severity === 'Critical' ? 'badge-critical' : (item.severity === 'High' ? 'badge-high' : 'badge-medium'));
|
|
2480
|
+
|
|
2481
|
+
const statusEl = document.getElementById('modalInvStatus');
|
|
2482
|
+
statusEl.innerHTML = isViolated ? '⚠ Violated' : '✔ Defended';
|
|
2483
|
+
statusEl.className = 'badge ' + (isViolated ? 'badge-critical' : 'badge-success');
|
|
2484
|
+
|
|
2485
|
+
const cmdBox = document.getElementById('modalInvCmd');
|
|
2486
|
+
cmdBox.innerHTML = `<code>npx torusguard harden --rule ${item.rule_id}</code>
|
|
2487
|
+
<button class="btn-copy" onclick="copyText('npx torusguard harden --rule ${item.rule_id}', this); event.stopPropagation();">📋 Copy</button>`;
|
|
2488
|
+
|
|
2489
|
+
if (modal) modal.style.display = 'flex';
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
function closeInvariantModal(e) {
|
|
2493
|
+
const modal = document.getElementById('invariantModal');
|
|
2494
|
+
if (modal) modal.style.display = 'none';
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
document.addEventListener('keydown', function(e) {
|
|
2498
|
+
if (e.key === 'Escape') closeInvariantModal();
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
// ── Golden Recipes View Switcher & Category Filter ──
|
|
2502
|
+
function setRecipeView(recipeId, viewMode) {
|
|
2503
|
+
const diffView = document.getElementById('recipe-view-diff-' + recipeId);
|
|
2504
|
+
const splitView = document.getElementById('recipe-view-split-' + recipeId);
|
|
2505
|
+
const btnDiff = document.getElementById('btn-view-diff-' + recipeId);
|
|
2506
|
+
const btnSplit = document.getElementById('btn-view-split-' + recipeId);
|
|
2507
|
+
|
|
2508
|
+
if (viewMode === 'diff') {
|
|
2509
|
+
if (diffView) diffView.style.display = 'block';
|
|
2510
|
+
if (splitView) splitView.style.display = 'none';
|
|
2511
|
+
if (btnDiff) btnDiff.classList.add('active');
|
|
2512
|
+
if (btnSplit) btnSplit.classList.remove('active');
|
|
2513
|
+
} else {
|
|
2514
|
+
if (diffView) diffView.style.display = 'none';
|
|
2515
|
+
if (splitView) splitView.style.display = 'grid';
|
|
2516
|
+
if (btnDiff) btnDiff.classList.remove('active');
|
|
2517
|
+
if (btnSplit) btnSplit.classList.add('active');
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
function filterRecipeCategory(cat) {
|
|
2522
|
+
currentRecipeCategory = cat;
|
|
2523
|
+
document.querySelectorAll('[data-recipe-cat-filter]').forEach(p => {
|
|
2524
|
+
if (p.getAttribute('data-recipe-cat-filter') === cat) p.classList.add('active');
|
|
2525
|
+
else p.classList.remove('active');
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
document.querySelectorAll('.recipe-card').forEach(card => {
|
|
2529
|
+
const cardCat = card.getAttribute('data-category') || '';
|
|
2530
|
+
if (cat === 'all' || cardCat === cat) {
|
|
2531
|
+
card.style.display = '';
|
|
2532
|
+
} else {
|
|
2533
|
+
card.style.display = 'none';
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// ── Findings Filtering & Drawer Lifecycle ──
|
|
2539
|
+
function filterBySeverity(sev) {
|
|
2540
|
+
currentSeverity = sev;
|
|
2541
|
+
document.querySelectorAll('[data-sev-filter]').forEach(p => {
|
|
2542
|
+
if (p.getAttribute('data-sev-filter') === sev) p.classList.add('active');
|
|
2543
|
+
else p.classList.remove('active');
|
|
2544
|
+
});
|
|
2545
|
+
searchFindings();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function filterByStatus(status) {
|
|
2549
|
+
currentStatus = status;
|
|
2550
|
+
document.querySelectorAll('[data-status-filter]').forEach(p => {
|
|
2551
|
+
if (p.getAttribute('data-status-filter') === status) p.classList.add('active');
|
|
2552
|
+
else p.classList.remove('active');
|
|
2553
|
+
});
|
|
2554
|
+
searchFindings();
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
function filterFindings(sev) {
|
|
2558
|
+
filterBySeverity(sev);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function filterByDirectory(dir) {
|
|
2562
|
+
currentDirectory = dir.replace(/\/$/, '');
|
|
2563
|
+
const chip = document.getElementById('dirFilterChip');
|
|
2564
|
+
const chipName = document.getElementById('dirFilterName');
|
|
2565
|
+
if (chip && chipName) {
|
|
2566
|
+
chipName.textContent = currentDirectory;
|
|
2567
|
+
chip.style.display = 'inline-flex';
|
|
2568
|
+
}
|
|
2569
|
+
searchFindings();
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
function clearDirectoryFilter() {
|
|
2573
|
+
currentDirectory = '';
|
|
2574
|
+
const chip = document.getElementById('dirFilterChip');
|
|
2575
|
+
if (chip) chip.style.display = 'none';
|
|
2576
|
+
searchFindings();
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function clearSearch() {
|
|
2580
|
+
const searchInput = document.getElementById('searchInput');
|
|
2581
|
+
if (searchInput) searchInput.value = '';
|
|
2582
|
+
searchFindings();
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
function resetAllFilters() {
|
|
2586
|
+
currentSeverity = 'all';
|
|
2587
|
+
currentStatus = 'all';
|
|
2588
|
+
currentDirectory = '';
|
|
2589
|
+
const searchInput = document.getElementById('searchInput');
|
|
2590
|
+
if (searchInput) searchInput.value = '';
|
|
2591
|
+
|
|
2592
|
+
document.querySelectorAll('[data-sev-filter]').forEach(p => {
|
|
2593
|
+
if (p.getAttribute('data-sev-filter') === 'all') p.classList.add('active');
|
|
2594
|
+
else p.classList.remove('active');
|
|
2595
|
+
});
|
|
2596
|
+
document.querySelectorAll('[data-status-filter]').forEach(p => {
|
|
2597
|
+
if (p.getAttribute('data-status-filter') === 'all') p.classList.add('active');
|
|
2598
|
+
else p.classList.remove('active');
|
|
2599
|
+
});
|
|
2600
|
+
const chip = document.getElementById('dirFilterChip');
|
|
2601
|
+
if (chip) chip.style.display = 'none';
|
|
2602
|
+
|
|
2603
|
+
searchFindings();
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
function toggleDefendedVisibility() {
|
|
2607
|
+
const body = document.getElementById('defendedMatrixBody');
|
|
2608
|
+
const icon = document.getElementById('defendedToggleIcon');
|
|
2609
|
+
if (!body) return;
|
|
2610
|
+
if (body.style.display === 'none') {
|
|
2611
|
+
body.style.display = 'block';
|
|
2612
|
+
icon.innerHTML = '▼ Collapse Matrix';
|
|
2613
|
+
} else {
|
|
2614
|
+
body.style.display = 'none';
|
|
2615
|
+
icon.innerHTML = '▶ Expand Matrix';
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
function toggleDefendedSection() {
|
|
2620
|
+
const sec = document.getElementById('defendedSection');
|
|
2621
|
+
if (sec) sec.scrollIntoView({ behavior: 'smooth' });
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
function searchFindings() {
|
|
2625
|
+
const searchInput = document.getElementById('searchInput');
|
|
2626
|
+
const clearBtn = document.getElementById('searchClearBtn');
|
|
2627
|
+
const query = (searchInput ? searchInput.value : '').toLowerCase().trim();
|
|
2628
|
+
|
|
2629
|
+
if (clearBtn) clearBtn.style.display = query ? 'block' : 'none';
|
|
2630
|
+
|
|
2631
|
+
const rows = document.querySelectorAll('.finding-row');
|
|
2632
|
+
let visibleCount = 0;
|
|
2633
|
+
|
|
2634
|
+
rows.forEach(r => {
|
|
2635
|
+
const rSev = r.getAttribute('data-severity') || '';
|
|
2636
|
+
const rStatus = r.getAttribute('data-status') || 'open';
|
|
2637
|
+
const rSearch = r.getAttribute('data-search') || '';
|
|
2638
|
+
const rPath = r.getAttribute('data-path') || '';
|
|
2639
|
+
const drawerId = r.getAttribute('data-drawer-id');
|
|
2640
|
+
const drawer = document.getElementById(drawerId);
|
|
2641
|
+
|
|
2642
|
+
let matchSev = (currentSeverity === 'all') || (rSev === currentSeverity);
|
|
2643
|
+
if (currentSeverity === 'medium') {
|
|
2644
|
+
matchSev = (rSev === 'medium' || rSev === 'low');
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
let matchStatus = (currentStatus === 'all') || (rStatus === currentStatus);
|
|
2648
|
+
|
|
2649
|
+
let matchDir = true;
|
|
2650
|
+
if (currentDirectory) {
|
|
2651
|
+
matchDir = rPath.toLowerCase().startsWith(currentDirectory.toLowerCase());
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
const matchQuery = !query || rSearch.includes(query);
|
|
2655
|
+
const visible = matchSev && matchStatus && matchDir && matchQuery;
|
|
2656
|
+
|
|
2657
|
+
if (visible) visibleCount++;
|
|
2658
|
+
r.style.display = visible ? '' : 'none';
|
|
2659
|
+
if (drawer && !visible) {
|
|
2660
|
+
drawer.style.display = 'none';
|
|
2661
|
+
r.classList.remove('expanded');
|
|
2662
|
+
}
|
|
2663
|
+
});
|
|
2664
|
+
|
|
2665
|
+
const countEl = document.getElementById('visibleCount');
|
|
2666
|
+
if (countEl) countEl.textContent = visibleCount;
|
|
2667
|
+
|
|
2668
|
+
const resetBtn = document.getElementById('btnResetFilters');
|
|
2669
|
+
const hasActiveFilters = currentSeverity !== 'all' || currentStatus !== 'all' || currentDirectory !== '' || query !== '';
|
|
2670
|
+
if (resetBtn) {
|
|
2671
|
+
resetBtn.style.display = hasActiveFilters ? 'inline-block' : 'none';
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
const summaryEl = document.getElementById('activeFilterSummary');
|
|
2675
|
+
if (summaryEl) {
|
|
2676
|
+
const parts = [];
|
|
2677
|
+
if (currentSeverity !== 'all') parts.push(`Sev: ${currentSeverity}`);
|
|
2678
|
+
if (currentStatus !== 'all') parts.push(`Status: ${currentStatus}`);
|
|
2679
|
+
if (currentDirectory) parts.push(`Dir: ${currentDirectory}`);
|
|
2680
|
+
if (query) parts.push(`Query: "${query}"`);
|
|
2681
|
+
summaryEl.textContent = parts.length ? `(${parts.join(', ')})` : '';
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function initPostureGauge() {
|
|
2686
|
+
const targetScore = 100;
|
|
2687
|
+
const circumference = 251.2;
|
|
2688
|
+
const targetOffset = circumference - (circumference * (targetScore / 100.0));
|
|
2689
|
+
const gaugeEl = document.getElementById('gaugeCircleFill');
|
|
2690
|
+
const textEl = document.getElementById('postureScoreText');
|
|
2691
|
+
|
|
2692
|
+
setTimeout(() => {
|
|
2693
|
+
if (gaugeEl) {
|
|
2694
|
+
gaugeEl.style.strokeDashoffset = targetOffset;
|
|
2695
|
+
}
|
|
2696
|
+
}, 50);
|
|
2697
|
+
|
|
2698
|
+
if (textEl) {
|
|
2699
|
+
const duration = 800;
|
|
2700
|
+
const startTime = performance.now();
|
|
2701
|
+
function updateCount(now) {
|
|
2702
|
+
const elapsed = now - startTime;
|
|
2703
|
+
const progress = Math.min(elapsed / duration, 1.0);
|
|
2704
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
2705
|
+
const val = Math.round(eased * targetScore);
|
|
2706
|
+
textEl.textContent = val;
|
|
2707
|
+
if (progress < 1.0) {
|
|
2708
|
+
requestAnimationFrame(updateCount);
|
|
2709
|
+
} else {
|
|
2710
|
+
textEl.textContent = targetScore;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
requestAnimationFrame(updateCount);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
document.addEventListener('DOMContentLoaded', initPostureGauge);
|
|
2717
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
2718
|
+
initPostureGauge();
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
function toggleDrawer(row) {
|
|
2722
|
+
const drawerId = row.getAttribute('data-drawer-id');
|
|
2723
|
+
const drawer = document.getElementById(drawerId);
|
|
2724
|
+
if (!drawer) return;
|
|
2725
|
+
|
|
2726
|
+
const isExpanded = row.classList.contains('expanded');
|
|
2727
|
+
if (isExpanded) {
|
|
2728
|
+
row.classList.remove('expanded');
|
|
2729
|
+
drawer.style.display = 'none';
|
|
2730
|
+
} else {
|
|
2731
|
+
row.classList.add('expanded');
|
|
2732
|
+
drawer.style.display = '';
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
function copyText(text, btn) {
|
|
2737
|
+
const cleanText = text.replace(/\\n/g, '\n');
|
|
2738
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
2739
|
+
navigator.clipboard.writeText(cleanText).then(() => {
|
|
2740
|
+
showCopied(btn);
|
|
2741
|
+
}).catch(() => {
|
|
2742
|
+
fallbackCopy(cleanText, btn);
|
|
2743
|
+
});
|
|
2744
|
+
} else {
|
|
2745
|
+
fallbackCopy(cleanText, btn);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
function fallbackCopy(text, btn) {
|
|
2750
|
+
const ta = document.createElement('textarea');
|
|
2751
|
+
ta.value = text;
|
|
2752
|
+
ta.style.position = 'fixed';
|
|
2753
|
+
ta.style.opacity = '0';
|
|
2754
|
+
document.body.appendChild(ta);
|
|
2755
|
+
ta.select();
|
|
2756
|
+
try {
|
|
2757
|
+
document.execCommand('copy');
|
|
2758
|
+
showCopied(btn);
|
|
2759
|
+
} catch (e) {}
|
|
2760
|
+
document.body.removeChild(ta);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
function showCopied(btn) {
|
|
2764
|
+
const orig = btn.innerHTML;
|
|
2765
|
+
btn.innerHTML = '✔ Copied!';
|
|
2766
|
+
btn.classList.add('copied');
|
|
2767
|
+
setTimeout(() => {
|
|
2768
|
+
btn.innerHTML = orig;
|
|
2769
|
+
btn.classList.remove('copied');
|
|
2770
|
+
}, 2000);
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function downloadSarif() {
|
|
2774
|
+
const sarifData = {
|
|
2775
|
+
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
2776
|
+
"version": "2.1.0",
|
|
2777
|
+
"runs": [{
|
|
2778
|
+
"tool": {
|
|
2779
|
+
"driver": {
|
|
2780
|
+
"name": "TorusGuard",
|
|
2781
|
+
"semanticVersion": "1.3.6",
|
|
2782
|
+
"informationUri": "https://github.com/githubmofo/TorusGuard",
|
|
2783
|
+
"rules": SARIF_RULES
|
|
2784
|
+
}
|
|
2785
|
+
},
|
|
2786
|
+
"results": RAW_FINDINGS.map(f => ({
|
|
2787
|
+
"ruleId": f.rule_id || "TG-GEN",
|
|
2788
|
+
"message": { "text": f.description || f.title || "Violation" },
|
|
2789
|
+
"level": f.severity === "Critical" ? "error" : (f.severity === "High" ? "error" : "warning"),
|
|
2790
|
+
"locations": [{
|
|
2791
|
+
"physicalLocation": {
|
|
2792
|
+
"artifactLocation": { "uri": f.file_path || "unknown" },
|
|
2793
|
+
"region": { "startLine": f.line_number || 1 }
|
|
2794
|
+
}
|
|
2795
|
+
}]
|
|
2796
|
+
}))
|
|
2797
|
+
}]
|
|
2798
|
+
};
|
|
2799
|
+
|
|
2800
|
+
const blob = new Blob([JSON.stringify(sarifData, null, 2)], { type: "application/json" });
|
|
2801
|
+
const url = URL.createObjectURL(blob);
|
|
2802
|
+
const a = document.createElement("a");
|
|
2803
|
+
a.href = url;
|
|
2804
|
+
a.download = "torusguard-report.sarif";
|
|
2805
|
+
document.body.appendChild(a);
|
|
2806
|
+
a.click();
|
|
2807
|
+
document.body.removeChild(a);
|
|
2808
|
+
URL.revokeObjectURL(url);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
function downloadCsv() {
|
|
2812
|
+
let csv = "Finding ID,Rule ID,Severity,Title,File Path,Line Number,Confidence\n";
|
|
2813
|
+
RAW_FINDINGS.forEach(f => {
|
|
2814
|
+
const row = [
|
|
2815
|
+
`"${(f.finding_id || '').replace(/"/g, '""')}"`,
|
|
2816
|
+
`"${(f.rule_id || '').replace(/"/g, '""')}"`,
|
|
2817
|
+
`"${(f.severity || '').replace(/"/g, '""')}"`,
|
|
2818
|
+
`"${(f.title || '').replace(/"/g, '""')}"`,
|
|
2819
|
+
`"${(f.file_path || '').replace(/"/g, '""')}"`,
|
|
2820
|
+
f.line_number || 0,
|
|
2821
|
+
f.confidence_score || 70
|
|
2822
|
+
];
|
|
2823
|
+
csv += row.join(",") + "\n";
|
|
2824
|
+
});
|
|
2825
|
+
|
|
2826
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
2827
|
+
const url = URL.createObjectURL(blob);
|
|
2828
|
+
const a = document.createElement("a");
|
|
2829
|
+
a.href = url;
|
|
2830
|
+
a.download = "torusguard-report.csv";
|
|
2831
|
+
document.body.appendChild(a);
|
|
2832
|
+
a.click();
|
|
2833
|
+
document.body.removeChild(a);
|
|
2834
|
+
URL.revokeObjectURL(url);
|
|
2835
|
+
}
|
|
2836
|
+
</script>
|
|
455
2837
|
</body>
|
|
456
2838
|
</html>
|