@vibecheckai/cli 3.1.2 → 3.1.4
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/README.md +60 -33
- package/bin/registry.js +319 -34
- package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
- package/bin/runners/REPORT_AUDIT.md +64 -0
- package/bin/runners/lib/entitlements-v2.js +97 -28
- package/bin/runners/lib/entitlements.js +3 -6
- package/bin/runners/lib/init-wizard.js +1 -1
- package/bin/runners/lib/report-engine.js +459 -280
- package/bin/runners/lib/report-html.js +1154 -1423
- package/bin/runners/lib/report-output.js +187 -0
- package/bin/runners/lib/report-templates.js +848 -850
- package/bin/runners/lib/scan-output.js +545 -0
- package/bin/runners/lib/server-usage.js +0 -12
- package/bin/runners/lib/ship-output.js +641 -0
- package/bin/runners/lib/status-output.js +253 -0
- package/bin/runners/lib/terminal-ui.js +853 -0
- package/bin/runners/runCheckpoint.js +502 -0
- package/bin/runners/runContracts.js +105 -0
- package/bin/runners/runExport.js +93 -0
- package/bin/runners/runFix.js +31 -24
- package/bin/runners/runInit.js +377 -112
- package/bin/runners/runInstall.js +1 -5
- package/bin/runners/runLabs.js +3 -3
- package/bin/runners/runPolish.js +2452 -0
- package/bin/runners/runProve.js +2 -2
- package/bin/runners/runReport.js +251 -200
- package/bin/runners/runRuntime.js +110 -0
- package/bin/runners/runScan.js +477 -379
- package/bin/runners/runSecurity.js +92 -0
- package/bin/runners/runShip.js +137 -207
- package/bin/runners/runStatus.js +16 -68
- package/bin/runners/utils.js +5 -5
- package/bin/vibecheck.js +25 -11
- package/mcp-server/index.js +150 -18
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +13 -13
- package/mcp-server/tier-auth.js +292 -27
- package/mcp-server/vibecheck-tools.js +9 -9
- package/package.json +1 -1
- package/bin/runners/runClaimVerifier.js +0 -483
- package/bin/runners/runContextCompiler.js +0 -385
- package/bin/runners/runGate.js +0 -17
- package/bin/runners/runInitGha.js +0 -164
- package/bin/runners/runInteractive.js +0 -388
- package/bin/runners/runMdc.js +0 -204
- package/bin/runners/runMissionGenerator.js +0 -282
- package/bin/runners/runTruthpack.js +0 -636
|
@@ -1,969 +1,967 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Import from report.js for the unified API.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Enhanced Report Templates
|
|
2
|
+
* Report Templates - Executive & Compliance Reports
|
|
8
3
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* - Category progress bars
|
|
13
|
-
* - Severity breakdown charts
|
|
14
|
-
* - Trend sparklines
|
|
15
|
-
* - Professional branding
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Generate SVG score gauge
|
|
4
|
+
* Purpose-built reports for different audiences:
|
|
5
|
+
* - Executive: C-suite friendly, minimal technical detail
|
|
6
|
+
* - Compliance: SOC2/HIPAA/PCI-DSS ready language
|
|
20
7
|
*/
|
|
21
|
-
function generateScoreGauge(score, size = 180) {
|
|
22
|
-
const radius = (size - 20) / 2;
|
|
23
|
-
const circumference = 2 * Math.PI * radius;
|
|
24
|
-
const progress = (score / 100) * circumference;
|
|
25
|
-
const remaining = circumference - progress;
|
|
26
|
-
|
|
27
|
-
const color = score >= 80 ? '#22c55e' : score >= 60 ? '#eab308' : '#ef4444';
|
|
28
|
-
const bgColor = '#e2e8f0';
|
|
29
|
-
|
|
30
|
-
return `
|
|
31
|
-
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" class="score-gauge">
|
|
32
|
-
<circle
|
|
33
|
-
cx="${size/2}" cy="${size/2}" r="${radius}"
|
|
34
|
-
fill="none" stroke="${bgColor}" stroke-width="12"
|
|
35
|
-
/>
|
|
36
|
-
<circle
|
|
37
|
-
cx="${size/2}" cy="${size/2}" r="${radius}"
|
|
38
|
-
fill="none" stroke="${color}" stroke-width="12"
|
|
39
|
-
stroke-dasharray="${progress} ${remaining}"
|
|
40
|
-
stroke-linecap="round"
|
|
41
|
-
transform="rotate(-90 ${size/2} ${size/2})"
|
|
42
|
-
class="gauge-progress"
|
|
43
|
-
/>
|
|
44
|
-
<text x="${size/2}" y="${size/2 + 8}" text-anchor="middle" class="gauge-score">${score}</text>
|
|
45
|
-
<text x="${size/2}" y="${size/2 + 28}" text-anchor="middle" class="gauge-label">/ 100</text>
|
|
46
|
-
</svg>
|
|
47
|
-
`;
|
|
48
|
-
}
|
|
49
8
|
|
|
50
9
|
/**
|
|
51
|
-
* Generate
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const displayName = formatCategoryName(name);
|
|
60
|
-
|
|
61
|
-
html += `
|
|
62
|
-
<div class="category-bar-item">
|
|
63
|
-
<div class="category-bar-header">
|
|
64
|
-
<span class="category-icon" style="color: ${color}">${icon}</span>
|
|
65
|
-
<span class="category-name">${displayName}</span>
|
|
66
|
-
<span class="category-score" style="color: ${color}">${score}%</span>
|
|
67
|
-
</div>
|
|
68
|
-
<div class="category-bar-track">
|
|
69
|
-
<div class="category-bar-fill" style="width: ${score}%; background: ${color}"></div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
html += '</div>';
|
|
76
|
-
return html;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Generate severity breakdown
|
|
10
|
+
* Generate Executive Report (C-Suite / Stakeholders)
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Single-page summary
|
|
14
|
+
* - No code snippets or file paths
|
|
15
|
+
* - Risk-focused language
|
|
16
|
+
* - Clear action items
|
|
17
|
+
* - Print-ready layout
|
|
81
18
|
*/
|
|
82
|
-
function
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
medium: findings.filter(f => f.severity === 'medium').length,
|
|
87
|
-
low: findings.filter(f => f.severity === 'low').length,
|
|
88
|
-
};
|
|
19
|
+
function generateEnhancedExecutiveReport(data, opts = {}) {
|
|
20
|
+
const { projectName, generatedAt, score, verdict, findings, categoryScores } = data;
|
|
21
|
+
const company = opts.company || "";
|
|
22
|
+
const logo = opts.logo || "";
|
|
89
23
|
|
|
90
|
-
|
|
24
|
+
// Calculate metrics
|
|
25
|
+
const criticalCount = findings?.filter(f => ["BLOCK", "critical"].includes(f.severity)).length || 0;
|
|
26
|
+
const highCount = findings?.filter(f => f.severity === "high").length || 0;
|
|
27
|
+
const totalIssues = findings?.length || 0;
|
|
91
28
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
medium: '#eab308',
|
|
96
|
-
low: '#94a3b8',
|
|
97
|
-
};
|
|
29
|
+
// Risk level
|
|
30
|
+
const riskLevel = score >= 80 ? "Low" : score >= 60 ? "Medium" : "High";
|
|
31
|
+
const riskColor = score >= 80 ? "#10b981" : score >= 60 ? "#f59e0b" : "#ef4444";
|
|
98
32
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
33
|
+
// Verdict config
|
|
34
|
+
const verdictMap = {
|
|
35
|
+
SHIP: { text: "Ready for Production", color: "#10b981", icon: "✓" },
|
|
36
|
+
WARN: { text: "Requires Attention", color: "#f59e0b", icon: "!" },
|
|
37
|
+
BLOCK: { text: "Not Recommended", color: "#ef4444", icon: "✕" },
|
|
104
38
|
};
|
|
39
|
+
const v = verdictMap[verdict] || verdictMap.WARN;
|
|
105
40
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (count > 0) {
|
|
116
|
-
const width = (count / total) * 100;
|
|
117
|
-
html += `<div class="severity-segment" style="width: ${width}%; background: ${colors[sev]}" title="${sev}: ${count}"></div>`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
html += '</div>';
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
html += '</div><div class="severity-legend">';
|
|
124
|
-
|
|
125
|
-
for (const [sev, count] of Object.entries(counts)) {
|
|
126
|
-
html += `
|
|
127
|
-
<div class="severity-item">
|
|
128
|
-
<span class="severity-icon">${icons[sev]}</span>
|
|
129
|
-
<span class="severity-label">${sev.charAt(0).toUpperCase() + sev.slice(1)}</span>
|
|
130
|
-
<span class="severity-count">${count}</span>
|
|
131
|
-
</div>
|
|
132
|
-
`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
html += '</div></div>';
|
|
136
|
-
return html;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Generate findings list
|
|
141
|
-
*/
|
|
142
|
-
function generateFindingsList(findings, options = {}) {
|
|
143
|
-
const limit = options.limit || 10;
|
|
144
|
-
const showFile = !options.redactPaths;
|
|
145
|
-
|
|
146
|
-
const severityColors = {
|
|
147
|
-
critical: '#dc2626',
|
|
148
|
-
high: '#f97316',
|
|
149
|
-
medium: '#eab308',
|
|
150
|
-
low: '#94a3b8',
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
let html = '<div class="findings-list">';
|
|
154
|
-
|
|
155
|
-
const displayFindings = findings.slice(0, limit);
|
|
156
|
-
|
|
157
|
-
for (const finding of displayFindings) {
|
|
158
|
-
const color = severityColors[finding.severity] || '#94a3b8';
|
|
159
|
-
const sevLabel = (finding.severity || 'medium').toUpperCase();
|
|
160
|
-
|
|
161
|
-
html += `
|
|
162
|
-
<div class="finding-card" style="border-left-color: ${color}">
|
|
163
|
-
<div class="finding-header">
|
|
164
|
-
<span class="finding-severity" style="background: ${color}">${sevLabel}</span>
|
|
165
|
-
<span class="finding-title">${finding.message || finding.title || 'Finding'}</span>
|
|
166
|
-
</div>
|
|
167
|
-
${finding.description ? `<p class="finding-description">${finding.description}</p>` : ''}
|
|
168
|
-
${showFile && finding.file ? `<code class="finding-file">${finding.file}${finding.line ? ':' + finding.line : ''}</code>` : ''}
|
|
169
|
-
${finding.fix ? `<div class="finding-fix"><strong>Fix:</strong> ${finding.fix}</div>` : ''}
|
|
170
|
-
</div>
|
|
171
|
-
`;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (findings.length > limit) {
|
|
175
|
-
html += `<div class="findings-more">+ ${findings.length - limit} more findings...</div>`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
html += '</div>';
|
|
179
|
-
return html;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get enhanced CSS styles
|
|
184
|
-
*/
|
|
185
|
-
function getEnhancedStyles() {
|
|
186
|
-
return `
|
|
187
|
-
:root {
|
|
188
|
-
--primary: #3b82f6;
|
|
189
|
-
--primary-dark: #1d4ed8;
|
|
190
|
-
--success: #22c55e;
|
|
191
|
-
--warning: #eab308;
|
|
192
|
-
--danger: #ef4444;
|
|
193
|
-
--gray-50: #f8fafc;
|
|
194
|
-
--gray-100: #f1f5f9;
|
|
195
|
-
--gray-200: #e2e8f0;
|
|
196
|
-
--gray-300: #cbd5e1;
|
|
197
|
-
--gray-600: #475569;
|
|
198
|
-
--gray-800: #1e293b;
|
|
199
|
-
--gray-900: #0f172a;
|
|
200
|
-
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
201
|
-
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
202
|
-
}
|
|
203
|
-
|
|
41
|
+
return `<!DOCTYPE html>
|
|
42
|
+
<html lang="en">
|
|
43
|
+
<head>
|
|
44
|
+
<meta charset="UTF-8">
|
|
45
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
46
|
+
<title>Executive Summary - ${projectName}</title>
|
|
47
|
+
<style>
|
|
48
|
+
@page { size: letter; margin: 0.75in; }
|
|
49
|
+
|
|
204
50
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
205
51
|
|
|
206
52
|
body {
|
|
207
|
-
font-family:
|
|
208
|
-
|
|
53
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
|
|
54
|
+
background: #ffffff;
|
|
55
|
+
color: #1a1a2e;
|
|
209
56
|
line-height: 1.6;
|
|
210
|
-
|
|
211
|
-
min-height: 100vh;
|
|
212
|
-
padding: 40px 20px;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.report {
|
|
216
|
-
max-width: 900px;
|
|
57
|
+
max-width: 850px;
|
|
217
58
|
margin: 0 auto;
|
|
218
|
-
background: white;
|
|
219
|
-
border-radius: 24px;
|
|
220
|
-
box-shadow: var(--shadow-lg);
|
|
221
|
-
overflow: hidden;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/* Header */
|
|
225
|
-
.report-header {
|
|
226
|
-
background: linear-gradient(135deg, var(--gray-900) 0%, var(--gray-800) 100%);
|
|
227
|
-
color: white;
|
|
228
59
|
padding: 48px;
|
|
229
|
-
position: relative;
|
|
230
|
-
overflow: hidden;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.report-header::before {
|
|
234
|
-
content: '';
|
|
235
|
-
position: absolute;
|
|
236
|
-
top: -50%;
|
|
237
|
-
right: -20%;
|
|
238
|
-
width: 60%;
|
|
239
|
-
height: 200%;
|
|
240
|
-
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 100%);
|
|
241
|
-
transform: rotate(-15deg);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.report-header h1 {
|
|
245
|
-
font-size: 32px;
|
|
246
|
-
font-weight: 700;
|
|
247
|
-
margin-bottom: 8px;
|
|
248
|
-
position: relative;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.report-meta {
|
|
252
|
-
opacity: 0.8;
|
|
253
|
-
font-size: 14px;
|
|
254
60
|
}
|
|
255
|
-
|
|
256
|
-
.
|
|
257
|
-
position: absolute;
|
|
258
|
-
top: 48px;
|
|
259
|
-
right: 48px;
|
|
260
|
-
font-size: 24px;
|
|
261
|
-
font-weight: 700;
|
|
262
|
-
opacity: 0.3;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/* Score Section */
|
|
266
|
-
.score-section {
|
|
61
|
+
|
|
62
|
+
.header {
|
|
267
63
|
display: flex;
|
|
268
|
-
align-items: center;
|
|
269
64
|
justify-content: space-between;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
65
|
+
align-items: flex-start;
|
|
66
|
+
border-bottom: 2px solid #e5e7eb;
|
|
67
|
+
padding-bottom: 24px;
|
|
68
|
+
margin-bottom: 32px;
|
|
273
69
|
}
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
277
|
-
align-items: center;
|
|
278
|
-
gap: 32px;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.score-gauge {
|
|
282
|
-
filter: drop-shadow(0 4px 6px rgb(0 0 0 / 0.1));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
.gauge-progress {
|
|
286
|
-
transition: stroke-dasharray 1s ease-out;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
.gauge-score {
|
|
290
|
-
font-size: 42px;
|
|
70
|
+
|
|
71
|
+
.header-left h1 {
|
|
72
|
+
font-size: 28px;
|
|
291
73
|
font-weight: 700;
|
|
292
|
-
|
|
74
|
+
color: #111827;
|
|
75
|
+
letter-spacing: -0.5px;
|
|
293
76
|
}
|
|
294
|
-
|
|
295
|
-
.
|
|
77
|
+
|
|
78
|
+
.header-left .subtitle {
|
|
296
79
|
font-size: 14px;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
.verdict-badge {
|
|
301
|
-
padding: 12px 28px;
|
|
302
|
-
border-radius: 50px;
|
|
303
|
-
font-weight: 600;
|
|
304
|
-
font-size: 18px;
|
|
305
|
-
text-transform: uppercase;
|
|
306
|
-
letter-spacing: 0.5px;
|
|
307
|
-
box-shadow: var(--shadow);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.verdict-badge.ship {
|
|
311
|
-
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
|
312
|
-
color: white;
|
|
80
|
+
color: #6b7280;
|
|
81
|
+
margin-top: 4px;
|
|
313
82
|
}
|
|
314
|
-
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
color: white;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
.verdict-badge.block {
|
|
321
|
-
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
322
|
-
color: white;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.score-info h2 {
|
|
326
|
-
font-size: 24px;
|
|
327
|
-
margin-bottom: 4px;
|
|
83
|
+
|
|
84
|
+
.header-right {
|
|
85
|
+
text-align: right;
|
|
328
86
|
}
|
|
329
|
-
|
|
330
|
-
.
|
|
331
|
-
|
|
332
|
-
|
|
87
|
+
|
|
88
|
+
.logo {
|
|
89
|
+
height: 36px;
|
|
90
|
+
margin-bottom: 8px;
|
|
333
91
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
92
|
+
|
|
93
|
+
.brand {
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
color: #9ca3af;
|
|
96
|
+
text-transform: uppercase;
|
|
97
|
+
letter-spacing: 0.1em;
|
|
338
98
|
}
|
|
339
|
-
|
|
340
|
-
.
|
|
341
|
-
|
|
99
|
+
|
|
100
|
+
.hero {
|
|
101
|
+
display: grid;
|
|
102
|
+
grid-template-columns: 200px 1fr;
|
|
103
|
+
gap: 40px;
|
|
104
|
+
margin-bottom: 40px;
|
|
105
|
+
padding: 32px;
|
|
106
|
+
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
107
|
+
border-radius: 16px;
|
|
108
|
+
border: 1px solid #e2e8f0;
|
|
342
109
|
}
|
|
343
|
-
|
|
344
|
-
.
|
|
110
|
+
|
|
111
|
+
.score-display {
|
|
345
112
|
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
346
114
|
align-items: center;
|
|
347
|
-
|
|
348
|
-
margin-bottom: 24px;
|
|
115
|
+
justify-content: center;
|
|
349
116
|
}
|
|
350
|
-
|
|
351
|
-
.
|
|
352
|
-
width:
|
|
353
|
-
height:
|
|
354
|
-
border-radius:
|
|
117
|
+
|
|
118
|
+
.score-circle {
|
|
119
|
+
width: 140px;
|
|
120
|
+
height: 140px;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
background: conic-gradient(
|
|
123
|
+
${riskColor} 0deg,
|
|
124
|
+
${riskColor} ${score * 3.6}deg,
|
|
125
|
+
#e5e7eb ${score * 3.6}deg,
|
|
126
|
+
#e5e7eb 360deg
|
|
127
|
+
);
|
|
355
128
|
display: flex;
|
|
356
129
|
align-items: center;
|
|
357
130
|
justify-content: center;
|
|
358
|
-
|
|
359
|
-
background: var(--primary);
|
|
360
|
-
color: white;
|
|
131
|
+
position: relative;
|
|
361
132
|
}
|
|
362
|
-
|
|
363
|
-
.
|
|
364
|
-
|
|
365
|
-
|
|
133
|
+
|
|
134
|
+
.score-circle::before {
|
|
135
|
+
content: '';
|
|
136
|
+
position: absolute;
|
|
137
|
+
width: 110px;
|
|
138
|
+
height: 110px;
|
|
139
|
+
border-radius: 50%;
|
|
140
|
+
background: white;
|
|
366
141
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
142
|
+
|
|
143
|
+
.score-value {
|
|
144
|
+
position: relative;
|
|
145
|
+
font-size: 40px;
|
|
146
|
+
font-weight: 800;
|
|
147
|
+
color: ${riskColor};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.score-label {
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
color: #6b7280;
|
|
153
|
+
text-transform: uppercase;
|
|
154
|
+
letter-spacing: 0.1em;
|
|
155
|
+
margin-top: 12px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.verdict-section {
|
|
370
159
|
display: flex;
|
|
371
160
|
flex-direction: column;
|
|
372
|
-
|
|
161
|
+
justify-content: center;
|
|
373
162
|
}
|
|
374
|
-
|
|
375
|
-
.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
163
|
+
|
|
164
|
+
.verdict-badge {
|
|
165
|
+
display: inline-flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: 10px;
|
|
168
|
+
padding: 10px 20px;
|
|
169
|
+
background: ${v.color}15;
|
|
170
|
+
border: 1px solid ${v.color}30;
|
|
171
|
+
border-radius: 50px;
|
|
172
|
+
width: fit-content;
|
|
173
|
+
margin-bottom: 16px;
|
|
379
174
|
}
|
|
380
|
-
|
|
381
|
-
.
|
|
175
|
+
|
|
176
|
+
.verdict-icon {
|
|
177
|
+
width: 24px;
|
|
178
|
+
height: 24px;
|
|
179
|
+
border-radius: 50%;
|
|
180
|
+
background: ${v.color};
|
|
181
|
+
color: white;
|
|
382
182
|
display: flex;
|
|
383
183
|
align-items: center;
|
|
384
|
-
|
|
385
|
-
margin-bottom: 8px;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.category-icon {
|
|
389
|
-
font-weight: 600;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.category-name {
|
|
393
|
-
flex: 1;
|
|
394
|
-
font-weight: 500;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
.category-score {
|
|
184
|
+
justify-content: center;
|
|
398
185
|
font-weight: 700;
|
|
186
|
+
font-size: 14px;
|
|
399
187
|
}
|
|
400
|
-
|
|
401
|
-
.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
overflow: hidden;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
.category-bar-fill {
|
|
409
|
-
height: 100%;
|
|
410
|
-
border-radius: 4px;
|
|
411
|
-
transition: width 0.5s ease-out;
|
|
188
|
+
|
|
189
|
+
.verdict-text {
|
|
190
|
+
font-weight: 700;
|
|
191
|
+
font-size: 15px;
|
|
192
|
+
color: ${v.color};
|
|
412
193
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
border-radius: 12px;
|
|
194
|
+
|
|
195
|
+
.verdict-summary {
|
|
196
|
+
font-size: 15px;
|
|
197
|
+
color: #374151;
|
|
198
|
+
line-height: 1.7;
|
|
419
199
|
}
|
|
420
|
-
|
|
421
|
-
.
|
|
422
|
-
|
|
423
|
-
height: 24px;
|
|
424
|
-
border-radius: 12px;
|
|
425
|
-
overflow: hidden;
|
|
426
|
-
margin-bottom: 20px;
|
|
200
|
+
|
|
201
|
+
.section {
|
|
202
|
+
margin-bottom: 32px;
|
|
427
203
|
}
|
|
428
|
-
|
|
429
|
-
.
|
|
430
|
-
|
|
204
|
+
|
|
205
|
+
.section-title {
|
|
206
|
+
font-size: 18px;
|
|
207
|
+
font-weight: 600;
|
|
208
|
+
color: #111827;
|
|
209
|
+
margin-bottom: 16px;
|
|
210
|
+
padding-bottom: 8px;
|
|
211
|
+
border-bottom: 1px solid #e5e7eb;
|
|
431
212
|
}
|
|
432
|
-
|
|
433
|
-
.
|
|
213
|
+
|
|
214
|
+
.metrics-grid {
|
|
434
215
|
display: grid;
|
|
435
216
|
grid-template-columns: repeat(4, 1fr);
|
|
436
217
|
gap: 16px;
|
|
437
218
|
}
|
|
438
|
-
|
|
439
|
-
.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
.severity-label {
|
|
446
|
-
flex: 1;
|
|
447
|
-
font-size: 14px;
|
|
448
|
-
color: var(--gray-600);
|
|
219
|
+
|
|
220
|
+
.metric-card {
|
|
221
|
+
background: #f9fafb;
|
|
222
|
+
border: 1px solid #e5e7eb;
|
|
223
|
+
border-radius: 12px;
|
|
224
|
+
padding: 20px;
|
|
225
|
+
text-align: center;
|
|
449
226
|
}
|
|
450
|
-
|
|
451
|
-
.
|
|
227
|
+
|
|
228
|
+
.metric-value {
|
|
229
|
+
font-size: 32px;
|
|
452
230
|
font-weight: 700;
|
|
453
|
-
font-size: 18px;
|
|
454
231
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
232
|
+
|
|
233
|
+
.metric-value.critical { color: #ef4444; }
|
|
234
|
+
.metric-value.high { color: #f97316; }
|
|
235
|
+
.metric-value.medium { color: #f59e0b; }
|
|
236
|
+
.metric-value.low { color: #3b82f6; }
|
|
237
|
+
.metric-value.neutral { color: #6b7280; }
|
|
238
|
+
|
|
239
|
+
.metric-label {
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
color: #6b7280;
|
|
242
|
+
text-transform: uppercase;
|
|
243
|
+
letter-spacing: 0.05em;
|
|
244
|
+
margin-top: 4px;
|
|
461
245
|
}
|
|
462
|
-
|
|
463
|
-
.
|
|
464
|
-
|
|
465
|
-
border-
|
|
466
|
-
padding: 16px 20px;
|
|
467
|
-
border-radius: 0 12px 12px 0;
|
|
246
|
+
|
|
247
|
+
.risk-table {
|
|
248
|
+
width: 100%;
|
|
249
|
+
border-collapse: collapse;
|
|
468
250
|
}
|
|
469
|
-
|
|
470
|
-
.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
251
|
+
|
|
252
|
+
.risk-table th {
|
|
253
|
+
text-align: left;
|
|
254
|
+
font-size: 12px;
|
|
255
|
+
color: #6b7280;
|
|
256
|
+
text-transform: uppercase;
|
|
257
|
+
letter-spacing: 0.05em;
|
|
258
|
+
padding: 12px 16px;
|
|
259
|
+
background: #f9fafb;
|
|
260
|
+
border-bottom: 1px solid #e5e7eb;
|
|
475
261
|
}
|
|
476
|
-
|
|
477
|
-
.
|
|
478
|
-
padding:
|
|
479
|
-
border-
|
|
480
|
-
font-size:
|
|
481
|
-
font-weight: 700;
|
|
482
|
-
color: white;
|
|
262
|
+
|
|
263
|
+
.risk-table td {
|
|
264
|
+
padding: 16px;
|
|
265
|
+
border-bottom: 1px solid #e5e7eb;
|
|
266
|
+
font-size: 14px;
|
|
483
267
|
}
|
|
484
|
-
|
|
485
|
-
.
|
|
268
|
+
|
|
269
|
+
.risk-table .category {
|
|
486
270
|
font-weight: 600;
|
|
271
|
+
color: #111827;
|
|
487
272
|
}
|
|
488
|
-
|
|
489
|
-
.
|
|
490
|
-
color: var(--gray-600);
|
|
491
|
-
font-size: 14px;
|
|
492
|
-
margin-bottom: 8px;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.finding-file {
|
|
273
|
+
|
|
274
|
+
.status-badge {
|
|
496
275
|
display: inline-block;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
border-radius: 4px;
|
|
276
|
+
padding: 4px 12px;
|
|
277
|
+
border-radius: 50px;
|
|
500
278
|
font-size: 12px;
|
|
501
|
-
|
|
279
|
+
font-weight: 600;
|
|
502
280
|
}
|
|
503
|
-
|
|
504
|
-
.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
281
|
+
|
|
282
|
+
.status-badge.pass { background: #d1fae5; color: #065f46; }
|
|
283
|
+
.status-badge.attention { background: #fef3c7; color: #92400e; }
|
|
284
|
+
.status-badge.fail { background: #fee2e2; color: #991b1b; }
|
|
285
|
+
|
|
286
|
+
.recommendation-box {
|
|
287
|
+
background: #fffbeb;
|
|
288
|
+
border: 1px solid #fcd34d;
|
|
289
|
+
border-radius: 12px;
|
|
290
|
+
padding: 20px;
|
|
511
291
|
}
|
|
512
|
-
|
|
513
|
-
.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
font-style: italic;
|
|
292
|
+
|
|
293
|
+
.recommendation-title {
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
color: #92400e;
|
|
296
|
+
margin-bottom: 8px;
|
|
518
297
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
padding: 32px 48px;
|
|
525
|
-
display: flex;
|
|
526
|
-
justify-content: space-between;
|
|
527
|
-
align-items: center;
|
|
298
|
+
|
|
299
|
+
.recommendation-text {
|
|
300
|
+
font-size: 14px;
|
|
301
|
+
color: #78350f;
|
|
302
|
+
line-height: 1.6;
|
|
528
303
|
}
|
|
529
|
-
|
|
530
|
-
.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
gap: 8px;
|
|
534
|
-
font-weight: 600;
|
|
304
|
+
|
|
305
|
+
.action-list {
|
|
306
|
+
list-style: none;
|
|
307
|
+
margin-top: 12px;
|
|
535
308
|
}
|
|
536
|
-
|
|
537
|
-
.
|
|
538
|
-
|
|
539
|
-
|
|
309
|
+
|
|
310
|
+
.action-list li {
|
|
311
|
+
padding: 8px 0 8px 24px;
|
|
312
|
+
position: relative;
|
|
313
|
+
font-size: 14px;
|
|
314
|
+
color: #78350f;
|
|
540
315
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
316
|
+
|
|
317
|
+
.action-list li::before {
|
|
318
|
+
content: '→';
|
|
319
|
+
position: absolute;
|
|
320
|
+
left: 0;
|
|
321
|
+
color: #f59e0b;
|
|
322
|
+
font-weight: bold;
|
|
548
323
|
}
|
|
549
|
-
|
|
550
|
-
.
|
|
551
|
-
margin-
|
|
324
|
+
|
|
325
|
+
.footer {
|
|
326
|
+
margin-top: 48px;
|
|
327
|
+
padding-top: 24px;
|
|
328
|
+
border-top: 1px solid #e5e7eb;
|
|
329
|
+
display: flex;
|
|
330
|
+
justify-content: space-between;
|
|
331
|
+
align-items: center;
|
|
332
|
+
font-size: 12px;
|
|
333
|
+
color: #9ca3af;
|
|
552
334
|
}
|
|
553
|
-
|
|
554
|
-
.
|
|
555
|
-
color:
|
|
335
|
+
|
|
336
|
+
.footer a {
|
|
337
|
+
color: #6b7280;
|
|
338
|
+
text-decoration: none;
|
|
556
339
|
}
|
|
557
|
-
|
|
340
|
+
|
|
558
341
|
@media print {
|
|
559
|
-
body {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
box-shadow: none;
|
|
565
|
-
border-radius: 0;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
`;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Format category name
|
|
573
|
-
*/
|
|
574
|
-
function formatCategoryName(name) {
|
|
575
|
-
const names = {
|
|
576
|
-
functionality: 'Core Functionality',
|
|
577
|
-
auth: 'Authentication',
|
|
578
|
-
billing: 'Payment Integration',
|
|
579
|
-
reality: 'Runtime Verification',
|
|
580
|
-
code_quality: 'Code Quality',
|
|
581
|
-
};
|
|
582
|
-
return names[name] || name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' ');
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Generate enhanced executive report
|
|
587
|
-
*/
|
|
588
|
-
function generateEnhancedExecutiveReport(data, options = {}) {
|
|
589
|
-
const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
|
|
590
|
-
const verdictText = data.verdict === 'SHIP' ? '✅ Ready to Ship' :
|
|
591
|
-
data.verdict === 'WARN' ? '⚠️ Ship with Caution' : '🚫 Not Ready';
|
|
592
|
-
const verdictMessage = data.verdict === 'SHIP'
|
|
593
|
-
? 'All systems go. This application is ready for production.'
|
|
594
|
-
: data.verdict === 'WARN'
|
|
595
|
-
? 'Minor issues detected. Review recommended before deployment.'
|
|
596
|
-
: 'Critical issues found. Remediation required before shipping.';
|
|
597
|
-
|
|
598
|
-
return `<!DOCTYPE html>
|
|
599
|
-
<html lang="en">
|
|
600
|
-
<head>
|
|
601
|
-
<meta charset="UTF-8">
|
|
602
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
603
|
-
<title>Ship Readiness Report - ${data.projectName}</title>
|
|
604
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
605
|
-
<style>${getEnhancedStyles()}</style>
|
|
342
|
+
body { padding: 0; max-width: none; }
|
|
343
|
+
.hero { break-inside: avoid; }
|
|
344
|
+
.section { break-inside: avoid; }
|
|
345
|
+
}
|
|
346
|
+
</style>
|
|
606
347
|
</head>
|
|
607
348
|
<body>
|
|
608
|
-
<
|
|
609
|
-
<
|
|
610
|
-
<h1>
|
|
611
|
-
<div class="
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
</div>
|
|
616
|
-
|
|
617
|
-
|
|
349
|
+
<header class="header">
|
|
350
|
+
<div class="header-left">
|
|
351
|
+
<h1>Security Assessment</h1>
|
|
352
|
+
<div class="subtitle">${projectName} · ${new Date(generatedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</div>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="header-right">
|
|
355
|
+
${logo ? `<img src="${logo}" alt="Logo" class="logo">` : ''}
|
|
356
|
+
<div class="brand">${company || 'VibeCheck Report'}</div>
|
|
357
|
+
</div>
|
|
358
|
+
</header>
|
|
618
359
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
<
|
|
623
|
-
<h2>Vibe Score</h2>
|
|
624
|
-
<p>${verdictMessage}</p>
|
|
625
|
-
</div>
|
|
360
|
+
<section class="hero">
|
|
361
|
+
<div class="score-display">
|
|
362
|
+
<div class="score-circle">
|
|
363
|
+
<span class="score-value">${score}</span>
|
|
626
364
|
</div>
|
|
627
|
-
<div class="
|
|
628
|
-
</section>
|
|
629
|
-
|
|
630
|
-
<div class="report-content">
|
|
631
|
-
<section class="section">
|
|
632
|
-
<div class="section-header">
|
|
633
|
-
<div class="section-icon">📊</div>
|
|
634
|
-
<h3 class="section-title">Category Breakdown</h3>
|
|
635
|
-
</div>
|
|
636
|
-
${generateCategoryBars(data.categoryScores)}
|
|
637
|
-
</section>
|
|
638
|
-
|
|
639
|
-
<section class="section">
|
|
640
|
-
<div class="section-header">
|
|
641
|
-
<div class="section-icon">🎯</div>
|
|
642
|
-
<h3 class="section-title">Issues by Severity</h3>
|
|
643
|
-
</div>
|
|
644
|
-
${generateSeverityBreakdown(data.findings)}
|
|
645
|
-
</section>
|
|
646
|
-
|
|
647
|
-
${data.findings.length > 0 ? `
|
|
648
|
-
<section class="section">
|
|
649
|
-
<div class="section-header">
|
|
650
|
-
<div class="section-icon">🔍</div>
|
|
651
|
-
<h3 class="section-title">Key Findings</h3>
|
|
652
|
-
</div>
|
|
653
|
-
${generateFindingsList(data.findings, { limit: 5, redactPaths: options.redactPaths })}
|
|
654
|
-
</section>
|
|
655
|
-
` : ''}
|
|
656
|
-
|
|
657
|
-
<section class="section">
|
|
658
|
-
<div class="recommendation">
|
|
659
|
-
<h3>📋 Recommendation</h3>
|
|
660
|
-
<p>${data.verdict === 'SHIP'
|
|
661
|
-
? 'This application has passed all quality checks and is ready for production deployment. Continue monitoring after launch.'
|
|
662
|
-
: data.verdict === 'WARN'
|
|
663
|
-
? 'This application can be deployed, but the identified issues should be addressed in the next sprint to ensure long-term stability.'
|
|
664
|
-
: 'This application requires immediate attention. Critical issues must be resolved before deployment to prevent production incidents.'
|
|
665
|
-
}</p>
|
|
666
|
-
</div>
|
|
667
|
-
</section>
|
|
365
|
+
<div class="score-label">Security Score</div>
|
|
668
366
|
</div>
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
<span
|
|
673
|
-
|
|
367
|
+
<div class="verdict-section">
|
|
368
|
+
<div class="verdict-badge">
|
|
369
|
+
<span class="verdict-icon">${v.icon}</span>
|
|
370
|
+
<span class="verdict-text">${v.text}</span>
|
|
371
|
+
</div>
|
|
372
|
+
<p class="verdict-summary">${getExecutiveSummary(verdict, criticalCount, highCount, score)}</p>
|
|
373
|
+
</div>
|
|
374
|
+
</section>
|
|
375
|
+
|
|
376
|
+
<section class="section">
|
|
377
|
+
<h2 class="section-title">Risk Overview</h2>
|
|
378
|
+
<div class="metrics-grid">
|
|
379
|
+
<div class="metric-card">
|
|
380
|
+
<div class="metric-value critical">${criticalCount}</div>
|
|
381
|
+
<div class="metric-label">Critical Risk</div>
|
|
674
382
|
</div>
|
|
675
|
-
<div class="
|
|
676
|
-
|
|
383
|
+
<div class="metric-card">
|
|
384
|
+
<div class="metric-value high">${highCount}</div>
|
|
385
|
+
<div class="metric-label">High Risk</div>
|
|
677
386
|
</div>
|
|
678
|
-
|
|
679
|
-
|
|
387
|
+
<div class="metric-card">
|
|
388
|
+
<div class="metric-value neutral">${totalIssues}</div>
|
|
389
|
+
<div class="metric-label">Total Issues</div>
|
|
390
|
+
</div>
|
|
391
|
+
<div class="metric-card">
|
|
392
|
+
<div class="metric-value" style="color: ${riskColor}">${riskLevel}</div>
|
|
393
|
+
<div class="metric-label">Risk Level</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
</section>
|
|
397
|
+
|
|
398
|
+
${categoryScores && Object.keys(categoryScores).length > 0 ? `
|
|
399
|
+
<section class="section">
|
|
400
|
+
<h2 class="section-title">Security Categories</h2>
|
|
401
|
+
<table class="risk-table">
|
|
402
|
+
<thead>
|
|
403
|
+
<tr>
|
|
404
|
+
<th>Category</th>
|
|
405
|
+
<th>Score</th>
|
|
406
|
+
<th>Status</th>
|
|
407
|
+
</tr>
|
|
408
|
+
</thead>
|
|
409
|
+
<tbody>
|
|
410
|
+
${Object.entries(categoryScores).map(([cat, catScore]) => {
|
|
411
|
+
const status = catScore >= 80 ? 'pass' : catScore >= 60 ? 'attention' : 'fail';
|
|
412
|
+
const statusText = catScore >= 80 ? 'Passing' : catScore >= 60 ? 'Needs Review' : 'At Risk';
|
|
413
|
+
return `
|
|
414
|
+
<tr>
|
|
415
|
+
<td class="category">${formatCategoryName(cat)}</td>
|
|
416
|
+
<td>${catScore}%</td>
|
|
417
|
+
<td><span class="status-badge ${status}">${statusText}</span></td>
|
|
418
|
+
</tr>
|
|
419
|
+
`;
|
|
420
|
+
}).join('')}
|
|
421
|
+
</tbody>
|
|
422
|
+
</table>
|
|
423
|
+
</section>
|
|
424
|
+
` : ''}
|
|
425
|
+
|
|
426
|
+
<section class="section">
|
|
427
|
+
<h2 class="section-title">Recommended Actions</h2>
|
|
428
|
+
<div class="recommendation-box">
|
|
429
|
+
<div class="recommendation-title">${getRecommendationTitle(verdict)}</div>
|
|
430
|
+
<p class="recommendation-text">${getRecommendationText(verdict, criticalCount, highCount)}</p>
|
|
431
|
+
<ul class="action-list">
|
|
432
|
+
${getActionItems(verdict, criticalCount, highCount).map(item => `<li>${item}</li>`).join('')}
|
|
433
|
+
</ul>
|
|
434
|
+
</div>
|
|
435
|
+
</section>
|
|
436
|
+
|
|
437
|
+
<footer class="footer">
|
|
438
|
+
<span>Generated by VibeCheck · <a href="https://vibecheck.dev">vibecheck.dev</a></span>
|
|
439
|
+
<span>Report ID: ${data.reportId || 'VC-' + Date.now().toString(36).toUpperCase()}</span>
|
|
440
|
+
</footer>
|
|
680
441
|
</body>
|
|
681
442
|
</html>`;
|
|
682
443
|
}
|
|
683
444
|
|
|
684
445
|
/**
|
|
685
|
-
* Generate
|
|
446
|
+
* Generate Compliance Report (SOC2/HIPAA/PCI-DSS ready)
|
|
686
447
|
*/
|
|
687
|
-
function
|
|
688
|
-
const
|
|
448
|
+
function generateEnhancedComplianceReport(data, opts = {}) {
|
|
449
|
+
const { projectName, generatedAt, score, verdict, findings, categoryScores } = data;
|
|
450
|
+
const company = opts.company || "Organization";
|
|
451
|
+
const framework = opts.framework || "SOC2";
|
|
452
|
+
|
|
453
|
+
// Map findings to compliance controls
|
|
454
|
+
const complianceMapping = mapToComplianceControls(findings || [], framework);
|
|
689
455
|
|
|
690
456
|
return `<!DOCTYPE html>
|
|
691
457
|
<html lang="en">
|
|
692
458
|
<head>
|
|
693
459
|
<meta charset="UTF-8">
|
|
694
460
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
695
|
-
<title
|
|
696
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono&display=swap" rel="stylesheet">
|
|
461
|
+
<title>${framework} Compliance Report - ${projectName}</title>
|
|
697
462
|
<style>
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
463
|
+
@page { size: letter; margin: 0.75in; }
|
|
464
|
+
|
|
465
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
466
|
+
|
|
467
|
+
body {
|
|
468
|
+
font-family: 'Times New Roman', Georgia, serif;
|
|
469
|
+
background: #ffffff;
|
|
470
|
+
color: #1a1a1a;
|
|
471
|
+
line-height: 1.7;
|
|
472
|
+
max-width: 850px;
|
|
473
|
+
margin: 0 auto;
|
|
474
|
+
padding: 48px;
|
|
475
|
+
font-size: 12pt;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.cover {
|
|
479
|
+
text-align: center;
|
|
480
|
+
padding: 80px 0;
|
|
481
|
+
border-bottom: 3px double #1a1a1a;
|
|
482
|
+
margin-bottom: 40px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.cover h1 {
|
|
486
|
+
font-size: 28pt;
|
|
487
|
+
font-weight: normal;
|
|
488
|
+
text-transform: uppercase;
|
|
489
|
+
letter-spacing: 0.1em;
|
|
490
|
+
margin-bottom: 16px;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.cover .subtitle {
|
|
494
|
+
font-size: 14pt;
|
|
495
|
+
color: #666;
|
|
496
|
+
margin-bottom: 40px;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.cover .meta {
|
|
500
|
+
font-size: 11pt;
|
|
501
|
+
color: #666;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.cover .meta p {
|
|
505
|
+
margin: 4px 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.toc {
|
|
509
|
+
margin-bottom: 40px;
|
|
510
|
+
page-break-after: always;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.toc h2 {
|
|
514
|
+
font-size: 14pt;
|
|
515
|
+
text-transform: uppercase;
|
|
516
|
+
letter-spacing: 0.1em;
|
|
517
|
+
border-bottom: 1px solid #1a1a1a;
|
|
518
|
+
padding-bottom: 8px;
|
|
519
|
+
margin-bottom: 16px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.toc-item {
|
|
523
|
+
display: flex;
|
|
524
|
+
justify-content: space-between;
|
|
525
|
+
padding: 8px 0;
|
|
526
|
+
border-bottom: 1px dotted #ccc;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.section {
|
|
530
|
+
margin-bottom: 32px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.section h2 {
|
|
534
|
+
font-size: 14pt;
|
|
535
|
+
text-transform: uppercase;
|
|
536
|
+
letter-spacing: 0.1em;
|
|
537
|
+
border-bottom: 1px solid #1a1a1a;
|
|
538
|
+
padding-bottom: 8px;
|
|
539
|
+
margin-bottom: 16px;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.section h3 {
|
|
543
|
+
font-size: 12pt;
|
|
544
|
+
font-weight: bold;
|
|
545
|
+
margin: 16px 0 8px;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
p {
|
|
549
|
+
margin-bottom: 12px;
|
|
550
|
+
text-align: justify;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.attestation-box {
|
|
554
|
+
border: 2px solid #1a1a1a;
|
|
555
|
+
padding: 24px;
|
|
556
|
+
margin: 24px 0;
|
|
557
|
+
background: #fafafa;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.attestation-box h3 {
|
|
561
|
+
text-transform: uppercase;
|
|
562
|
+
letter-spacing: 0.1em;
|
|
563
|
+
font-size: 11pt;
|
|
564
|
+
margin-bottom: 12px;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.control-table {
|
|
568
|
+
width: 100%;
|
|
569
|
+
border-collapse: collapse;
|
|
570
|
+
margin: 16px 0;
|
|
571
|
+
font-size: 10pt;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.control-table th {
|
|
575
|
+
text-align: left;
|
|
576
|
+
background: #f0f0f0;
|
|
577
|
+
padding: 12px;
|
|
578
|
+
border: 1px solid #ccc;
|
|
579
|
+
font-weight: bold;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.control-table td {
|
|
583
|
+
padding: 12px;
|
|
584
|
+
border: 1px solid #ccc;
|
|
585
|
+
vertical-align: top;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.control-id {
|
|
589
|
+
font-family: 'Courier New', monospace;
|
|
590
|
+
font-weight: bold;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.status-compliant {
|
|
594
|
+
color: #065f46;
|
|
595
|
+
font-weight: bold;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.status-partial {
|
|
599
|
+
color: #92400e;
|
|
600
|
+
font-weight: bold;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.status-gap {
|
|
604
|
+
color: #991b1b;
|
|
605
|
+
font-weight: bold;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.finding-ref {
|
|
609
|
+
font-family: 'Courier New', monospace;
|
|
610
|
+
font-size: 9pt;
|
|
611
|
+
color: #666;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.signature-section {
|
|
615
|
+
margin-top: 60px;
|
|
616
|
+
padding-top: 24px;
|
|
617
|
+
border-top: 1px solid #1a1a1a;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.signature-line {
|
|
621
|
+
display: grid;
|
|
622
|
+
grid-template-columns: 1fr 1fr;
|
|
623
|
+
gap: 40px;
|
|
624
|
+
margin-top: 40px;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.signature-block {
|
|
628
|
+
border-top: 1px solid #1a1a1a;
|
|
629
|
+
padding-top: 8px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.signature-label {
|
|
633
|
+
font-size: 10pt;
|
|
634
|
+
color: #666;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.footer {
|
|
638
|
+
margin-top: 40px;
|
|
639
|
+
padding-top: 16px;
|
|
640
|
+
border-top: 1px solid #ccc;
|
|
641
|
+
font-size: 10pt;
|
|
642
|
+
color: #666;
|
|
643
|
+
text-align: center;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
@media print {
|
|
647
|
+
body { padding: 0; max-width: none; }
|
|
648
|
+
.section { break-inside: avoid; }
|
|
649
|
+
.control-table { break-inside: avoid; }
|
|
650
|
+
}
|
|
709
651
|
</style>
|
|
710
652
|
</head>
|
|
711
653
|
<body>
|
|
712
|
-
<div class="
|
|
713
|
-
<
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
</
|
|
720
|
-
<
|
|
721
|
-
</
|
|
722
|
-
|
|
723
|
-
<section class="score-section">
|
|
724
|
-
<div class="score-main">
|
|
725
|
-
${generateScoreGauge(data.score)}
|
|
726
|
-
<div class="score-info">
|
|
727
|
-
<h2>Technical Score</h2>
|
|
728
|
-
<p>${data.findings.length} issues found across ${Object.keys(data.categoryScores).length} categories</p>
|
|
729
|
-
</div>
|
|
730
|
-
</div>
|
|
731
|
-
<div class="verdict-badge ${verdictClass}">${data.verdict}</div>
|
|
732
|
-
</section>
|
|
654
|
+
<div class="cover">
|
|
655
|
+
<h1>${framework} Type II</h1>
|
|
656
|
+
<div class="subtitle">Security Assessment Report</div>
|
|
657
|
+
<p style="font-size: 18pt; margin-bottom: 40px;">${company}</p>
|
|
658
|
+
<div class="meta">
|
|
659
|
+
<p><strong>Application:</strong> ${projectName}</p>
|
|
660
|
+
<p><strong>Assessment Date:</strong> ${new Date(generatedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</p>
|
|
661
|
+
<p><strong>Report Period:</strong> Point-in-Time Assessment</p>
|
|
662
|
+
<p><strong>Overall Score:</strong> ${score}/100</p>
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
733
665
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
<tr><th>Category</th><th>Score</th><th>Status</th><th>Details</th></tr>
|
|
743
|
-
</thead>
|
|
744
|
-
<tbody>
|
|
745
|
-
${Object.entries(data.categoryScores).map(([cat, score]) => `
|
|
746
|
-
<tr>
|
|
747
|
-
<td>${formatCategoryName(cat)}</td>
|
|
748
|
-
<td><strong>${score}%</strong></td>
|
|
749
|
-
<td>${score >= 80 ? '✅ Pass' : score >= 60 ? '⚠️ Warning' : '❌ Fail'}</td>
|
|
750
|
-
<td>${score >= 80 ? 'No issues' : 'Requires attention'}</td>
|
|
751
|
-
</tr>
|
|
752
|
-
`).join('')}
|
|
753
|
-
</tbody>
|
|
754
|
-
</table>
|
|
755
|
-
</section>
|
|
666
|
+
<div class="toc">
|
|
667
|
+
<h2>Table of Contents</h2>
|
|
668
|
+
<div class="toc-item"><span>1. Executive Summary</span><span>2</span></div>
|
|
669
|
+
<div class="toc-item"><span>2. Scope of Assessment</span><span>3</span></div>
|
|
670
|
+
<div class="toc-item"><span>3. Control Assessment Results</span><span>4</span></div>
|
|
671
|
+
<div class="toc-item"><span>4. Findings and Recommendations</span><span>5</span></div>
|
|
672
|
+
<div class="toc-item"><span>5. Management Attestation</span><span>6</span></div>
|
|
673
|
+
</div>
|
|
756
674
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
675
|
+
<section class="section">
|
|
676
|
+
<h2>1. Executive Summary</h2>
|
|
677
|
+
<p>This report presents the results of a ${framework} security assessment conducted on ${projectName} as of ${new Date(generatedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}. The assessment was performed using automated security scanning tools to evaluate the application's adherence to ${framework} Trust Services Criteria.</p>
|
|
678
|
+
|
|
679
|
+
<div class="attestation-box">
|
|
680
|
+
<h3>Assessment Summary</h3>
|
|
681
|
+
<p><strong>Overall Compliance Score:</strong> ${score}/100</p>
|
|
682
|
+
<p><strong>Assessment Result:</strong> ${getComplianceVerdict(score)}</p>
|
|
683
|
+
<p><strong>Critical Findings:</strong> ${findings?.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length || 0}</p>
|
|
684
|
+
<p><strong>Total Findings:</strong> ${findings?.length || 0}</p>
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
<p>${getComplianceSummaryText(score, findings?.length || 0)}</p>
|
|
688
|
+
</section>
|
|
689
|
+
|
|
690
|
+
<section class="section">
|
|
691
|
+
<h2>2. Scope of Assessment</h2>
|
|
692
|
+
<p>The assessment covered the following areas of the ${projectName} application:</p>
|
|
693
|
+
<ul style="margin-left: 24px; margin-bottom: 16px;">
|
|
694
|
+
<li>Source code security analysis</li>
|
|
695
|
+
<li>Authentication and authorization controls</li>
|
|
696
|
+
<li>Data protection mechanisms</li>
|
|
697
|
+
<li>Configuration management</li>
|
|
698
|
+
<li>Error handling and logging</li>
|
|
699
|
+
<li>Third-party dependency analysis</li>
|
|
700
|
+
</ul>
|
|
701
|
+
<p>The assessment was conducted using VibeCheck automated security scanning, which evaluates code against industry-standard security controls and best practices.</p>
|
|
702
|
+
</section>
|
|
703
|
+
|
|
704
|
+
<section class="section">
|
|
705
|
+
<h2>3. Control Assessment Results</h2>
|
|
706
|
+
<p>The following table summarizes the assessment results mapped to ${framework} Trust Services Criteria:</p>
|
|
707
|
+
|
|
708
|
+
<table class="control-table">
|
|
709
|
+
<thead>
|
|
710
|
+
<tr>
|
|
711
|
+
<th style="width: 100px;">Control ID</th>
|
|
712
|
+
<th style="width: 200px;">Control Description</th>
|
|
713
|
+
<th style="width: 80px;">Status</th>
|
|
714
|
+
<th>Findings</th>
|
|
715
|
+
</tr>
|
|
716
|
+
</thead>
|
|
717
|
+
<tbody>
|
|
718
|
+
${complianceMapping.map(control => `
|
|
719
|
+
<tr>
|
|
720
|
+
<td class="control-id">${control.id}</td>
|
|
721
|
+
<td>${control.description}</td>
|
|
722
|
+
<td class="${control.statusClass}">${control.status}</td>
|
|
723
|
+
<td>${control.findings.length > 0
|
|
724
|
+
? control.findings.map(f => `<span class="finding-ref">${f}</span>`).join(', ')
|
|
725
|
+
: 'No findings'}</td>
|
|
726
|
+
</tr>
|
|
727
|
+
`).join('')}
|
|
728
|
+
</tbody>
|
|
729
|
+
</table>
|
|
730
|
+
</section>
|
|
731
|
+
|
|
732
|
+
<section class="section">
|
|
733
|
+
<h2>4. Findings and Recommendations</h2>
|
|
734
|
+
${findings && findings.length > 0 ? `
|
|
735
|
+
<p>The following findings were identified during the assessment and require remediation:</p>
|
|
736
|
+
|
|
737
|
+
${findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high').slice(0, 10).map((f, i) => `
|
|
738
|
+
<h3>Finding ${i + 1}: ${sanitizeHtml(f.title || f.message)}</h3>
|
|
739
|
+
<p><strong>Severity:</strong> ${(f.severity || 'medium').toUpperCase()}</p>
|
|
740
|
+
<p><strong>Impact:</strong> ${getComplianceImpact(f.severity)}</p>
|
|
741
|
+
<p><strong>Recommendation:</strong> ${sanitizeHtml(f.fix) || 'Implement appropriate controls to address this finding.'}</p>
|
|
742
|
+
`).join('')}
|
|
743
|
+
` : `
|
|
744
|
+
<p>No significant findings were identified during this assessment. The application demonstrates strong adherence to ${framework} security controls.</p>
|
|
745
|
+
`}
|
|
746
|
+
</section>
|
|
747
|
+
|
|
748
|
+
<section class="section">
|
|
749
|
+
<h2>5. Management Attestation</h2>
|
|
750
|
+
<p>This security assessment report has been prepared in accordance with ${framework} Trust Services Criteria. The assessment represents a point-in-time evaluation of the security controls implemented within the ${projectName} application.</p>
|
|
751
|
+
|
|
752
|
+
<div class="attestation-box">
|
|
753
|
+
<p>Management of ${company} is responsible for the design, implementation, and maintenance of effective internal controls relevant to the security, availability, processing integrity, confidentiality, and privacy of the ${projectName} application.</p>
|
|
754
|
+
</div>
|
|
755
|
+
|
|
756
|
+
<div class="signature-section">
|
|
757
|
+
<p>Authorized Signatures:</p>
|
|
758
|
+
<div class="signature-line">
|
|
759
|
+
<div class="signature-block">
|
|
760
|
+
<div class="signature-label">Security Officer / Date</div>
|
|
761
761
|
</div>
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
<section class="section">
|
|
766
|
-
<div class="section-header">
|
|
767
|
-
<div class="section-icon">🔍</div>
|
|
768
|
-
<h3 class="section-title">All Findings (${data.findings.length})</h3>
|
|
762
|
+
<div class="signature-block">
|
|
763
|
+
<div class="signature-label">Engineering Lead / Date</div>
|
|
769
764
|
</div>
|
|
770
|
-
|
|
771
|
-
</section>
|
|
765
|
+
</div>
|
|
772
766
|
</div>
|
|
767
|
+
</section>
|
|
773
768
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
</div>
|
|
779
|
-
<div class="footer-meta">
|
|
780
|
-
vibecheck.dev
|
|
781
|
-
</div>
|
|
782
|
-
</footer>
|
|
783
|
-
</div>
|
|
769
|
+
<footer class="footer">
|
|
770
|
+
<p>This report was generated by VibeCheck Security Assessment Tool · vibecheck.dev</p>
|
|
771
|
+
<p>Report ID: ${data.reportId || 'VC-' + Date.now().toString(36).toUpperCase()}</p>
|
|
772
|
+
</footer>
|
|
784
773
|
</body>
|
|
785
774
|
</html>`;
|
|
786
775
|
}
|
|
787
776
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
function generateEnhancedComplianceReport(data, options = {}) {
|
|
792
|
-
const assessmentId = `VC-${new Date().toISOString().split('T')[0].replace(/-/g, '')}-${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
|
|
793
|
-
const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
|
|
794
|
-
|
|
795
|
-
const criticalCount = data.findings.filter(f => f.severity === 'critical').length;
|
|
796
|
-
const highCount = data.findings.filter(f => f.severity === 'high').length;
|
|
797
|
-
|
|
798
|
-
return `<!DOCTYPE html>
|
|
799
|
-
<html lang="en">
|
|
800
|
-
<head>
|
|
801
|
-
<meta charset="UTF-8">
|
|
802
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
803
|
-
<title>Security Assessment Report - ${data.projectName}</title>
|
|
804
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
805
|
-
<style>
|
|
806
|
-
${getEnhancedStyles()}
|
|
807
|
-
.compliance-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin: 24px 0; }
|
|
808
|
-
.compliance-card { background: var(--gray-50); padding: 20px; border-radius: 12px; border: 1px solid var(--gray-200); }
|
|
809
|
-
.compliance-card h4 { margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
|
|
810
|
-
.control-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--gray-200); }
|
|
811
|
-
.control-item:last-child { border-bottom: none; }
|
|
812
|
-
.control-status { width: 24px; text-align: center; }
|
|
813
|
-
.attestation { background: linear-gradient(135deg, #1e293b 0%, #334155 100%); color: white; padding: 32px; border-radius: 12px; margin-top: 32px; }
|
|
814
|
-
.attestation h3 { margin-bottom: 16px; }
|
|
815
|
-
.signature-line { margin-top: 32px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.2); display: flex; justify-content: space-between; }
|
|
816
|
-
</style>
|
|
817
|
-
</head>
|
|
818
|
-
<body>
|
|
819
|
-
<div class="report">
|
|
820
|
-
<header class="report-header" style="background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);">
|
|
821
|
-
<h1>Application Security Assessment</h1>
|
|
822
|
-
<div class="report-meta">
|
|
823
|
-
<p><strong>Application:</strong> ${data.projectName}</p>
|
|
824
|
-
<p><strong>Assessment Date:</strong> ${new Date(data.generatedAt).toLocaleDateString()}</p>
|
|
825
|
-
<p><strong>Assessment ID:</strong> ${assessmentId}</p>
|
|
826
|
-
${options.company ? `<p><strong>Prepared For:</strong> ${options.company}</p>` : ''}
|
|
827
|
-
</div>
|
|
828
|
-
<div class="report-logo">VIBECHECK</div>
|
|
829
|
-
</header>
|
|
777
|
+
// ============================================================================
|
|
778
|
+
// HELPER FUNCTIONS
|
|
779
|
+
// ============================================================================
|
|
830
780
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
781
|
+
function formatCategoryName(cat) {
|
|
782
|
+
const names = {
|
|
783
|
+
security: "Security Controls",
|
|
784
|
+
auth: "Authentication & Access",
|
|
785
|
+
billing: "Payment Security",
|
|
786
|
+
routes: "API Security",
|
|
787
|
+
env: "Configuration Management",
|
|
788
|
+
quality: "Code Quality",
|
|
789
|
+
mock: "Data Integrity",
|
|
790
|
+
error: "Error Handling",
|
|
791
|
+
};
|
|
792
|
+
return names[cat] || cat.charAt(0).toUpperCase() + cat.slice(1).replace(/_/g, " ");
|
|
793
|
+
}
|
|
841
794
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
data protection, and operational security controls.
|
|
852
|
-
</p>
|
|
853
|
-
<div class="compliance-grid">
|
|
854
|
-
<div class="compliance-card">
|
|
855
|
-
<h4>🔴 Critical Findings</h4>
|
|
856
|
-
<div style="font-size: 32px; font-weight: 700; color: ${criticalCount > 0 ? '#dc2626' : '#22c55e'};">${criticalCount}</div>
|
|
857
|
-
<p style="color: var(--gray-600); font-size: 14px;">${criticalCount > 0 ? 'Immediate remediation required' : 'No critical issues'}</p>
|
|
858
|
-
</div>
|
|
859
|
-
<div class="compliance-card">
|
|
860
|
-
<h4>🟠 High Priority</h4>
|
|
861
|
-
<div style="font-size: 32px; font-weight: 700; color: ${highCount > 0 ? '#f97316' : '#22c55e'};">${highCount}</div>
|
|
862
|
-
<p style="color: var(--gray-600); font-size: 14px;">${highCount > 0 ? 'Should be addressed soon' : 'No high priority issues'}</p>
|
|
863
|
-
</div>
|
|
864
|
-
</div>
|
|
865
|
-
</section>
|
|
795
|
+
function getExecutiveSummary(verdict, critical, high, score) {
|
|
796
|
+
if (verdict === "SHIP") {
|
|
797
|
+
return "The application has successfully passed all critical security checks. No blocking vulnerabilities were identified, and the security posture meets production deployment requirements.";
|
|
798
|
+
} else if (verdict === "WARN") {
|
|
799
|
+
return `The application requires attention before production deployment. ${critical > 0 ? `${critical} critical` : `${high} high priority`} security issue${(critical + high) > 1 ? 's were' : ' was'} identified that should be addressed to reduce organizational risk.`;
|
|
800
|
+
} else {
|
|
801
|
+
return `The application is not recommended for production deployment in its current state. ${critical} critical security vulnerabilit${critical > 1 ? 'ies were' : 'y was'} identified that pose significant risk to the organization.`;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
866
804
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
<div class="compliance-grid">
|
|
873
|
-
<div class="compliance-card">
|
|
874
|
-
<h4>🔐 Access Control</h4>
|
|
875
|
-
<div class="control-item">
|
|
876
|
-
<span class="control-status">${data.categoryScores.auth >= 80 ? '✅' : '❌'}</span>
|
|
877
|
-
<span>Authentication required for protected routes</span>
|
|
878
|
-
</div>
|
|
879
|
-
<div class="control-item">
|
|
880
|
-
<span class="control-status">⬜</span>
|
|
881
|
-
<span>Session management (manual review)</span>
|
|
882
|
-
</div>
|
|
883
|
-
<div class="control-item">
|
|
884
|
-
<span class="control-status">⬜</span>
|
|
885
|
-
<span>Password hashing (manual review)</span>
|
|
886
|
-
</div>
|
|
887
|
-
</div>
|
|
888
|
-
<div class="compliance-card">
|
|
889
|
-
<h4>🛡️ Data Protection</h4>
|
|
890
|
-
<div class="control-item">
|
|
891
|
-
<span class="control-status">⬜</span>
|
|
892
|
-
<span>Encryption in transit (TLS)</span>
|
|
893
|
-
</div>
|
|
894
|
-
<div class="control-item">
|
|
895
|
-
<span class="control-status">${data.findings.filter(f => f.type === 'secret').length === 0 ? '✅' : '❌'}</span>
|
|
896
|
-
<span>No secrets in codebase</span>
|
|
897
|
-
</div>
|
|
898
|
-
</div>
|
|
899
|
-
<div class="compliance-card">
|
|
900
|
-
<h4>💳 Payment Security</h4>
|
|
901
|
-
<div class="control-item">
|
|
902
|
-
<span class="control-status">${data.categoryScores.billing >= 80 ? '✅' : '❌'}</span>
|
|
903
|
-
<span>Billing gates enforced server-side</span>
|
|
904
|
-
</div>
|
|
905
|
-
</div>
|
|
906
|
-
<div class="compliance-card">
|
|
907
|
-
<h4>📝 Code Quality</h4>
|
|
908
|
-
<div class="control-item">
|
|
909
|
-
<span class="control-status">${data.categoryScores.code_quality >= 80 ? '✅' : '❌'}</span>
|
|
910
|
-
<span>No mock/demo code in production paths</span>
|
|
911
|
-
</div>
|
|
912
|
-
</div>
|
|
913
|
-
</div>
|
|
914
|
-
</section>
|
|
805
|
+
function getRecommendationTitle(verdict) {
|
|
806
|
+
if (verdict === "SHIP") return "Proceed with Deployment";
|
|
807
|
+
if (verdict === "WARN") return "Address Issues Before Deployment";
|
|
808
|
+
return "Halt Deployment - Remediation Required";
|
|
809
|
+
}
|
|
915
810
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
811
|
+
function getRecommendationText(verdict, critical, high) {
|
|
812
|
+
if (verdict === "SHIP") {
|
|
813
|
+
return "The security assessment indicates the application is ready for production. Maintain security hygiene through regular assessments.";
|
|
814
|
+
} else if (verdict === "WARN") {
|
|
815
|
+
return `Based on the assessment findings, we recommend addressing the identified issues before proceeding to production. Estimated remediation effort: ${(critical * 2 + high)}–${(critical * 4 + high * 2)} hours.`;
|
|
816
|
+
} else {
|
|
817
|
+
return `Immediate remediation is required before this application should be considered for production deployment. The identified vulnerabilities represent unacceptable risk to the organization.`;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
925
820
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
821
|
+
function getActionItems(verdict, critical, high) {
|
|
822
|
+
if (verdict === "SHIP") {
|
|
823
|
+
return [
|
|
824
|
+
"Schedule regular security assessments (recommended: quarterly)",
|
|
825
|
+
"Monitor for newly discovered vulnerabilities in dependencies",
|
|
826
|
+
"Maintain security documentation for compliance purposes",
|
|
827
|
+
];
|
|
828
|
+
} else if (verdict === "WARN") {
|
|
829
|
+
return [
|
|
830
|
+
critical > 0 ? `Resolve ${critical} critical finding${critical > 1 ? 's' : ''} immediately` : `Address ${high} high priority finding${high > 1 ? 's' : ''}`,
|
|
831
|
+
"Conduct security review with engineering team",
|
|
832
|
+
"Re-run assessment after remediation before deployment",
|
|
833
|
+
];
|
|
834
|
+
} else {
|
|
835
|
+
return [
|
|
836
|
+
`Immediately address all ${critical} critical vulnerabilities`,
|
|
837
|
+
"Implement security review process before any code changes",
|
|
838
|
+
"Consider engaging security consultant for remediation guidance",
|
|
839
|
+
"Do not deploy to production until all critical issues are resolved",
|
|
840
|
+
];
|
|
841
|
+
}
|
|
842
|
+
}
|
|
944
843
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
844
|
+
function mapToComplianceControls(findings, framework) {
|
|
845
|
+
// SOC2 Trust Services Criteria mapping
|
|
846
|
+
const controls = [
|
|
847
|
+
{ id: "CC6.1", description: "Logical Access Security", category: ["auth", "secret"] },
|
|
848
|
+
{ id: "CC6.2", description: "Access Provisioning", category: ["auth"] },
|
|
849
|
+
{ id: "CC6.6", description: "Data Transmission Protection", category: ["security", "config"] },
|
|
850
|
+
{ id: "CC6.7", description: "Data Destruction", category: ["quality"] },
|
|
851
|
+
{ id: "CC7.1", description: "Configuration Management", category: ["config", "env"] },
|
|
852
|
+
{ id: "CC7.2", description: "Security Monitoring", category: ["error", "quality"] },
|
|
853
|
+
{ id: "CC8.1", description: "Change Management", category: ["quality", "mock"] },
|
|
854
|
+
{ id: "CC9.1", description: "Business Continuity", category: ["error"] },
|
|
855
|
+
{ id: "PI1.1", description: "Input Validation", category: ["injection", "xss", "security"] },
|
|
856
|
+
{ id: "A1.2", description: "System Availability", category: ["error", "route"] },
|
|
857
|
+
];
|
|
858
|
+
|
|
859
|
+
return controls.map(control => {
|
|
860
|
+
const relatedFindings = findings.filter(f =>
|
|
861
|
+
control.category.includes(f.type) || control.category.includes(f.category)
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const criticalCount = relatedFindings.filter(f =>
|
|
865
|
+
f.severity === "critical" || f.severity === "BLOCK"
|
|
866
|
+
).length;
|
|
867
|
+
|
|
868
|
+
const highCount = relatedFindings.filter(f => f.severity === "high").length;
|
|
869
|
+
|
|
870
|
+
let status, statusClass;
|
|
871
|
+
if (criticalCount > 0) {
|
|
872
|
+
status = "Gap";
|
|
873
|
+
statusClass = "status-gap";
|
|
874
|
+
} else if (highCount > 0 || relatedFindings.length > 2) {
|
|
875
|
+
status = "Partial";
|
|
876
|
+
statusClass = "status-partial";
|
|
877
|
+
} else {
|
|
878
|
+
status = "Compliant";
|
|
879
|
+
statusClass = "status-compliant";
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return {
|
|
883
|
+
...control,
|
|
884
|
+
status,
|
|
885
|
+
statusClass,
|
|
886
|
+
findings: relatedFindings.slice(0, 3).map(f => f.id),
|
|
887
|
+
};
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function getComplianceVerdict(score) {
|
|
892
|
+
if (score >= 80) return "Meets Requirements";
|
|
893
|
+
if (score >= 60) return "Partially Meets Requirements";
|
|
894
|
+
return "Does Not Meet Requirements";
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function getComplianceSummaryText(score, findingCount) {
|
|
898
|
+
if (score >= 80) {
|
|
899
|
+
return `The assessment indicates that ${findingCount > 0 ? 'while minor findings were identified, ' : ''}the application substantially meets the security requirements outlined in the Trust Services Criteria. Continued monitoring and periodic assessments are recommended to maintain compliance.`;
|
|
900
|
+
} else if (score >= 60) {
|
|
901
|
+
return `The assessment identified several areas where the application's security controls require enhancement to fully meet Trust Services Criteria requirements. Remediation of the identified findings is recommended before the next audit period.`;
|
|
902
|
+
} else {
|
|
903
|
+
return `The assessment identified significant gaps in the application's security controls relative to Trust Services Criteria requirements. Management should prioritize remediation of critical and high-severity findings before considering the application compliant.`;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function getComplianceImpact(severity) {
|
|
908
|
+
const impacts = {
|
|
909
|
+
critical: "This finding represents a significant risk that could result in unauthorized access, data breach, or system compromise.",
|
|
910
|
+
BLOCK: "This finding represents a significant risk that could result in unauthorized access, data breach, or system compromise.",
|
|
911
|
+
high: "This finding could potentially lead to security incidents if exploited.",
|
|
912
|
+
medium: "This finding represents a moderate risk that should be addressed to maintain security posture.",
|
|
913
|
+
low: "This finding represents a minor risk with limited potential impact.",
|
|
914
|
+
};
|
|
915
|
+
return impacts[severity] || impacts.medium;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function sanitizeHtml(str) {
|
|
919
|
+
if (!str) return '';
|
|
920
|
+
return String(str)
|
|
921
|
+
.replace(/&/g, '&')
|
|
922
|
+
.replace(/</g, '<')
|
|
923
|
+
.replace(/>/g, '>')
|
|
924
|
+
.replace(/"/g, '"');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Generate Technical Report (for developers/CTOs)
|
|
929
|
+
*/
|
|
930
|
+
function generateEnhancedTechnicalReport(data, opts = {}) {
|
|
931
|
+
// For technical reports, we use the main HTML generator
|
|
932
|
+
// This is a passthrough - the report-html.js handles technical reports
|
|
933
|
+
const reportHtml = require("./report-html");
|
|
934
|
+
|
|
935
|
+
// Build report data if needed
|
|
936
|
+
const reportData = {
|
|
937
|
+
meta: {
|
|
938
|
+
projectName: data.projectName,
|
|
939
|
+
generatedAt: data.generatedAt,
|
|
940
|
+
version: "2.0.0",
|
|
941
|
+
reportId: data.reportId || 'VC-' + Date.now().toString(36).toUpperCase(),
|
|
942
|
+
},
|
|
943
|
+
summary: {
|
|
944
|
+
score: data.score,
|
|
945
|
+
verdict: data.verdict,
|
|
946
|
+
totalFindings: data.findings?.length || 0,
|
|
947
|
+
severityCounts: {
|
|
948
|
+
critical: data.findings?.filter(f => f.severity === "BLOCK" || f.severity === "critical").length || 0,
|
|
949
|
+
high: data.findings?.filter(f => f.severity === "high").length || 0,
|
|
950
|
+
medium: data.findings?.filter(f => f.severity === "WARN" || f.severity === "medium").length || 0,
|
|
951
|
+
low: data.findings?.filter(f => f.severity === "INFO" || f.severity === "low").length || 0,
|
|
952
|
+
},
|
|
953
|
+
categoryScores: data.categoryScores || {},
|
|
954
|
+
},
|
|
955
|
+
findings: data.findings || [],
|
|
956
|
+
fixEstimates: { humanReadable: "~2h" },
|
|
957
|
+
reality: data.reality || null,
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
return reportHtml.generateWorldClassHTML(reportData, opts);
|
|
957
961
|
}
|
|
958
962
|
|
|
959
963
|
module.exports = {
|
|
960
|
-
generateScoreGauge,
|
|
961
|
-
generateCategoryBars,
|
|
962
|
-
generateSeverityBreakdown,
|
|
963
|
-
generateFindingsList,
|
|
964
|
-
getEnhancedStyles,
|
|
965
964
|
generateEnhancedExecutiveReport,
|
|
966
|
-
generateEnhancedTechnicalReport,
|
|
967
965
|
generateEnhancedComplianceReport,
|
|
968
|
-
|
|
966
|
+
generateEnhancedTechnicalReport,
|
|
969
967
|
};
|