supasec 1.0.3 → 1.0.5
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/Feature-List.md +233 -0
- package/README.md +53 -12
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +82 -26
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/snapshot.d.ts +32 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +282 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/reporters/html.d.ts +3 -2
- package/dist/reporters/html.d.ts.map +1 -1
- package/dist/reporters/html.js +844 -538
- package/dist/reporters/html.js.map +1 -1
- package/dist/reporters/terminal.d.ts +38 -2
- package/dist/reporters/terminal.d.ts.map +1 -1
- package/dist/reporters/terminal.js +292 -131
- package/dist/reporters/terminal.js.map +1 -1
- package/dist/scanners/auth/analyzer.d.ts +40 -0
- package/dist/scanners/auth/analyzer.d.ts.map +1 -0
- package/dist/scanners/auth/analyzer.js +673 -0
- package/dist/scanners/auth/analyzer.js.map +1 -0
- package/dist/scanners/auth/index.d.ts +6 -0
- package/dist/scanners/auth/index.d.ts.map +1 -0
- package/dist/scanners/auth/index.js +22 -0
- package/dist/scanners/auth/index.js.map +1 -0
- package/dist/scanners/edge/analyzer.d.ts +35 -0
- package/dist/scanners/edge/analyzer.d.ts.map +1 -0
- package/dist/scanners/edge/analyzer.js +614 -0
- package/dist/scanners/edge/analyzer.js.map +1 -0
- package/dist/scanners/edge/index.d.ts +6 -0
- package/dist/scanners/edge/index.d.ts.map +1 -0
- package/dist/scanners/edge/index.js +22 -0
- package/dist/scanners/edge/index.js.map +1 -0
- package/dist/scanners/functions/analyzer.d.ts +41 -0
- package/dist/scanners/functions/analyzer.d.ts.map +1 -0
- package/dist/scanners/functions/analyzer.js +378 -0
- package/dist/scanners/functions/analyzer.js.map +1 -0
- package/dist/scanners/functions/index.d.ts +6 -0
- package/dist/scanners/functions/index.d.ts.map +1 -0
- package/dist/scanners/functions/index.js +22 -0
- package/dist/scanners/functions/index.js.map +1 -0
- package/dist/scanners/git/index.d.ts +6 -0
- package/dist/scanners/git/index.d.ts.map +1 -0
- package/dist/scanners/git/index.js +22 -0
- package/dist/scanners/git/index.js.map +1 -0
- package/dist/scanners/git/scanner.d.ts +22 -0
- package/dist/scanners/git/scanner.d.ts.map +1 -0
- package/dist/scanners/git/scanner.js +531 -0
- package/dist/scanners/git/scanner.js.map +1 -0
- package/dist/scanners/https/analyzer.d.ts +42 -0
- package/dist/scanners/https/analyzer.d.ts.map +1 -0
- package/dist/scanners/https/analyzer.js +470 -0
- package/dist/scanners/https/analyzer.js.map +1 -0
- package/dist/scanners/https/index.d.ts +8 -0
- package/dist/scanners/https/index.d.ts.map +1 -0
- package/dist/scanners/https/index.js +17 -0
- package/dist/scanners/https/index.js.map +1 -0
- package/dist/scanners/index.d.ts +6 -0
- package/dist/scanners/index.d.ts.map +1 -1
- package/dist/scanners/index.js +6 -0
- package/dist/scanners/index.js.map +1 -1
- package/dist/scanners/rls/fuzzer.d.ts +40 -0
- package/dist/scanners/rls/fuzzer.d.ts.map +1 -0
- package/dist/scanners/rls/fuzzer.js +360 -0
- package/dist/scanners/rls/fuzzer.js.map +1 -0
- package/dist/scanners/rls/index.d.ts +1 -0
- package/dist/scanners/rls/index.d.ts.map +1 -1
- package/dist/scanners/rls/index.js +1 -0
- package/dist/scanners/rls/index.js.map +1 -1
- package/dist/scanners/secrets/detector.d.ts.map +1 -1
- package/dist/scanners/secrets/detector.js +44 -12
- package/dist/scanners/secrets/detector.js.map +1 -1
- package/dist/scanners/secrets/index.d.ts +1 -0
- package/dist/scanners/secrets/index.d.ts.map +1 -1
- package/dist/scanners/secrets/index.js +4 -0
- package/dist/scanners/secrets/index.js.map +1 -1
- package/dist/scanners/secrets/patterns.d.ts +25 -0
- package/dist/scanners/secrets/patterns.d.ts.map +1 -1
- package/dist/scanners/secrets/patterns.js +138 -27
- package/dist/scanners/secrets/patterns.js.map +1 -1
- package/dist/scanners/storage/analyzer.d.ts +49 -0
- package/dist/scanners/storage/analyzer.d.ts.map +1 -0
- package/dist/scanners/storage/analyzer.js +438 -0
- package/dist/scanners/storage/analyzer.js.map +1 -0
- package/dist/scanners/storage/index.d.ts +6 -0
- package/dist/scanners/storage/index.d.ts.map +1 -0
- package/dist/scanners/storage/index.js +22 -0
- package/dist/scanners/storage/index.js.map +1 -0
- package/package.json +1 -1
- package/reports/{supasec-audityour-app-2026-01-28-17-09-24.html → supasec-audityour-app-2026-01-28-19-42-22.html} +51 -16
- package/reports/supasec-audityour-app-2026-01-28-19-49-18.html +1122 -0
- package/COMPLETION_REPORT.md +0 -324
- package/FIXES_SUMMARY.md +0 -224
- package/IMPLEMENTATION_NOTES.md +0 -305
- package/QUICK_REFERENCE.md +0 -185
- package/REPORTING.md +0 -217
- package/STATUS.md +0 -269
- package/reports/supasec---------app-2026-01-28-16-58-47.html +0 -804
- package/reports/supasec---------app-2026-01-28-17-06-43.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-07-23.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-00.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-20.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-41.html +0 -722
- package/reports/supasec-au---your-app-2026-01-28-17-14-57.html +0 -715
- package/reports/supasec-au---your-app-2026-01-28-17-19-03.html +0 -715
- package/reports/supasec-ex-mple-com-2026-01-28-17-14-52.json +0 -229
- package/reports/supasec-ex-mple-com-2026-01-28-17-15-39.html +0 -715
- package/reports/supasec-ex-mple-com-2026-01-28-17-17-22.html +0 -715
- package/reports/supasec-example-com-2026-01-28-17-15-06.html +0 -715
- package/reports/supasec-my--------------name-com-2026-01-28-17-15-02.html +0 -715
- package/reports/supasec-st-ging-com-2026-01-28-17-16-17.html +0 -715
package/dist/reporters/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* HTML Reporter
|
|
4
|
-
* Generates
|
|
4
|
+
* Generates enterprise-grade professional HTML reports
|
|
5
5
|
*/
|
|
6
6
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
7
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,10 +41,10 @@ exports.generateHTMLReport = generateHTMLReport;
|
|
|
41
41
|
exports.saveHTMLReport = saveHTMLReport;
|
|
42
42
|
const finding_js_1 = require("../models/finding.js");
|
|
43
43
|
/**
|
|
44
|
-
* Generate HTML report from scan result
|
|
44
|
+
* Generate enterprise-grade HTML report from scan result
|
|
45
45
|
*/
|
|
46
46
|
function generateHTMLReport(result, options = {}) {
|
|
47
|
-
const { title = 'SupaSec Security Audit Report', includeDetails = true } = options;
|
|
47
|
+
const { title = 'SupaSec Security Audit Report', includeDetails = true, theme = 'light' } = options;
|
|
48
48
|
const counts = (0, finding_js_1.countFindingsBySeverity)(result.findings);
|
|
49
49
|
const sortedFindings = (0, finding_js_1.sortFindingsBySeverity)(result.findings);
|
|
50
50
|
// Group findings by severity
|
|
@@ -59,6 +59,8 @@ function generateHTMLReport(result, options = {}) {
|
|
|
59
59
|
findingsBySeverity[finding.severity].push(finding);
|
|
60
60
|
}
|
|
61
61
|
const scanDate = new Date(result.scan_metadata.scan_date).toLocaleString();
|
|
62
|
+
const grade = result.grading.overall_grade;
|
|
63
|
+
const score = result.grading.overall_score;
|
|
62
64
|
return `<!DOCTYPE html>
|
|
63
65
|
<html lang="en">
|
|
64
66
|
<head>
|
|
@@ -66,6 +68,25 @@ function generateHTMLReport(result, options = {}) {
|
|
|
66
68
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
67
69
|
<title>${escapeHtml(title)}</title>
|
|
68
70
|
<style>
|
|
71
|
+
:root {
|
|
72
|
+
--color-bg: ${theme === 'dark' ? '#0f172a' : '#f8fafc'};
|
|
73
|
+
--color-card: ${theme === 'dark' ? '#1e293b' : '#ffffff'};
|
|
74
|
+
--color-text: ${theme === 'dark' ? '#f1f5f9' : '#1e293b'};
|
|
75
|
+
--color-text-secondary: ${theme === 'dark' ? '#94a3b8' : '#64748b'};
|
|
76
|
+
--color-border: ${theme === 'dark' ? '#334155' : '#e2e8f0'};
|
|
77
|
+
--color-primary: #3b82f6;
|
|
78
|
+
--color-primary-light: #eff6ff;
|
|
79
|
+
--color-success: #22c55e;
|
|
80
|
+
--color-warning: #f59e0b;
|
|
81
|
+
--color-danger: #ef4444;
|
|
82
|
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
83
|
+
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
84
|
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
85
|
+
--radius-sm: 6px;
|
|
86
|
+
--radius: 12px;
|
|
87
|
+
--radius-lg: 16px;
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
* {
|
|
70
91
|
margin: 0;
|
|
71
92
|
padding: 0;
|
|
@@ -73,140 +94,214 @@ function generateHTMLReport(result, options = {}) {
|
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
body {
|
|
76
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
77
|
-
background:
|
|
97
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
98
|
+
background: var(--color-bg);
|
|
78
99
|
min-height: 100vh;
|
|
79
|
-
color:
|
|
100
|
+
color: var(--color-text);
|
|
80
101
|
line-height: 1.6;
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
.container {
|
|
84
|
-
max-width:
|
|
105
|
+
max-width: 1200px;
|
|
85
106
|
margin: 0 auto;
|
|
86
|
-
padding:
|
|
107
|
+
padding: 24px;
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
/* Header */
|
|
90
111
|
.header {
|
|
91
|
-
background:
|
|
92
|
-
padding:
|
|
93
|
-
border-
|
|
112
|
+
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
|
113
|
+
padding: 32px;
|
|
114
|
+
border-radius: var(--radius-lg);
|
|
115
|
+
margin-bottom: 24px;
|
|
116
|
+
box-shadow: var(--shadow-lg);
|
|
117
|
+
color: white;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.header-top {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: space-between;
|
|
124
|
+
margin-bottom: 24px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.brand {
|
|
94
128
|
display: flex;
|
|
95
129
|
align-items: center;
|
|
96
130
|
gap: 12px;
|
|
97
|
-
margin: -20px -20px 20px -20px;
|
|
98
131
|
}
|
|
99
132
|
|
|
100
133
|
.logo {
|
|
101
|
-
width:
|
|
102
|
-
height:
|
|
103
|
-
background:
|
|
104
|
-
border-radius:
|
|
134
|
+
width: 48px;
|
|
135
|
+
height: 48px;
|
|
136
|
+
background: rgba(255,255,255,0.2);
|
|
137
|
+
border-radius: var(--radius);
|
|
105
138
|
display: flex;
|
|
106
139
|
align-items: center;
|
|
107
140
|
justify-content: center;
|
|
108
|
-
|
|
109
|
-
font-weight:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.header h1 {
|
|
113
|
-
font-size: 20px;
|
|
114
|
-
color: #1e293b;
|
|
115
|
-
font-weight: 600;
|
|
141
|
+
font-size: 24px;
|
|
142
|
+
font-weight: 700;
|
|
143
|
+
backdrop-filter: blur(10px);
|
|
116
144
|
}
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
border-radius: 8px;
|
|
123
|
-
padding: 16px 20px;
|
|
124
|
-
margin-bottom: 24px;
|
|
125
|
-
display: flex;
|
|
126
|
-
align-items: flex-start;
|
|
127
|
-
gap: 12px;
|
|
146
|
+
.brand-text h1 {
|
|
147
|
+
font-size: 24px;
|
|
148
|
+
font-weight: 700;
|
|
149
|
+
margin: 0;
|
|
128
150
|
}
|
|
129
151
|
|
|
130
|
-
.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
margin
|
|
152
|
+
.brand-text p {
|
|
153
|
+
font-size: 13px;
|
|
154
|
+
opacity: 0.9;
|
|
155
|
+
margin: 0;
|
|
134
156
|
}
|
|
135
157
|
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
color: #1e40af;
|
|
139
|
-
margin-bottom: 4px;
|
|
140
|
-
font-weight: 600;
|
|
158
|
+
.report-meta {
|
|
159
|
+
text-align: right;
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
.
|
|
144
|
-
font-size:
|
|
145
|
-
|
|
162
|
+
.report-id {
|
|
163
|
+
font-size: 12px;
|
|
164
|
+
opacity: 0.8;
|
|
165
|
+
font-family: monospace;
|
|
146
166
|
}
|
|
147
167
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
border: 1px solid #e2e8f0;
|
|
152
|
-
border-radius: 12px;
|
|
153
|
-
padding: 24px;
|
|
154
|
-
margin-bottom: 24px;
|
|
168
|
+
.report-date {
|
|
169
|
+
font-size: 13px;
|
|
170
|
+
opacity: 0.9;
|
|
155
171
|
}
|
|
156
172
|
|
|
157
|
-
|
|
173
|
+
/* Grade Section */
|
|
174
|
+
.grade-section {
|
|
158
175
|
display: flex;
|
|
159
176
|
align-items: center;
|
|
160
|
-
gap:
|
|
161
|
-
|
|
177
|
+
gap: 32px;
|
|
178
|
+
padding: 24px;
|
|
179
|
+
background: rgba(255,255,255,0.1);
|
|
180
|
+
border-radius: var(--radius);
|
|
181
|
+
backdrop-filter: blur(10px);
|
|
162
182
|
}
|
|
163
183
|
|
|
164
|
-
.
|
|
165
|
-
width:
|
|
166
|
-
height:
|
|
167
|
-
background: #dcfce7;
|
|
184
|
+
.grade-circle {
|
|
185
|
+
width: 120px;
|
|
186
|
+
height: 120px;
|
|
168
187
|
border-radius: 50%;
|
|
169
188
|
display: flex;
|
|
189
|
+
flex-direction: column;
|
|
170
190
|
align-items: center;
|
|
171
191
|
justify-content: center;
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
background: ${getGradeColor(grade)};
|
|
193
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
|
194
|
+
position: relative;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.grade-circle::before {
|
|
198
|
+
content: '';
|
|
199
|
+
position: absolute;
|
|
200
|
+
inset: 4px;
|
|
201
|
+
border-radius: 50%;
|
|
202
|
+
border: 3px solid rgba(255,255,255,0.3);
|
|
174
203
|
}
|
|
175
204
|
|
|
176
|
-
.
|
|
177
|
-
font-size:
|
|
205
|
+
.grade-letter {
|
|
206
|
+
font-size: 56px;
|
|
207
|
+
font-weight: 800;
|
|
208
|
+
line-height: 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.grade-score {
|
|
212
|
+
font-size: 14px;
|
|
178
213
|
font-weight: 600;
|
|
179
|
-
|
|
214
|
+
opacity: 0.9;
|
|
180
215
|
}
|
|
181
216
|
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
217
|
+
.grade-info {
|
|
218
|
+
flex: 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.grade-info h2 {
|
|
222
|
+
font-size: 20px;
|
|
223
|
+
font-weight: 600;
|
|
224
|
+
margin-bottom: 8px;
|
|
185
225
|
}
|
|
186
226
|
|
|
187
|
-
.
|
|
188
|
-
|
|
227
|
+
.grade-info p {
|
|
228
|
+
font-size: 14px;
|
|
229
|
+
opacity: 0.9;
|
|
230
|
+
margin-bottom: 16px;
|
|
189
231
|
}
|
|
190
232
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
233
|
+
.security-meter {
|
|
234
|
+
height: 8px;
|
|
235
|
+
background: rgba(255,255,255,0.2);
|
|
236
|
+
border-radius: 4px;
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.security-meter-fill {
|
|
241
|
+
height: 100%;
|
|
242
|
+
background: white;
|
|
243
|
+
border-radius: 4px;
|
|
244
|
+
transition: width 1s ease;
|
|
245
|
+
width: ${score}%;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.security-meter-labels {
|
|
249
|
+
display: flex;
|
|
250
|
+
justify-content: space-between;
|
|
251
|
+
margin-top: 4px;
|
|
252
|
+
font-size: 11px;
|
|
253
|
+
opacity: 0.8;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Target Info Card */
|
|
257
|
+
.target-card {
|
|
258
|
+
background: var(--color-card);
|
|
259
|
+
border: 1px solid var(--color-border);
|
|
260
|
+
border-radius: var(--radius);
|
|
261
|
+
padding: 20px;
|
|
262
|
+
margin-bottom: 24px;
|
|
263
|
+
box-shadow: var(--shadow-sm);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.target-card h3 {
|
|
267
|
+
font-size: 12px;
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
text-transform: uppercase;
|
|
270
|
+
letter-spacing: 0.5px;
|
|
271
|
+
color: var(--color-text-secondary);
|
|
272
|
+
margin-bottom: 8px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.target-url {
|
|
276
|
+
font-size: 16px;
|
|
277
|
+
font-weight: 500;
|
|
278
|
+
color: var(--color-primary);
|
|
279
|
+
word-break: break-all;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.target-meta {
|
|
283
|
+
display: flex;
|
|
195
284
|
gap: 24px;
|
|
285
|
+
margin-top: 16px;
|
|
286
|
+
padding-top: 16px;
|
|
287
|
+
border-top: 1px solid var(--color-border);
|
|
196
288
|
}
|
|
197
289
|
|
|
198
|
-
.
|
|
290
|
+
.target-meta-item {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
gap: 4px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.target-meta-item span:first-child {
|
|
199
297
|
font-size: 11px;
|
|
298
|
+
color: var(--color-text-secondary);
|
|
200
299
|
text-transform: uppercase;
|
|
201
|
-
color: #64748b;
|
|
202
|
-
font-weight: 600;
|
|
203
300
|
letter-spacing: 0.5px;
|
|
204
|
-
margin-bottom: 6px;
|
|
205
301
|
}
|
|
206
302
|
|
|
207
|
-
.
|
|
208
|
-
font-size:
|
|
209
|
-
color: #334155;
|
|
303
|
+
.target-meta-item span:last-child {
|
|
304
|
+
font-size: 13px;
|
|
210
305
|
font-weight: 500;
|
|
211
306
|
}
|
|
212
307
|
|
|
@@ -219,92 +314,225 @@ function generateHTMLReport(result, options = {}) {
|
|
|
219
314
|
}
|
|
220
315
|
|
|
221
316
|
.stat-card {
|
|
222
|
-
background:
|
|
223
|
-
border: 1px solid
|
|
224
|
-
border-radius:
|
|
317
|
+
background: var(--color-card);
|
|
318
|
+
border: 1px solid var(--color-border);
|
|
319
|
+
border-radius: var(--radius);
|
|
225
320
|
padding: 20px;
|
|
226
321
|
text-align: center;
|
|
322
|
+
box-shadow: var(--shadow-sm);
|
|
323
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.stat-card:hover {
|
|
327
|
+
transform: translateY(-2px);
|
|
328
|
+
box-shadow: var(--shadow);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.stat-icon {
|
|
332
|
+
width: 40px;
|
|
333
|
+
height: 40px;
|
|
334
|
+
border-radius: var(--radius-sm);
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
margin: 0 auto 12px;
|
|
339
|
+
font-size: 20px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.stat-icon.critical { background: #fef2f2; }
|
|
343
|
+
.stat-icon.high { background: #fffbeb; }
|
|
344
|
+
.stat-icon.medium { background: #fefce8; }
|
|
345
|
+
.stat-icon.low { background: #eff6ff; }
|
|
346
|
+
|
|
347
|
+
.stat-value {
|
|
348
|
+
font-size: 36px;
|
|
349
|
+
font-weight: 700;
|
|
350
|
+
line-height: 1;
|
|
351
|
+
margin-bottom: 4px;
|
|
227
352
|
}
|
|
228
353
|
|
|
354
|
+
.stat-value.critical { color: #dc2626; }
|
|
355
|
+
.stat-value.high { color: #d97706; }
|
|
356
|
+
.stat-value.medium { color: #a16207; }
|
|
357
|
+
.stat-value.low { color: #2563eb; }
|
|
358
|
+
|
|
229
359
|
.stat-label {
|
|
230
|
-
font-size:
|
|
231
|
-
text-transform: uppercase;
|
|
232
|
-
color: #64748b;
|
|
360
|
+
font-size: 12px;
|
|
233
361
|
font-weight: 600;
|
|
362
|
+
text-transform: uppercase;
|
|
234
363
|
letter-spacing: 0.5px;
|
|
235
|
-
|
|
364
|
+
color: var(--color-text-secondary);
|
|
236
365
|
}
|
|
237
366
|
|
|
238
|
-
|
|
239
|
-
|
|
367
|
+
/* Category Scores */
|
|
368
|
+
.category-section {
|
|
369
|
+
background: var(--color-card);
|
|
370
|
+
border: 1px solid var(--color-border);
|
|
371
|
+
border-radius: var(--radius);
|
|
372
|
+
padding: 24px;
|
|
373
|
+
margin-bottom: 24px;
|
|
374
|
+
box-shadow: var(--shadow-sm);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.category-section h2 {
|
|
378
|
+
font-size: 16px;
|
|
379
|
+
font-weight: 600;
|
|
380
|
+
margin-bottom: 20px;
|
|
381
|
+
display: flex;
|
|
382
|
+
align-items: center;
|
|
383
|
+
gap: 8px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.category-grid {
|
|
387
|
+
display: grid;
|
|
388
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
389
|
+
gap: 16px;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.category-item {
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
gap: 12px;
|
|
396
|
+
padding: 16px;
|
|
397
|
+
background: ${theme === 'dark' ? '#334155' : '#f8fafc'};
|
|
398
|
+
border-radius: var(--radius-sm);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.category-grade {
|
|
402
|
+
width: 40px;
|
|
403
|
+
height: 40px;
|
|
404
|
+
border-radius: 50%;
|
|
405
|
+
display: flex;
|
|
406
|
+
align-items: center;
|
|
407
|
+
justify-content: center;
|
|
240
408
|
font-weight: 700;
|
|
241
|
-
|
|
409
|
+
font-size: 14px;
|
|
410
|
+
flex-shrink: 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.category-info {
|
|
414
|
+
flex: 1;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.category-name {
|
|
418
|
+
font-size: 14px;
|
|
419
|
+
font-weight: 600;
|
|
420
|
+
margin-bottom: 4px;
|
|
421
|
+
text-transform: capitalize;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.category-bar {
|
|
425
|
+
height: 6px;
|
|
426
|
+
background: ${theme === 'dark' ? '#475569' : '#e2e8f0'};
|
|
427
|
+
border-radius: 3px;
|
|
428
|
+
overflow: hidden;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.category-bar-fill {
|
|
432
|
+
height: 100%;
|
|
433
|
+
border-radius: 3px;
|
|
434
|
+
transition: width 1s ease;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.category-score {
|
|
438
|
+
font-size: 12px;
|
|
439
|
+
font-weight: 600;
|
|
440
|
+
color: var(--color-text-secondary);
|
|
441
|
+
margin-left: 8px;
|
|
242
442
|
}
|
|
243
443
|
|
|
244
|
-
/*
|
|
245
|
-
.section {
|
|
444
|
+
/* Findings Section */
|
|
445
|
+
.findings-section {
|
|
246
446
|
margin-bottom: 24px;
|
|
247
447
|
}
|
|
248
448
|
|
|
249
449
|
.section-header {
|
|
250
|
-
|
|
450
|
+
display: flex;
|
|
451
|
+
align-items: center;
|
|
452
|
+
justify-content: space-between;
|
|
453
|
+
margin-bottom: 20px;
|
|
251
454
|
}
|
|
252
455
|
|
|
253
456
|
.section-header h2 {
|
|
254
457
|
font-size: 20px;
|
|
255
458
|
font-weight: 600;
|
|
256
|
-
color: #1e293b;
|
|
257
|
-
margin-bottom: 4px;
|
|
258
459
|
}
|
|
259
460
|
|
|
260
461
|
.section-header p {
|
|
261
462
|
font-size: 14px;
|
|
262
|
-
color:
|
|
463
|
+
color: var(--color-text-secondary);
|
|
263
464
|
}
|
|
264
465
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
border:
|
|
269
|
-
|
|
270
|
-
margin-bottom: 12px;
|
|
466
|
+
.finding-card {
|
|
467
|
+
background: var(--color-card);
|
|
468
|
+
border: 1px solid var(--color-border);
|
|
469
|
+
border-radius: var(--radius);
|
|
470
|
+
margin-bottom: 16px;
|
|
271
471
|
overflow: hidden;
|
|
472
|
+
box-shadow: var(--shadow-sm);
|
|
272
473
|
}
|
|
273
474
|
|
|
274
|
-
.
|
|
275
|
-
padding:
|
|
475
|
+
.finding-header {
|
|
476
|
+
padding: 20px;
|
|
276
477
|
cursor: pointer;
|
|
277
478
|
display: flex;
|
|
278
|
-
align-items:
|
|
279
|
-
|
|
280
|
-
gap: 12px;
|
|
479
|
+
align-items: flex-start;
|
|
480
|
+
gap: 16px;
|
|
281
481
|
transition: background 0.2s;
|
|
282
482
|
}
|
|
283
483
|
|
|
284
|
-
.
|
|
285
|
-
background: #f8fafc;
|
|
484
|
+
.finding-header:hover {
|
|
485
|
+
background: ${theme === 'dark' ? '#334155' : '#f8fafc'};
|
|
286
486
|
}
|
|
287
487
|
|
|
288
|
-
.
|
|
488
|
+
.finding-severity {
|
|
489
|
+
width: 48px;
|
|
490
|
+
height: 48px;
|
|
491
|
+
border-radius: var(--radius-sm);
|
|
289
492
|
display: flex;
|
|
290
493
|
align-items: center;
|
|
291
|
-
|
|
494
|
+
justify-content: center;
|
|
495
|
+
font-size: 24px;
|
|
496
|
+
flex-shrink: 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.finding-severity.critical { background: #fef2f2; }
|
|
500
|
+
.finding-severity.high { background: #fffbeb; }
|
|
501
|
+
.finding-severity.medium { background: #fefce8; }
|
|
502
|
+
.finding-severity.low { background: #eff6ff; }
|
|
503
|
+
|
|
504
|
+
.finding-title-section {
|
|
292
505
|
flex: 1;
|
|
293
506
|
}
|
|
294
507
|
|
|
295
|
-
.
|
|
296
|
-
font-size:
|
|
508
|
+
.finding-title {
|
|
509
|
+
font-size: 16px;
|
|
297
510
|
font-weight: 600;
|
|
298
|
-
|
|
511
|
+
margin-bottom: 4px;
|
|
512
|
+
display: flex;
|
|
513
|
+
align-items: center;
|
|
514
|
+
gap: 8px;
|
|
299
515
|
}
|
|
300
516
|
|
|
301
|
-
.
|
|
302
|
-
font-size:
|
|
303
|
-
color:
|
|
304
|
-
|
|
517
|
+
.finding-id {
|
|
518
|
+
font-size: 12px;
|
|
519
|
+
color: var(--color-text-secondary);
|
|
520
|
+
font-family: monospace;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.finding-preview {
|
|
524
|
+
font-size: 14px;
|
|
525
|
+
color: var(--color-text-secondary);
|
|
526
|
+
line-height: 1.5;
|
|
305
527
|
}
|
|
306
528
|
|
|
307
|
-
.
|
|
529
|
+
.finding-meta {
|
|
530
|
+
display: flex;
|
|
531
|
+
gap: 12px;
|
|
532
|
+
margin-top: 8px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.finding-badge {
|
|
308
536
|
display: inline-flex;
|
|
309
537
|
align-items: center;
|
|
310
538
|
gap: 4px;
|
|
@@ -316,304 +544,357 @@ function generateHTMLReport(result, options = {}) {
|
|
|
316
544
|
letter-spacing: 0.5px;
|
|
317
545
|
}
|
|
318
546
|
|
|
319
|
-
.badge
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.badge-high {
|
|
325
|
-
background: #fef3c7;
|
|
326
|
-
color: #d97706;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.badge-medium {
|
|
330
|
-
background: #fef9c3;
|
|
331
|
-
color: #a16207;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
.badge-low {
|
|
335
|
-
background: #dbeafe;
|
|
336
|
-
color: #2563eb;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
.badge-info {
|
|
340
|
-
background: #dcfce7;
|
|
341
|
-
color: #16a34a;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.badge-risk {
|
|
345
|
-
background: #fee2e2;
|
|
346
|
-
color: #dc2626;
|
|
347
|
-
}
|
|
547
|
+
.finding-badge.critical { background: #fef2f2; color: #dc2626; }
|
|
548
|
+
.finding-badge.high { background: #fffbeb; color: #d97706; }
|
|
549
|
+
.finding-badge.medium { background: #fefce8; color: #a16207; }
|
|
550
|
+
.finding-badge.low { background: #eff6ff; color: #2563eb; }
|
|
348
551
|
|
|
349
|
-
.
|
|
350
|
-
background: #
|
|
351
|
-
color: #
|
|
552
|
+
.finding-category {
|
|
553
|
+
background: ${theme === 'dark' ? '#475569' : '#e2e8f0'};
|
|
554
|
+
color: ${theme === 'dark' ? '#e2e8f0' : '#475569'};
|
|
352
555
|
}
|
|
353
556
|
|
|
354
|
-
.
|
|
355
|
-
|
|
356
|
-
|
|
557
|
+
.finding-toggle {
|
|
558
|
+
width: 32px;
|
|
559
|
+
height: 32px;
|
|
560
|
+
border-radius: 50%;
|
|
561
|
+
display: flex;
|
|
562
|
+
align-items: center;
|
|
563
|
+
justify-content: center;
|
|
564
|
+
background: ${theme === 'dark' ? '#475569' : '#e2e8f0'};
|
|
565
|
+
color: ${theme === 'dark' ? '#e2e8f0' : '#64748b'};
|
|
357
566
|
transition: transform 0.2s;
|
|
358
567
|
}
|
|
359
568
|
|
|
360
|
-
.
|
|
569
|
+
.finding-content {
|
|
361
570
|
display: none;
|
|
362
|
-
padding: 0 20px 20px
|
|
363
|
-
border-top: 1px solid
|
|
571
|
+
padding: 0 20px 20px 84px;
|
|
572
|
+
border-top: 1px solid var(--color-border);
|
|
364
573
|
}
|
|
365
574
|
|
|
366
|
-
.
|
|
575
|
+
.finding-content.active {
|
|
367
576
|
display: block;
|
|
368
577
|
}
|
|
369
578
|
|
|
370
|
-
.content-section {
|
|
371
|
-
|
|
579
|
+
.finding-content-section {
|
|
580
|
+
padding: 20px 0;
|
|
581
|
+
border-bottom: 1px solid var(--color-border);
|
|
372
582
|
}
|
|
373
583
|
|
|
374
|
-
.content-section:last-child {
|
|
375
|
-
|
|
584
|
+
.finding-content-section:last-child {
|
|
585
|
+
border-bottom: none;
|
|
376
586
|
}
|
|
377
587
|
|
|
378
|
-
.content-section h4 {
|
|
379
|
-
font-size:
|
|
588
|
+
.finding-content-section h4 {
|
|
589
|
+
font-size: 12px;
|
|
380
590
|
font-weight: 600;
|
|
381
|
-
|
|
382
|
-
|
|
591
|
+
text-transform: uppercase;
|
|
592
|
+
letter-spacing: 0.5px;
|
|
593
|
+
color: var(--color-text-secondary);
|
|
594
|
+
margin-bottom: 12px;
|
|
383
595
|
}
|
|
384
596
|
|
|
385
|
-
.content-section p {
|
|
597
|
+
.finding-content-section p {
|
|
386
598
|
font-size: 14px;
|
|
387
|
-
|
|
388
|
-
|
|
599
|
+
line-height: 1.7;
|
|
600
|
+
color: var(--color-text);
|
|
389
601
|
}
|
|
390
602
|
|
|
391
|
-
.content-section ul {
|
|
603
|
+
.finding-content-section ul {
|
|
392
604
|
list-style: none;
|
|
393
605
|
padding: 0;
|
|
394
606
|
}
|
|
395
607
|
|
|
396
|
-
.content-section li {
|
|
608
|
+
.finding-content-section li {
|
|
397
609
|
font-size: 14px;
|
|
398
|
-
|
|
399
|
-
padding:
|
|
400
|
-
padding-left: 16px;
|
|
610
|
+
padding: 6px 0;
|
|
611
|
+
padding-left: 20px;
|
|
401
612
|
position: relative;
|
|
402
613
|
}
|
|
403
614
|
|
|
404
|
-
.content-section li::before {
|
|
405
|
-
content: "
|
|
615
|
+
.finding-content-section li::before {
|
|
616
|
+
content: "→";
|
|
406
617
|
position: absolute;
|
|
407
618
|
left: 0;
|
|
408
|
-
color:
|
|
619
|
+
color: var(--color-primary);
|
|
409
620
|
}
|
|
410
621
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
622
|
+
/* Code Blocks */
|
|
623
|
+
.code-block {
|
|
624
|
+
background: #1e293b;
|
|
625
|
+
border-radius: var(--radius-sm);
|
|
626
|
+
padding: 16px;
|
|
415
627
|
margin-top: 12px;
|
|
628
|
+
overflow-x: auto;
|
|
416
629
|
}
|
|
417
630
|
|
|
418
|
-
.
|
|
419
|
-
|
|
631
|
+
.code-block pre {
|
|
632
|
+
margin: 0;
|
|
633
|
+
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
|
420
634
|
font-size: 13px;
|
|
421
|
-
|
|
635
|
+
line-height: 1.6;
|
|
636
|
+
color: #e2e8f0;
|
|
422
637
|
}
|
|
423
638
|
|
|
424
|
-
.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
639
|
+
.code-block code {
|
|
640
|
+
font-family: inherit;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/* Technical Details */
|
|
644
|
+
.tech-details {
|
|
645
|
+
background: ${theme === 'dark' ? '#0f172a' : '#f8fafc'};
|
|
646
|
+
border: 1px solid var(--color-border);
|
|
647
|
+
border-radius: var(--radius-sm);
|
|
648
|
+
padding: 16px;
|
|
428
649
|
margin-top: 16px;
|
|
429
|
-
padding: 8px 16px;
|
|
430
|
-
background: white;
|
|
431
|
-
border: 1px solid #e2e8f0;
|
|
432
|
-
border-radius: 6px;
|
|
433
|
-
font-size: 13px;
|
|
434
|
-
color: #64748b;
|
|
435
|
-
cursor: pointer;
|
|
436
|
-
transition: all 0.2s;
|
|
437
650
|
}
|
|
438
651
|
|
|
439
|
-
.tech-details
|
|
440
|
-
|
|
441
|
-
|
|
652
|
+
.tech-details h5 {
|
|
653
|
+
font-size: 11px;
|
|
654
|
+
font-weight: 600;
|
|
655
|
+
text-transform: uppercase;
|
|
656
|
+
letter-spacing: 0.5px;
|
|
657
|
+
color: var(--color-text-secondary);
|
|
658
|
+
margin-bottom: 12px;
|
|
442
659
|
}
|
|
443
660
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
border: 1px solid #e2e8f0;
|
|
448
|
-
border-radius: 12px;
|
|
449
|
-
overflow: hidden;
|
|
661
|
+
.tech-row {
|
|
662
|
+
display: flex;
|
|
663
|
+
margin-bottom: 12px;
|
|
450
664
|
}
|
|
451
665
|
|
|
452
|
-
.
|
|
453
|
-
|
|
454
|
-
border-bottom: 1px solid #f1f5f9;
|
|
666
|
+
.tech-row:last-child {
|
|
667
|
+
margin-bottom: 0;
|
|
455
668
|
}
|
|
456
669
|
|
|
457
|
-
.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
font-weight: 600;
|
|
463
|
-
color: #1e293b;
|
|
464
|
-
margin-bottom: 4px;
|
|
670
|
+
.tech-label {
|
|
671
|
+
width: 120px;
|
|
672
|
+
font-size: 12px;
|
|
673
|
+
color: var(--color-text-secondary);
|
|
674
|
+
flex-shrink: 0;
|
|
465
675
|
}
|
|
466
676
|
|
|
467
|
-
.
|
|
677
|
+
.tech-value {
|
|
678
|
+
flex: 1;
|
|
468
679
|
font-size: 13px;
|
|
469
|
-
|
|
680
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
681
|
+
color: var(--color-text);
|
|
682
|
+
word-break: break-all;
|
|
470
683
|
}
|
|
471
684
|
|
|
472
|
-
|
|
473
|
-
|
|
685
|
+
/* Passed Checks */
|
|
686
|
+
.passed-section {
|
|
687
|
+
background: var(--color-card);
|
|
688
|
+
border: 1px solid var(--color-border);
|
|
689
|
+
border-radius: var(--radius);
|
|
690
|
+
padding: 24px;
|
|
691
|
+
margin-bottom: 24px;
|
|
692
|
+
box-shadow: var(--shadow-sm);
|
|
474
693
|
}
|
|
475
694
|
|
|
476
|
-
.
|
|
477
|
-
|
|
478
|
-
grid-template-columns: 2fr 1fr 1fr 1fr 1fr;
|
|
479
|
-
gap: 12px;
|
|
480
|
-
padding: 12px 20px;
|
|
481
|
-
background: #f8fafc;
|
|
482
|
-
border-bottom: 1px solid #e2e8f0;
|
|
483
|
-
font-size: 11px;
|
|
695
|
+
.passed-section h2 {
|
|
696
|
+
font-size: 16px;
|
|
484
697
|
font-weight: 600;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
698
|
+
margin-bottom: 16px;
|
|
699
|
+
display: flex;
|
|
700
|
+
align-items: center;
|
|
701
|
+
gap: 8px;
|
|
702
|
+
color: var(--color-success);
|
|
488
703
|
}
|
|
489
704
|
|
|
490
|
-
.
|
|
705
|
+
.passed-grid {
|
|
491
706
|
display: grid;
|
|
492
|
-
grid-template-columns:
|
|
707
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
493
708
|
gap: 12px;
|
|
494
|
-
padding: 14px 20px;
|
|
495
|
-
border-bottom: 1px solid #f1f5f9;
|
|
496
|
-
align-items: center;
|
|
497
|
-
font-size: 13px;
|
|
498
709
|
}
|
|
499
710
|
|
|
500
|
-
.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
background: #
|
|
711
|
+
.passed-item {
|
|
712
|
+
display: flex;
|
|
713
|
+
align-items: flex-start;
|
|
714
|
+
gap: 12px;
|
|
715
|
+
padding: 12px;
|
|
716
|
+
background: #f0fdf4;
|
|
717
|
+
border: 1px solid #bbf7d0;
|
|
718
|
+
border-radius: var(--radius-sm);
|
|
506
719
|
}
|
|
507
720
|
|
|
508
|
-
.
|
|
721
|
+
.passed-icon {
|
|
722
|
+
width: 20px;
|
|
723
|
+
height: 20px;
|
|
724
|
+
border-radius: 50%;
|
|
725
|
+
background: #22c55e;
|
|
509
726
|
display: flex;
|
|
510
727
|
align-items: center;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
.expand-icon {
|
|
517
|
-
color: #94a3b8;
|
|
518
|
-
font-size: 10px;
|
|
728
|
+
justify-content: center;
|
|
729
|
+
color: white;
|
|
730
|
+
font-size: 12px;
|
|
731
|
+
flex-shrink: 0;
|
|
519
732
|
}
|
|
520
733
|
|
|
521
|
-
.
|
|
522
|
-
|
|
523
|
-
padding: 4px 10px;
|
|
524
|
-
border-radius: 20px;
|
|
525
|
-
font-size: 11px;
|
|
734
|
+
.passed-content h4 {
|
|
735
|
+
font-size: 13px;
|
|
526
736
|
font-weight: 600;
|
|
737
|
+
margin-bottom: 2px;
|
|
527
738
|
}
|
|
528
739
|
|
|
529
|
-
.
|
|
530
|
-
|
|
531
|
-
color:
|
|
740
|
+
.passed-content p {
|
|
741
|
+
font-size: 12px;
|
|
742
|
+
color: var(--color-text-secondary);
|
|
532
743
|
}
|
|
533
744
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
745
|
+
/* Quick Actions */
|
|
746
|
+
.actions-section {
|
|
747
|
+
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
|
748
|
+
border-radius: var(--radius);
|
|
749
|
+
padding: 24px;
|
|
750
|
+
margin-bottom: 24px;
|
|
751
|
+
color: white;
|
|
537
752
|
}
|
|
538
753
|
|
|
539
|
-
.
|
|
540
|
-
|
|
541
|
-
|
|
754
|
+
.actions-section h2 {
|
|
755
|
+
font-size: 16px;
|
|
756
|
+
font-weight: 600;
|
|
757
|
+
margin-bottom: 16px;
|
|
542
758
|
}
|
|
543
759
|
|
|
544
|
-
.
|
|
545
|
-
|
|
760
|
+
.actions-grid {
|
|
761
|
+
display: grid;
|
|
762
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
763
|
+
gap: 12px;
|
|
546
764
|
}
|
|
547
765
|
|
|
548
|
-
.
|
|
549
|
-
|
|
766
|
+
.action-item {
|
|
767
|
+
background: rgba(255,255,255,0.1);
|
|
768
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
769
|
+
border-radius: var(--radius-sm);
|
|
770
|
+
padding: 16px;
|
|
550
771
|
}
|
|
551
772
|
|
|
552
|
-
.
|
|
553
|
-
|
|
554
|
-
padding: 4px 10px;
|
|
555
|
-
background: #fef3c7;
|
|
556
|
-
color: #92400e;
|
|
557
|
-
border-radius: 20px;
|
|
558
|
-
font-size: 11px;
|
|
773
|
+
.action-item h4 {
|
|
774
|
+
font-size: 13px;
|
|
559
775
|
font-weight: 600;
|
|
776
|
+
margin-bottom: 8px;
|
|
777
|
+
opacity: 0.9;
|
|
560
778
|
}
|
|
561
779
|
|
|
562
|
-
.
|
|
563
|
-
|
|
780
|
+
.action-item code {
|
|
781
|
+
display: block;
|
|
782
|
+
background: rgba(0,0,0,0.3);
|
|
783
|
+
padding: 8px 12px;
|
|
784
|
+
border-radius: 4px;
|
|
785
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
786
|
+
font-size: 12px;
|
|
787
|
+
color: #60a5fa;
|
|
564
788
|
}
|
|
565
789
|
|
|
566
790
|
/* Footer */
|
|
567
791
|
.footer {
|
|
568
792
|
text-align: center;
|
|
569
|
-
padding: 40px
|
|
793
|
+
padding: 40px 24px;
|
|
794
|
+
border-top: 1px solid var(--color-border);
|
|
795
|
+
margin-top: 40px;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.footer p {
|
|
570
799
|
font-size: 12px;
|
|
571
|
-
color:
|
|
572
|
-
|
|
800
|
+
color: var(--color-text-secondary);
|
|
801
|
+
margin-bottom: 8px;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* Responsive */
|
|
805
|
+
@media (max-width: 768px) {
|
|
806
|
+
.container {
|
|
807
|
+
padding: 16px;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.header {
|
|
811
|
+
padding: 20px;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.grade-section {
|
|
815
|
+
flex-direction: column;
|
|
816
|
+
text-align: center;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.stats-grid {
|
|
820
|
+
grid-template-columns: repeat(2, 1fr);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.finding-content {
|
|
824
|
+
padding-left: 20px;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.target-meta {
|
|
828
|
+
flex-direction: column;
|
|
829
|
+
gap: 12px;
|
|
830
|
+
}
|
|
573
831
|
}
|
|
574
832
|
|
|
575
833
|
@media print {
|
|
576
834
|
body { background: white; }
|
|
577
|
-
.
|
|
835
|
+
.finding-content { display: block !important; }
|
|
836
|
+
.header { background: #3b82f6 !important; -webkit-print-color-adjust: exact; }
|
|
578
837
|
}
|
|
579
838
|
</style>
|
|
580
839
|
</head>
|
|
581
840
|
<body>
|
|
582
841
|
<div class="container">
|
|
583
|
-
<!-- Header -->
|
|
842
|
+
<!-- Header with Grade -->
|
|
584
843
|
<div class="header">
|
|
585
|
-
<div class="
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
<div class="success-title">Scan completed successfully in ${result.scan_metadata.scan_duration_seconds.toFixed(0)} seconds</div>
|
|
597
|
-
<p style="font-size: 14px; color: #3b82f6; margin-top: 4px;">We found <a href="#findings">${result.summary.total_issues} issues to review</a></p>
|
|
844
|
+
<div class="header-top">
|
|
845
|
+
<div class="brand">
|
|
846
|
+
<div class="logo">S</div>
|
|
847
|
+
<div class="brand-text">
|
|
848
|
+
<h1>SupaSec Security Audit</h1>
|
|
849
|
+
<p>Enterprise-grade security assessment</p>
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
<div class="report-meta">
|
|
853
|
+
<div class="report-id">ID: ${escapeHtml(result.scan_metadata.scan_id)}</div>
|
|
854
|
+
<div class="report-date">${scanDate}</div>
|
|
598
855
|
</div>
|
|
599
856
|
</div>
|
|
600
857
|
|
|
601
|
-
<div class="
|
|
602
|
-
<div class="
|
|
603
|
-
<
|
|
604
|
-
<
|
|
858
|
+
<div class="grade-section">
|
|
859
|
+
<div class="grade-circle">
|
|
860
|
+
<div class="grade-letter">${grade}</div>
|
|
861
|
+
<div class="grade-score">${score}/100</div>
|
|
605
862
|
</div>
|
|
606
|
-
<div class="info
|
|
607
|
-
<
|
|
608
|
-
<p>${
|
|
863
|
+
<div class="grade-info">
|
|
864
|
+
<h2>Security Grade ${grade}</h2>
|
|
865
|
+
<p>${getGradeMessage(grade)}</p>
|
|
866
|
+
<div class="security-meter">
|
|
867
|
+
<div class="security-meter-fill"></div>
|
|
868
|
+
</div>
|
|
869
|
+
<div class="security-meter-labels">
|
|
870
|
+
<span>0</span>
|
|
871
|
+
<span>Security Score: ${score}</span>
|
|
872
|
+
<span>100</span>
|
|
873
|
+
</div>
|
|
609
874
|
</div>
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
875
|
+
</div>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
<!-- Target Info -->
|
|
879
|
+
<div class="target-card">
|
|
880
|
+
<h3>Target</h3>
|
|
881
|
+
<div class="target-url">${escapeHtml(result.scan_metadata.target_url)}</div>
|
|
882
|
+
<div class="target-meta">
|
|
883
|
+
<div class="target-meta-item">
|
|
884
|
+
<span>Scan Mode</span>
|
|
885
|
+
<span>${result.scan_metadata.scanner_mode === 'url' ? 'URL Analysis' : 'Project Scan'}</span>
|
|
613
886
|
</div>
|
|
614
|
-
<div class="
|
|
615
|
-
<
|
|
616
|
-
<
|
|
887
|
+
<div class="target-meta-item">
|
|
888
|
+
<span>Duration</span>
|
|
889
|
+
<span>${result.scan_metadata.scan_duration_seconds.toFixed(2)}s</span>
|
|
890
|
+
</div>
|
|
891
|
+
<div class="target-meta-item">
|
|
892
|
+
<span>Findings</span>
|
|
893
|
+
<span>${result.summary.total_issues} issues detected</span>
|
|
894
|
+
</div>
|
|
895
|
+
<div class="target-meta-item">
|
|
896
|
+
<span>Passed Checks</span>
|
|
897
|
+
<span>${result.passed_checks.length} checks passed</span>
|
|
617
898
|
</div>
|
|
618
899
|
</div>
|
|
619
900
|
</div>
|
|
@@ -621,43 +902,88 @@ function generateHTMLReport(result, options = {}) {
|
|
|
621
902
|
<!-- Stats Grid -->
|
|
622
903
|
<div class="stats-grid">
|
|
623
904
|
<div class="stat-card">
|
|
905
|
+
<div class="stat-icon critical">🚨</div>
|
|
906
|
+
<div class="stat-value critical">${counts.CRITICAL}</div>
|
|
624
907
|
<div class="stat-label">Critical</div>
|
|
625
|
-
<div class="stat-value" style="color: #dc2626;">${counts.CRITICAL}</div>
|
|
626
908
|
</div>
|
|
627
909
|
<div class="stat-card">
|
|
910
|
+
<div class="stat-icon high">⚠️</div>
|
|
911
|
+
<div class="stat-value high">${counts.HIGH}</div>
|
|
628
912
|
<div class="stat-label">High</div>
|
|
629
|
-
<div class="stat-value" style="color: #d97706;">${counts.HIGH}</div>
|
|
630
913
|
</div>
|
|
631
914
|
<div class="stat-card">
|
|
915
|
+
<div class="stat-icon medium">⚡</div>
|
|
916
|
+
<div class="stat-value medium">${counts.MEDIUM}</div>
|
|
632
917
|
<div class="stat-label">Medium</div>
|
|
633
|
-
<div class="stat-value" style="color: #a16207;">${counts.MEDIUM}</div>
|
|
634
918
|
</div>
|
|
635
919
|
<div class="stat-card">
|
|
920
|
+
<div class="stat-icon low">ℹ️</div>
|
|
921
|
+
<div class="stat-value low">${counts.LOW}</div>
|
|
636
922
|
<div class="stat-label">Low</div>
|
|
637
|
-
<div class="stat-value" style="color: #2563eb;">${counts.LOW}</div>
|
|
638
923
|
</div>
|
|
639
924
|
</div>
|
|
640
925
|
|
|
641
|
-
|
|
926
|
+
<!-- Category Scores -->
|
|
927
|
+
<div class="category-section">
|
|
928
|
+
<h2>📊 Category Breakdown</h2>
|
|
929
|
+
<div class="category-grid">
|
|
930
|
+
${generateCategoryScores(result.grading.category_scores)}
|
|
931
|
+
</div>
|
|
932
|
+
</div>
|
|
933
|
+
|
|
934
|
+
<!-- Findings -->
|
|
935
|
+
${includeDetails ? generateFindingsSection(findingsBySeverity) : ''}
|
|
642
936
|
|
|
643
|
-
|
|
937
|
+
<!-- Passed Checks -->
|
|
938
|
+
${result.passed_checks.length > 0 ? generatePassedChecksSection(result.passed_checks) : ''}
|
|
939
|
+
|
|
940
|
+
<!-- Quick Actions -->
|
|
941
|
+
<div class="actions-section">
|
|
942
|
+
<h2>🛠️ Quick Actions</h2>
|
|
943
|
+
<div class="actions-grid">
|
|
944
|
+
<div class="action-item">
|
|
945
|
+
<h4>Fix Critical Issues</h4>
|
|
946
|
+
<code>supasec fix --interactive</code>
|
|
947
|
+
</div>
|
|
948
|
+
<div class="action-item">
|
|
949
|
+
<h4>Export as JSON</h4>
|
|
950
|
+
<code>supasec scan --format json</code>
|
|
951
|
+
</div>
|
|
952
|
+
<div class="action-item">
|
|
953
|
+
<h4>Save Snapshot</h4>
|
|
954
|
+
<code>supasec snapshot save</code>
|
|
955
|
+
</div>
|
|
956
|
+
<div class="action-item">
|
|
957
|
+
<h4>Deep Scan</h4>
|
|
958
|
+
<code>supasec scan --deep</code>
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
644
962
|
|
|
645
963
|
<!-- Footer -->
|
|
646
964
|
<div class="footer">
|
|
647
|
-
<p>
|
|
648
|
-
<p>
|
|
649
|
-
<p style="margin-top: 16px; color: #64748b;">Generated by Supasec • Report ID: ${escapeHtml(result.scan_metadata.scan_id)}</p>
|
|
965
|
+
<p>Generated by SupaSec v1.0.4 • Report ID: ${escapeHtml(result.scan_metadata.scan_id)}</p>
|
|
966
|
+
<p>SupaSec is an independent security auditing tool and is not affiliated with Supabase Inc.</p>
|
|
650
967
|
</div>
|
|
651
968
|
</div>
|
|
652
969
|
|
|
653
970
|
<script>
|
|
654
|
-
//
|
|
655
|
-
document.querySelectorAll('.
|
|
971
|
+
// Toggle finding details
|
|
972
|
+
document.querySelectorAll('.finding-header').forEach(header => {
|
|
656
973
|
header.addEventListener('click', () => {
|
|
657
974
|
const content = header.nextElementSibling;
|
|
658
|
-
const
|
|
975
|
+
const toggle = header.querySelector('.finding-toggle');
|
|
659
976
|
content.classList.toggle('active');
|
|
660
|
-
|
|
977
|
+
toggle.style.transform = content.classList.contains('active') ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// Animate progress bars on load
|
|
982
|
+
window.addEventListener('load', () => {
|
|
983
|
+
document.querySelectorAll('.category-bar-fill').forEach(bar => {
|
|
984
|
+
const width = bar.style.width;
|
|
985
|
+
bar.style.width = '0';
|
|
986
|
+
setTimeout(() => bar.style.width = width, 100);
|
|
661
987
|
});
|
|
662
988
|
});
|
|
663
989
|
</script>
|
|
@@ -665,262 +991,242 @@ function generateHTMLReport(result, options = {}) {
|
|
|
665
991
|
</html>`;
|
|
666
992
|
}
|
|
667
993
|
/**
|
|
668
|
-
* Generate
|
|
994
|
+
* Generate category scores HTML
|
|
995
|
+
*/
|
|
996
|
+
function generateCategoryScores(categoryScores) {
|
|
997
|
+
const categoryIcons = {
|
|
998
|
+
secrets: '🔑',
|
|
999
|
+
rls: '🛡️',
|
|
1000
|
+
auth: '🔐',
|
|
1001
|
+
storage: '📦',
|
|
1002
|
+
api: '🔌',
|
|
1003
|
+
functions: '⚙️',
|
|
1004
|
+
database: '🗄️',
|
|
1005
|
+
transport: '🌐',
|
|
1006
|
+
pii: '👤'
|
|
1007
|
+
};
|
|
1008
|
+
return Object.entries(categoryScores)
|
|
1009
|
+
.map(([category, data]) => {
|
|
1010
|
+
const icon = categoryIcons[category] || '📋';
|
|
1011
|
+
const gradeColor = getGradeColor(data.grade);
|
|
1012
|
+
return `
|
|
1013
|
+
<div class="category-item">
|
|
1014
|
+
<div class="category-grade" style="background: ${gradeColor}; color: white;">${data.grade}</div>
|
|
1015
|
+
<div class="category-info">
|
|
1016
|
+
<div class="category-name">${icon} ${category}</div>
|
|
1017
|
+
<div style="display: flex; align-items: center;">
|
|
1018
|
+
<div class="category-bar" style="flex: 1;">
|
|
1019
|
+
<div class="category-bar-fill" style="width: ${data.score}%; background: ${gradeColor};"></div>
|
|
1020
|
+
</div>
|
|
1021
|
+
<span class="category-score">${data.score}/100</span>
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
</div>
|
|
1025
|
+
`;
|
|
1026
|
+
})
|
|
1027
|
+
.join('');
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Generate findings section HTML
|
|
669
1031
|
*/
|
|
670
|
-
function
|
|
1032
|
+
function generateFindingsSection(findingsBySeverity) {
|
|
671
1033
|
const severities = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
|
|
672
1034
|
const allFindings = [];
|
|
673
1035
|
for (const severity of severities) {
|
|
674
1036
|
allFindings.push(...findingsBySeverity[severity]);
|
|
675
1037
|
}
|
|
676
1038
|
if (allFindings.length === 0) {
|
|
677
|
-
return
|
|
1039
|
+
return `
|
|
1040
|
+
<div class="findings-section">
|
|
1041
|
+
<div class="section-header">
|
|
1042
|
+
<div>
|
|
1043
|
+
<h2>🔍 Security Findings</h2>
|
|
1044
|
+
<p>No security issues were detected in this scan.</p>
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
<div class="passed-section" style="text-align: center; padding: 60px;">
|
|
1048
|
+
<div style="font-size: 64px; margin-bottom: 16px;">🎉</div>
|
|
1049
|
+
<h3 style="font-size: 20px; margin-bottom: 8px;">Excellent Security Posture!</h3>
|
|
1050
|
+
<p style="color: var(--color-text-secondary);">No vulnerabilities or security issues were found.</p>
|
|
1051
|
+
</div>
|
|
1052
|
+
</div>`;
|
|
678
1053
|
}
|
|
679
1054
|
let html = `
|
|
680
|
-
|
|
681
|
-
<div class="section" id="findings">
|
|
1055
|
+
<div class="findings-section">
|
|
682
1056
|
<div class="section-header">
|
|
683
|
-
<
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1057
|
+
<div>
|
|
1058
|
+
<h2>🔍 Security Findings</h2>
|
|
1059
|
+
<p>${allFindings.length} security issues identified across ${Object.keys(findingsBySeverity).filter(k => findingsBySeverity[k].length > 0).length} severity levels</p>
|
|
1060
|
+
</div>
|
|
1061
|
+
</div>`;
|
|
687
1062
|
for (const finding of allFindings) {
|
|
688
|
-
html +=
|
|
1063
|
+
html += generateFindingCard(finding);
|
|
689
1064
|
}
|
|
690
1065
|
html += '</div>';
|
|
691
1066
|
return html;
|
|
692
1067
|
}
|
|
693
1068
|
/**
|
|
694
|
-
* Generate a single finding
|
|
1069
|
+
* Generate a single finding card
|
|
695
1070
|
*/
|
|
696
|
-
function
|
|
697
|
-
const
|
|
698
|
-
|
|
1071
|
+
function generateFindingCard(finding) {
|
|
1072
|
+
const severityIcons = {
|
|
1073
|
+
CRITICAL: '🚨',
|
|
1074
|
+
HIGH: '⚠️',
|
|
1075
|
+
MEDIUM: '⚡',
|
|
1076
|
+
LOW: 'ℹ️',
|
|
1077
|
+
INFO: '📌'
|
|
1078
|
+
};
|
|
1079
|
+
const locationText = finding.location?.table
|
|
1080
|
+
? `Table: ${finding.location.table}`
|
|
1081
|
+
: finding.location?.file
|
|
1082
|
+
? `${finding.location.file}${finding.location.line ? ':' + finding.location.line : ''}`
|
|
1083
|
+
: finding.location?.url
|
|
1084
|
+
? finding.location.url
|
|
1085
|
+
: '';
|
|
699
1086
|
let contentHtml = '';
|
|
700
|
-
//
|
|
1087
|
+
// Description
|
|
701
1088
|
contentHtml += `
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1089
|
+
<div class="finding-content-section">
|
|
1090
|
+
<h4>Description</h4>
|
|
1091
|
+
<p>${escapeHtml(finding.description)}</p>
|
|
1092
|
+
</div>`;
|
|
706
1093
|
// Impact
|
|
707
1094
|
if (finding.impact) {
|
|
708
1095
|
contentHtml += `
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1096
|
+
<div class="finding-content-section">
|
|
1097
|
+
<h4>Impact</h4>
|
|
1098
|
+
<p>${escapeHtml(finding.impact.description)}</p>
|
|
1099
|
+
${finding.impact.compliance_violations ? `
|
|
1100
|
+
<div style="margin-top: 12px;">
|
|
1101
|
+
<span style="font-size: 12px; color: var(--color-text-secondary);">Compliance Violations: </span>
|
|
1102
|
+
${finding.impact.compliance_violations.map(v => `<span style="font-size: 11px; background: #fef3c7; color: #92400e; padding: 2px 8px; border-radius: 4px; margin-right: 4px;">${escapeHtml(v)}</span>`).join('')}
|
|
1103
|
+
</div>` : ''}
|
|
1104
|
+
</div>`;
|
|
713
1105
|
}
|
|
714
1106
|
// Remediation
|
|
715
1107
|
if (finding.remediation) {
|
|
716
1108
|
contentHtml += `
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1109
|
+
<div class="finding-content-section">
|
|
1110
|
+
<h4>Remediation</h4>
|
|
1111
|
+
<p>${escapeHtml(finding.remediation.summary)}</p>
|
|
1112
|
+
${finding.remediation.steps ? `
|
|
1113
|
+
<ul style="margin-top: 12px;">
|
|
1114
|
+
${finding.remediation.steps.map(step => `<li>${escapeHtml(step.action)}</li>`).join('')}
|
|
1115
|
+
</ul>` : ''}
|
|
1116
|
+
${finding.remediation.sql ? `
|
|
1117
|
+
<div class="code-block">
|
|
1118
|
+
<pre><code>${escapeHtml(finding.remediation.sql)}</code></pre>
|
|
1119
|
+
</div>` : ''}
|
|
1120
|
+
</div>`;
|
|
727
1121
|
}
|
|
728
|
-
//
|
|
729
|
-
if (finding.
|
|
1122
|
+
// Technical Details
|
|
1123
|
+
if (finding.evidence || finding.location) {
|
|
730
1124
|
contentHtml += `
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (finding.
|
|
735
|
-
contentHtml +=
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1125
|
+
<div class="finding-content-section">
|
|
1126
|
+
<h4>Technical Details</h4>
|
|
1127
|
+
<div class="tech-details">`;
|
|
1128
|
+
if (finding.evidence?.sample_data?.masked) {
|
|
1129
|
+
contentHtml += `
|
|
1130
|
+
<div class="tech-row">
|
|
1131
|
+
<div class="tech-label">Exposed Key</div>
|
|
1132
|
+
<div class="tech-value">${escapeHtml(finding.evidence.sample_data.masked)}</div>
|
|
1133
|
+
</div>`;
|
|
1134
|
+
}
|
|
1135
|
+
if (finding.evidence?.matched_pattern) {
|
|
1136
|
+
contentHtml += `
|
|
1137
|
+
<div class="tech-row">
|
|
1138
|
+
<div class="tech-label">Key Type</div>
|
|
1139
|
+
<div class="tech-value">${escapeHtml(finding.evidence.matched_pattern)}</div>
|
|
1140
|
+
</div>`;
|
|
1141
|
+
}
|
|
1142
|
+
if (locationText) {
|
|
1143
|
+
contentHtml += `
|
|
1144
|
+
<div class="tech-row">
|
|
1145
|
+
<div class="tech-label">Location</div>
|
|
1146
|
+
<div class="tech-value">${escapeHtml(locationText)}</div>
|
|
1147
|
+
</div>`;
|
|
1148
|
+
}
|
|
1149
|
+
if (finding.evidence?.code_snippet) {
|
|
1150
|
+
contentHtml += `
|
|
1151
|
+
<div class="tech-row" style="flex-direction: column;">
|
|
1152
|
+
<div class="tech-label" style="margin-bottom: 8px;">Code Snippet</div>
|
|
1153
|
+
<div class="code-block" style="margin: 0;">
|
|
1154
|
+
<pre><code>${escapeHtml(finding.evidence.code_snippet)}</code></pre>
|
|
1155
|
+
</div>
|
|
1156
|
+
</div>`;
|
|
739
1157
|
}
|
|
740
|
-
contentHtml +=
|
|
1158
|
+
contentHtml += `
|
|
1159
|
+
</div>
|
|
1160
|
+
</div>`;
|
|
741
1161
|
}
|
|
742
|
-
// Technical details section (initially hidden, toggled by button)
|
|
743
|
-
contentHtml += generateTechnicalDetails(finding);
|
|
744
1162
|
return `
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
<div class="
|
|
749
|
-
<
|
|
750
|
-
|
|
1163
|
+
<div class="finding-card">
|
|
1164
|
+
<div class="finding-header">
|
|
1165
|
+
<div class="finding-severity ${finding.severity.toLowerCase()}">${severityIcons[finding.severity]}</div>
|
|
1166
|
+
<div class="finding-title-section">
|
|
1167
|
+
<div class="finding-title">
|
|
1168
|
+
${escapeHtml(finding.title)}
|
|
1169
|
+
<span class="finding-id">${finding.finding_id}</span>
|
|
1170
|
+
</div>
|
|
1171
|
+
<div class="finding-preview">${escapeHtml(finding.description.substring(0, 120))}${finding.description.length > 120 ? '...' : ''}</div>
|
|
1172
|
+
<div class="finding-meta">
|
|
1173
|
+
<span class="finding-badge ${finding.severity.toLowerCase()}">${finding.severity}</span>
|
|
1174
|
+
<span class="finding-badge finding-category">${finding.category}</span>
|
|
1175
|
+
${locationText ? `<span class="finding-badge" style="background: #f3f4f6; color: #4b5563;">📍 ${escapeHtml(locationText.substring(0, 30))}${locationText.length > 30 ? '...' : ''}</span>` : ''}
|
|
1176
|
+
</div>
|
|
751
1177
|
</div>
|
|
752
|
-
<
|
|
1178
|
+
<div class="finding-toggle">▼</div>
|
|
753
1179
|
</div>
|
|
754
|
-
<
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
</div>
|
|
759
|
-
</div>`;
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Generate technical details section for a finding
|
|
763
|
-
* Shows exposed key (masked), file path, line number, and code snippet
|
|
764
|
-
*/
|
|
765
|
-
function generateTechnicalDetails(finding) {
|
|
766
|
-
const hasEvidence = finding.evidence && (finding.evidence.code_snippet ||
|
|
767
|
-
finding.evidence.sample_data ||
|
|
768
|
-
finding.evidence.matched_pattern);
|
|
769
|
-
const hasLocation = finding.location && (finding.location.file ||
|
|
770
|
-
finding.location.line ||
|
|
771
|
-
finding.location.column ||
|
|
772
|
-
finding.location.url);
|
|
773
|
-
if (!hasEvidence && !hasLocation) {
|
|
774
|
-
return '';
|
|
775
|
-
}
|
|
776
|
-
let detailsHtml = `
|
|
777
|
-
<div class="tech-details-section" style="margin-top: 16px; padding: 16px; background: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0;">
|
|
778
|
-
<h4 style="font-size: 13px; font-weight: 600; color: #1e293b; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.5px;">Technical Details</h4>`;
|
|
779
|
-
// Exposed Key (masked)
|
|
780
|
-
if (finding.evidence?.sample_data?.masked) {
|
|
781
|
-
detailsHtml += `
|
|
782
|
-
<div style="margin-bottom: 12px;">
|
|
783
|
-
<span style="font-size: 12px; color: #64748b; display: block; margin-bottom: 4px;">Exposed Key (masked):</span>
|
|
784
|
-
<code style="font-family: 'Monaco', 'Consolas', monospace; font-size: 13px; background: #1e293b; color: #e2e8f0; padding: 8px 12px; border-radius: 6px; display: block; word-break: break-all;">${escapeHtml(finding.evidence.sample_data.masked)}</code>
|
|
785
|
-
</div>`;
|
|
786
|
-
}
|
|
787
|
-
// Key Type / Pattern
|
|
788
|
-
if (finding.evidence?.matched_pattern || finding.subcategory) {
|
|
789
|
-
const keyType = finding.evidence?.matched_pattern || finding.subcategory;
|
|
790
|
-
detailsHtml += `
|
|
791
|
-
<div style="margin-bottom: 12px;">
|
|
792
|
-
<span style="font-size: 12px; color: #64748b; display: block; margin-bottom: 4px;">Key Type:</span>
|
|
793
|
-
<span style="font-size: 13px; color: #334155; font-weight: 500;">${escapeHtml(keyType || 'Unknown')}</span>
|
|
794
|
-
</div>`;
|
|
795
|
-
}
|
|
796
|
-
// Location (File path and line number)
|
|
797
|
-
if (hasLocation) {
|
|
798
|
-
detailsHtml += `
|
|
799
|
-
<div style="margin-bottom: 12px;">
|
|
800
|
-
<span style="font-size: 12px; color: #64748b; display: block; margin-bottom: 4px;">Location:</span>`;
|
|
801
|
-
if (finding.location?.file) {
|
|
802
|
-
detailsHtml += `<div style="font-family: 'Monaco', 'Consolas', monospace; font-size: 13px; color: #334155;">${escapeHtml(finding.location.file)}`;
|
|
803
|
-
if (finding.location?.line) {
|
|
804
|
-
detailsHtml += `<span style="color: #64748b;">:${finding.location.line}</span>`;
|
|
805
|
-
if (finding.location?.column) {
|
|
806
|
-
detailsHtml += `<span style="color: #94a3b8;">:${finding.location.column}</span>`;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
detailsHtml += `</div>`;
|
|
810
|
-
}
|
|
811
|
-
if (finding.location?.url) {
|
|
812
|
-
detailsHtml += `<div style="font-family: 'Monaco', 'Consolas', monospace; font-size: 13px; color: #334155;">${escapeHtml(finding.location.url)}</div>`;
|
|
813
|
-
}
|
|
814
|
-
if (finding.location?.table) {
|
|
815
|
-
detailsHtml += `<div style="font-size: 13px; color: #334155;">Table: <span style="font-family: monospace;">${escapeHtml(finding.location.table)}</span></div>`;
|
|
816
|
-
}
|
|
817
|
-
detailsHtml += `</div>`;
|
|
818
|
-
}
|
|
819
|
-
// Code Snippet
|
|
820
|
-
if (finding.evidence?.code_snippet) {
|
|
821
|
-
detailsHtml += `
|
|
822
|
-
<div>
|
|
823
|
-
<span style="font-size: 12px; color: #64748b; display: block; margin-bottom: 4px;">Code Snippet:</span>
|
|
824
|
-
<pre style="font-family: 'Monaco', 'Consolas', monospace; font-size: 12px; background: #1e293b; color: #e2e8f0; padding: 12px; border-radius: 6px; overflow-x: auto; margin: 0; line-height: 1.5;"><code>${escapeHtml(finding.evidence.code_snippet)}</code></pre>
|
|
825
|
-
</div>`;
|
|
826
|
-
}
|
|
827
|
-
detailsHtml += `</div>`;
|
|
828
|
-
return detailsHtml;
|
|
1180
|
+
<div class="finding-content">
|
|
1181
|
+
${contentHtml}
|
|
1182
|
+
</div>
|
|
1183
|
+
</div>`;
|
|
829
1184
|
}
|
|
830
1185
|
/**
|
|
831
|
-
*
|
|
1186
|
+
* Generate passed checks section
|
|
832
1187
|
*/
|
|
833
|
-
function
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1188
|
+
function generatePassedChecksSection(passedChecks) {
|
|
1189
|
+
return `
|
|
1190
|
+
<div class="passed-section">
|
|
1191
|
+
<h2>✅ Passed Security Checks (${passedChecks.length})</h2>
|
|
1192
|
+
<div class="passed-grid">
|
|
1193
|
+
${passedChecks.map(check => `
|
|
1194
|
+
<div class="passed-item">
|
|
1195
|
+
<div class="passed-icon">✓</div>
|
|
1196
|
+
<div class="passed-content">
|
|
1197
|
+
<h4>${escapeHtml(check.title)}</h4>
|
|
1198
|
+
<p>${escapeHtml(check.description)}</p>
|
|
1199
|
+
</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
`).join('')}
|
|
1202
|
+
</div>
|
|
1203
|
+
</div>`;
|
|
841
1204
|
}
|
|
842
1205
|
/**
|
|
843
|
-
* Get
|
|
1206
|
+
* Get color for grade
|
|
844
1207
|
*/
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
}
|
|
1208
|
+
function getGradeColor(grade) {
|
|
1209
|
+
const colors = {
|
|
1210
|
+
'A': '#22c55e',
|
|
1211
|
+
'B': '#84cc16',
|
|
1212
|
+
'C': '#eab308',
|
|
1213
|
+
'D': '#f97316',
|
|
1214
|
+
'F': '#ef4444'
|
|
1215
|
+
};
|
|
1216
|
+
return colors[grade] || '#6b7280';
|
|
853
1217
|
}
|
|
854
1218
|
/**
|
|
855
|
-
*
|
|
856
|
-
* Shows real endpoints from scan results or a message if none detected
|
|
1219
|
+
* Get message for grade
|
|
857
1220
|
*/
|
|
858
|
-
function
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
<h2>
|
|
868
|
-
<span style="font-size: 20px;">🗄</span>
|
|
869
|
-
Endpoints
|
|
870
|
-
</h2>
|
|
871
|
-
<p>No API endpoints were detected during this scan.</p>
|
|
872
|
-
</div>
|
|
873
|
-
<div style="padding: 40px 20px; text-align: center; color: #64748b;">
|
|
874
|
-
<p style="font-size: 14px;">Endpoints are detected when scanning Supabase projects with accessible REST API.</p>
|
|
875
|
-
<p style="font-size: 13px; margin-top: 8px;">Try scanning with --project-url and --anon-key options for deeper analysis.</p>
|
|
876
|
-
</div>
|
|
877
|
-
</div>`;
|
|
878
|
-
}
|
|
879
|
-
// Generate rows from real endpoint data
|
|
880
|
-
let rowsHtml = '';
|
|
881
|
-
for (const endpoint of endpoints) {
|
|
882
|
-
const statusClass = endpoint.status === 'At Risk' ? 'status-at-risk' :
|
|
883
|
-
endpoint.status === 'Review' ? 'status-review' : 'status-secure';
|
|
884
|
-
const readableIcon = endpoint.readable === 'warning' ? '⚠' : '✓';
|
|
885
|
-
const writableIcon = endpoint.writable === 'warning' ? '⚠' : '✓';
|
|
886
|
-
const readableClass = endpoint.readable === 'warning' ? 'icon-warning' : 'icon-check';
|
|
887
|
-
const writableClass = endpoint.writable === 'warning' ? 'icon-warning' : 'icon-check';
|
|
888
|
-
const sensitiveDisplay = endpoint.sensitive && endpoint.sensitive !== 'None'
|
|
889
|
-
? `<span class="sensitive-badge">${escapeHtml(endpoint.sensitive)}</span>`
|
|
890
|
-
: '<span class="sensitive-none">None</span>';
|
|
891
|
-
rowsHtml += `
|
|
892
|
-
<div class="table-row">
|
|
893
|
-
<div class="endpoint-path">
|
|
894
|
-
<span class="expand-icon">›</span>
|
|
895
|
-
<span>${escapeHtml(endpoint.path)}</span>
|
|
896
|
-
</div>
|
|
897
|
-
<div><span class="status-badge ${statusClass}">${endpoint.status}</span></div>
|
|
898
|
-
<div><span class="${readableClass}">${readableIcon}</span></div>
|
|
899
|
-
<div><span class="${writableClass}">${writableIcon}</span></div>
|
|
900
|
-
<div>${sensitiveDisplay}</div>
|
|
901
|
-
</div>`;
|
|
902
|
-
}
|
|
903
|
-
return `
|
|
904
|
-
<!-- Endpoints Section -->
|
|
905
|
-
<div class="endpoints-section">
|
|
906
|
-
<div class="endpoints-header">
|
|
907
|
-
<h2>
|
|
908
|
-
<span style="font-size: 20px;">🗄</span>
|
|
909
|
-
Endpoints
|
|
910
|
-
</h2>
|
|
911
|
-
<p>A list of all API endpoints discovered and analyzed.</p>
|
|
912
|
-
</div>
|
|
913
|
-
<div class="endpoints-table">
|
|
914
|
-
<div class="table-header">
|
|
915
|
-
<div>Path</div>
|
|
916
|
-
<div>Status</div>
|
|
917
|
-
<div>Readable</div>
|
|
918
|
-
<div>Writable</div>
|
|
919
|
-
<div>Sensitive Data</div>
|
|
920
|
-
</div>
|
|
921
|
-
${rowsHtml}
|
|
922
|
-
</div>
|
|
923
|
-
</div>`;
|
|
1221
|
+
function getGradeMessage(grade) {
|
|
1222
|
+
const messages = {
|
|
1223
|
+
'A': 'Excellent security posture! Your application follows security best practices.',
|
|
1224
|
+
'B': 'Good security with minor improvements possible. Review medium and low priority issues.',
|
|
1225
|
+
'C': 'Average security posture. Some issues need attention to improve your security grade.',
|
|
1226
|
+
'D': 'Below average security. Multiple serious issues found that should be addressed.',
|
|
1227
|
+
'F': 'Critical vulnerabilities detected! Immediate action required to secure your application.'
|
|
1228
|
+
};
|
|
1229
|
+
return messages[grade] || 'Review findings for details.';
|
|
924
1230
|
}
|
|
925
1231
|
/**
|
|
926
1232
|
* Escape HTML special characters
|