verification-layer 0.24.5 → 0.25.0

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.
Files changed (66) hide show
  1. package/README.md +39 -1
  2. package/dist/cli.js +76 -12
  3. package/dist/cli.js.map +1 -1
  4. package/dist/exclusions.d.ts +13 -0
  5. package/dist/exclusions.d.ts.map +1 -0
  6. package/dist/exclusions.js +27 -0
  7. package/dist/exclusions.js.map +1 -0
  8. package/dist/reporters/auditor-report.d.ts +2 -1
  9. package/dist/reporters/auditor-report.d.ts.map +1 -1
  10. package/dist/reporters/auditor-report.js +203 -16
  11. package/dist/reporters/auditor-report.js.map +1 -1
  12. package/dist/reporters/branding.d.ts +39 -0
  13. package/dist/reporters/branding.d.ts.map +1 -0
  14. package/dist/reporters/branding.js +124 -0
  15. package/dist/reporters/branding.js.map +1 -0
  16. package/dist/reporters/finding-presentation.d.ts +74 -0
  17. package/dist/reporters/finding-presentation.d.ts.map +1 -0
  18. package/dist/reporters/finding-presentation.js +172 -0
  19. package/dist/reporters/finding-presentation.js.map +1 -0
  20. package/dist/reporters/index.d.ts.map +1 -1
  21. package/dist/reporters/index.js +34 -0
  22. package/dist/reporters/index.js.map +1 -1
  23. package/dist/reporters/scan-pdf-report.d.ts +23 -0
  24. package/dist/reporters/scan-pdf-report.d.ts.map +1 -0
  25. package/dist/reporters/scan-pdf-report.js +326 -0
  26. package/dist/reporters/scan-pdf-report.js.map +1 -0
  27. package/dist/scan.d.ts +11 -0
  28. package/dist/scan.d.ts.map +1 -1
  29. package/dist/scan.js +46 -1
  30. package/dist/scan.js.map +1 -1
  31. package/dist/scanners/authentication/index.d.ts.map +1 -1
  32. package/dist/scanners/authentication/index.js +30 -23
  33. package/dist/scanners/authentication/index.js.map +1 -1
  34. package/dist/scanners/credentials/index.d.ts.map +1 -1
  35. package/dist/scanners/credentials/index.js +7 -2
  36. package/dist/scanners/credentials/index.js.map +1 -1
  37. package/dist/scanners/credentials/index.test.js +3 -3
  38. package/dist/scanners/credentials/patterns.d.ts.map +1 -1
  39. package/dist/scanners/credentials/patterns.js +3 -3
  40. package/dist/scanners/credentials/patterns.js.map +1 -1
  41. package/dist/scanners/hipaa2026/index.d.ts.map +1 -1
  42. package/dist/scanners/hipaa2026/index.js +7 -19
  43. package/dist/scanners/hipaa2026/index.js.map +1 -1
  44. package/dist/scanners/hipaa2026/index.test.js +2 -2
  45. package/dist/scanners/hipaa2026/patterns.d.ts.map +1 -1
  46. package/dist/scanners/hipaa2026/patterns.js +18 -5
  47. package/dist/scanners/hipaa2026/patterns.js.map +1 -1
  48. package/dist/scanners/operational/index.d.ts.map +1 -1
  49. package/dist/scanners/operational/index.js +24 -24
  50. package/dist/scanners/operational/index.js.map +1 -1
  51. package/dist/scanners/rbac/index.test.js +3 -0
  52. package/dist/scanners/rbac/index.test.js.map +1 -1
  53. package/dist/scanners/rbac/patterns.d.ts.map +1 -1
  54. package/dist/scanners/rbac/patterns.js +10 -3
  55. package/dist/scanners/rbac/patterns.js.map +1 -1
  56. package/dist/scanners/utils.d.ts +44 -0
  57. package/dist/scanners/utils.d.ts.map +1 -0
  58. package/dist/scanners/utils.js +77 -0
  59. package/dist/scanners/utils.js.map +1 -0
  60. package/dist/types.d.ts +38 -1
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +2 -2
  63. package/dist/scan-code.d.ts +0 -12
  64. package/dist/scan-code.d.ts.map +0 -1
  65. package/dist/scan-code.js +0 -34
  66. package/dist/scan-code.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"auditor-report.d.ts","sourceRoot":"","sources":["../../src/reporters/auditor-report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,UAAU,oBAAoB;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,oBAAyB,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA6YhC"}
1
+ {"version":3,"file":"auditor-report.d.ts","sourceRoot":"","sources":["../../src/reporters/auditor-report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAWhE,UAAU,oBAAoB;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,oBAAyB,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAwkBhC"}
@@ -1,14 +1,30 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { generateComplianceScoreGauge, generateExecutiveSummary, generateEnhancedCSS } from './enhanced-html.js';
3
+ import { brandFooterText, brandPreparedBy, logoDataUri } from './branding.js';
4
+ import { groupFindingsByLocation, countGroupsBySeverity, formatHipaaRef, partitionFindingsByStatus, sortProposedFindings, } from './finding-presentation.js';
3
5
  /**
4
6
  * Generate auditor-ready HTML report with compliance score and SHA256 hash
5
7
  */
6
8
  export function generateAuditorReport(result, targetPath, options = {}) {
7
- const { organizationName = 'Organization', reportPeriod = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' }), auditorName = 'VLayer Automated Scan', includeBaseline = false, } = options;
9
+ const { organizationName = 'Organization', reportPeriod = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' }), auditorName = 'VLayer Automated Scan', includeBaseline = false, branding, } = options;
10
+ // White-label branding. When a brand is supplied the cover shows the brand
11
+ // logo + "Prepared by {brand}", and a footer line repeats on every printed
12
+ // page. With no branding the report renders exactly as before (default VLayer).
13
+ const brandLogo = logoDataUri(branding);
14
+ const hasBrand = Boolean(branding?.name || brandLogo);
15
+ const preparedBy = brandPreparedBy(branding);
16
+ const footerLine = brandFooterText(branding);
8
17
  const timestamp = new Date().toISOString();
9
18
  const score = result.complianceScore;
10
19
  // Calculate findings
11
20
  const activeFindings = result.findings.filter(f => !f.isBaseline && !f.suppressed);
21
+ // Presentation-only: separate proposed (NPRM) findings from current ones, then
22
+ // collapse multiple rules on the same file:line into one grouped entry. The
23
+ // findings themselves are untouched (count is preserved).
24
+ const { current: currentFindings, proposed: proposedRaw } = partitionFindingsByStatus(activeFindings);
25
+ const proposedFindings = sortProposedFindings(proposedRaw);
26
+ const locationGroups = groupFindingsByLocation(currentFindings);
27
+ const groupCounts = countGroupsBySeverity(locationGroups);
12
28
  const acknowledgedFindings = result.findings.filter(f => f.acknowledged && !f.acknowledgment?.expired);
13
29
  const suppressedFindings = result.findings.filter(f => f.suppressed);
14
30
  const baselineFindings = result.findings.filter(f => f.isBaseline);
@@ -50,6 +66,20 @@ export function generateAuditorReport(result, targetPath, options = {}) {
50
66
  font-weight: bold;
51
67
  }
52
68
 
69
+ .brand-logo {
70
+ max-height: 80px;
71
+ max-width: 240px;
72
+ margin: 0 auto 1rem;
73
+ display: block;
74
+ object-fit: contain;
75
+ }
76
+
77
+ .report-header .prepared-by {
78
+ opacity: 0.95;
79
+ font-size: 0.95rem;
80
+ margin-top: 0.5rem;
81
+ }
82
+
53
83
  .report-header h1 {
54
84
  font-size: 2rem;
55
85
  margin-bottom: 0.5rem;
@@ -151,6 +181,83 @@ export function generateAuditorReport(result, targetPath, options = {}) {
151
181
  .severity-high { background: #ea580c; }
152
182
  .severity-medium { background: #ca8a04; }
153
183
  .severity-low { background: #2563eb; }
184
+ .severity-info { background: #6b7280; }
185
+ /* Proposed (NPRM) — deliberately neutral: not a current violation. */
186
+ .severity-proposed { background: #64748b; }
187
+
188
+ .upcoming-subtitle {
189
+ font-size: 0.85rem;
190
+ font-weight: 500;
191
+ color: #64748b;
192
+ }
193
+
194
+ .upcoming-note {
195
+ background: #f1f5f9;
196
+ border-left: 3px solid #64748b;
197
+ padding: 0.75rem 1rem;
198
+ margin: 0.5rem 0 1rem;
199
+ font-size: 0.9rem;
200
+ color: #475569;
201
+ }
202
+
203
+ .upcoming-inline {
204
+ color: #475569;
205
+ font-weight: 600;
206
+ }
207
+
208
+ /* Consolidated multi-rule location entries */
209
+ .findings-count-note {
210
+ color: #4b5563;
211
+ font-size: 0.95rem;
212
+ margin: 0.25rem 0 0.75rem;
213
+ }
214
+
215
+ .finding-group td { vertical-align: top; }
216
+
217
+ .group-location {
218
+ font-weight: 600;
219
+ color: #374151;
220
+ margin-bottom: 0.5rem;
221
+ }
222
+
223
+ .group-location-path {
224
+ font-family: 'SF Mono', Monaco, 'Courier New', monospace;
225
+ font-size: 0.9rem;
226
+ }
227
+
228
+ .group-controls {
229
+ list-style: none;
230
+ margin: 0;
231
+ padding: 0;
232
+ }
233
+
234
+ .group-control {
235
+ display: grid;
236
+ grid-template-columns: 90px 1fr auto;
237
+ gap: 0.75rem;
238
+ align-items: baseline;
239
+ padding: 0.35rem 0;
240
+ border-top: 1px solid #f3f4f6;
241
+ }
242
+
243
+ .group-control:first-child { border-top: none; }
244
+ .group-control-sev { justify-self: start; }
245
+ .group-control-title { color: #1f2937; }
246
+
247
+ .group-control-ref {
248
+ color: #6b7280;
249
+ font-size: 0.85rem;
250
+ text-align: right;
251
+ white-space: normal;
252
+ }
253
+
254
+ @media (max-width: 640px) {
255
+ .group-control {
256
+ grid-template-columns: 1fr;
257
+ gap: 0.15rem;
258
+ }
259
+ .group-control-ref { text-align: left; }
260
+ }
154
261
 
155
262
  .evidence-box {
156
263
  background: #f9fafb;
@@ -176,6 +283,23 @@ export function generateAuditorReport(result, targetPath, options = {}) {
176
283
  color: #6b7280;
177
284
  }
178
285
 
286
+ /* Per-page footer: only visible when printing / exporting to PDF. */
287
+ .brand-page-footer { display: none; }
288
+ @media print {
289
+ .brand-page-footer {
290
+ display: block;
291
+ position: fixed;
292
+ bottom: 0;
293
+ left: 0;
294
+ right: 0;
295
+ text-align: center;
296
+ font-size: 0.7rem;
297
+ color: #6b7280;
298
+ padding: 0.4rem 0;
299
+ }
300
+ @page { margin-bottom: 2.2cm; }
301
+ }
302
+
179
303
  .report-hash {
180
304
  font-family: 'SF Mono', Monaco, 'Courier New', monospace;
181
305
  font-size: 0.75rem;
@@ -223,9 +347,12 @@ export function generateAuditorReport(result, targetPath, options = {}) {
223
347
  <body>
224
348
  <div class="container">
225
349
  <div class="report-header">
226
- <div class="logo-placeholder">VL</div>
350
+ ${brandLogo
351
+ ? `<img src="${brandLogo}" alt="${escapeHtml(preparedBy)} logo" class="brand-logo">`
352
+ : '<div class="logo-placeholder">VL</div>'}
227
353
  <h1>HIPAA Compliance Audit Report</h1>
228
- <div class="subtitle">${organizationName} - ${reportPeriod}</div>
354
+ <div class="subtitle">${escapeHtml(organizationName)} - ${escapeHtml(reportPeriod)}</div>
355
+ ${hasBrand ? `<div class="prepared-by">Prepared by ${escapeHtml(preparedBy)}</div>` : ''}
229
356
  </div>
230
357
 
231
358
  <div class="report-meta">
@@ -255,13 +382,21 @@ export function generateAuditorReport(result, targetPath, options = {}) {
255
382
  ${generateComplianceScoreGauge(score)}
256
383
 
257
384
  <h2>📋 Findings Summary</h2>
385
+ <p class="findings-count-note">
386
+ <strong>${currentFindings.length} current findings</strong> across
387
+ <strong>${locationGroups.length} entries</strong>
388
+ — grouped by file, line &amp; control family. Filters count entries.
389
+ ${proposedFindings.length > 0
390
+ ? `<br><span class="upcoming-inline">+ ${proposedFindings.length} upcoming requirement${proposedFindings.length === 1 ? '' : 's'} (NPRM — proposed rule)</span> — listed separately below.`
391
+ : ''}
392
+ </p>
258
393
  <div class="filters">
259
394
  <div class="filter-buttons">
260
- <button class="filter-btn active" onclick="filterFindings('all')">All (${activeFindings.length})</button>
261
- <button class="filter-btn" onclick="filterFindings('critical')">Critical (${activeFindings.filter(f => f.severity === 'critical').length})</button>
262
- <button class="filter-btn" onclick="filterFindings('high')">High (${activeFindings.filter(f => f.severity === 'high').length})</button>
263
- <button class="filter-btn" onclick="filterFindings('medium')">Medium (${activeFindings.filter(f => f.severity === 'medium').length})</button>
264
- <button class="filter-btn" onclick="filterFindings('low')">Low (${activeFindings.filter(f => f.severity === 'low').length})</button>
395
+ <button class="filter-btn active" onclick="filterFindings('all')">All (${locationGroups.length})</button>
396
+ <button class="filter-btn" onclick="filterFindings('critical')">Critical (${groupCounts.critical})</button>
397
+ <button class="filter-btn" onclick="filterFindings('high')">High (${groupCounts.high})</button>
398
+ <button class="filter-btn" onclick="filterFindings('medium')">Medium (${groupCounts.medium})</button>
399
+ <button class="filter-btn" onclick="filterFindings('low')">Low (${groupCounts.low})</button>
265
400
  </div>
266
401
  </div>
267
402
 
@@ -275,17 +410,66 @@ export function generateAuditorReport(result, targetPath, options = {}) {
275
410
  </tr>
276
411
  </thead>
277
412
  <tbody>
278
- ${activeFindings.map(f => `
279
- <tr class="finding-row" data-severity="${f.severity}">
280
- <td><span class="severity-badge severity-${f.severity}">${f.severity}</span></td>
413
+ ${locationGroups.map(g => {
414
+ const fileLine = `${escapeHtml(g.file)}:${g.line ?? 'N/A'}`;
415
+ if (g.members.length === 1) {
416
+ const f = g.members[0];
417
+ return `
418
+ <tr class="finding-row" data-severity="${g.severity}">
419
+ <td><span class="severity-badge severity-${g.severity}">${g.severity}</span></td>
281
420
  <td>${escapeHtml(f.title)}</td>
282
- <td style="font-family: monospace; font-size: 0.875rem;">${escapeHtml(f.file)}:${f.line || 'N/A'}</td>
283
- <td>${f.hipaaReference || '-'}</td>
284
- </tr>
285
- `).join('')}
421
+ <td style="font-family: monospace; font-size: 0.875rem;">${fileLine}</td>
422
+ <td>${escapeHtml(formatHipaaRef(f.hipaaReference))}</td>
423
+ </tr>`;
424
+ }
425
+ const controls = g.members.map(f => `
426
+ <li class="group-control">
427
+ <span class="severity-badge severity-${f.severity} group-control-sev">${f.severity}</span>
428
+ <span class="group-control-title">${escapeHtml(f.title)}</span>
429
+ <span class="group-control-ref">${escapeHtml(formatHipaaRef(f.hipaaReference))}</span>
430
+ </li>`).join('');
431
+ return `
432
+ <tr class="finding-row finding-group" data-severity="${g.severity}">
433
+ <td><span class="severity-badge severity-${g.severity}">${g.severity}</span></td>
434
+ <td colspan="3">
435
+ <div class="group-location"><span class="group-location-path">${fileLine}</span> · ${g.members.length} controls flagged at this location</div>
436
+ <ul class="group-controls">${controls}
437
+ </ul>
438
+ </td>
439
+ </tr>`;
440
+ }).join('')}
286
441
  </tbody>
287
442
  </table>
288
443
 
444
+ ${proposedFindings.length > 0 ? `
445
+ <h2>🕓 Upcoming Requirements <span class="upcoming-subtitle">(NPRM — proposed rule, not yet in effect)</span></h2>
446
+ <p class="upcoming-note">
447
+ These reference the proposed 2026 HIPAA Security Rule (NPRM). They are
448
+ <strong>not current obligations</strong> and are excluded from the severity counts above.
449
+ They will apply if and when the rule is finalized — treat them as forward-looking guidance.
450
+ </p>
451
+ <table class="findings-table">
452
+ <thead>
453
+ <tr>
454
+ <th>Status</th>
455
+ <th>Requirement</th>
456
+ <th>File</th>
457
+ <th>HIPAA Ref</th>
458
+ </tr>
459
+ </thead>
460
+ <tbody>
461
+ ${proposedFindings.map(f => `
462
+ <tr>
463
+ <td><span class="severity-badge severity-proposed">Proposed</span></td>
464
+ <td>${escapeHtml(f.title)}</td>
465
+ <td style="font-family: monospace; font-size: 0.875rem;">${escapeHtml(f.file)}:${f.line ?? 'N/A'}</td>
466
+ <td>${escapeHtml(formatHipaaRef(f.hipaaReference))}</td>
467
+ </tr>
468
+ `).join('')}
469
+ </tbody>
470
+ </table>
471
+ ` : ''}
472
+
289
473
  ${suppressedFindings.length > 0 ? `
290
474
  <h2>🔕 Suppression Audit Trail</h2>
291
475
  <p>The following findings have been suppressed with documented justifications:</p>
@@ -351,11 +535,14 @@ export function generateAuditorReport(result, targetPath, options = {}) {
351
535
  </div>
352
536
 
353
537
  <div class="report-footer">
354
- <p><strong>VLayer HIPAA Compliance Scanner</strong></p>
538
+ ${hasBrand
539
+ ? `<p><strong>${escapeHtml(footerLine)}</strong></p>`
540
+ : '<p><strong>VLayer HIPAA Compliance Scanner</strong></p>'}
355
541
  <p>Automated compliance scanning for healthcare applications</p>
356
542
  <p style="margin-top: 1rem; font-size: 0.875rem;">Generated: ${new Date(timestamp).toLocaleString()}</p>
357
543
  </div>
358
544
  </div>
545
+ ${hasBrand ? `<div class="brand-page-footer">${escapeHtml(footerLine)}</div>` : ''}
359
546
 
360
547
  <script>
361
548
  function filterFindings(severity) {
@@ -1 +1 @@
1
- {"version":3,"file":"auditor-report.js","sourceRoot":"","sources":["../../src/reporters/auditor-report.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auditor-report.js","sourceRoot":"","sources":["../../src/reporters/auditor-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { Branding, ResolvedBranding } from '../types.js';
2
+ /** Default author shown when no brand name is provided. */
3
+ export declare const DEFAULT_BRAND_NAME = "VLayer Automated Scan";
4
+ /**
5
+ * Resolve branding from CLI flags (which take precedence) and config, validating
6
+ * the logo. This never throws: an invalid or missing logo becomes a warning and
7
+ * is dropped so the scan/report always completes.
8
+ *
9
+ * @param cli Branding from CLI flags (`--brand-name`, `--brand-logo`).
10
+ * @param config Branding from the `branding` block in config.
11
+ * @param baseDir Directory used to resolve a relative logo path (defaults to cwd).
12
+ */
13
+ export declare function resolveBranding(cli: Branding | undefined, config: Branding | undefined, baseDir?: string): ResolvedBranding;
14
+ /**
15
+ * Footer line shown on every page. With branding it reads
16
+ * "Prepared by {name} · Powered by VLayer"; without, just "Powered by VLayer".
17
+ */
18
+ export declare function brandFooterText(branding?: ResolvedBranding): string;
19
+ /** Author label for the cover ("Prepared by ..."), falling back to the default. */
20
+ export declare function brandPreparedBy(branding?: ResolvedBranding): string;
21
+ /**
22
+ * Read the logo and return a base64 data URI for embedding in HTML `<img src>`.
23
+ * Returns null if the logo can't be read (the report still renders).
24
+ */
25
+ export declare function logoDataUri(branding?: ResolvedBranding): string | null;
26
+ /**
27
+ * Whether the logo can be drawn into a PDF via pdfkit. pdfkit's `image()`
28
+ * supports PNG and JPEG but not SVG, so SVG logos are skipped in PDFs.
29
+ */
30
+ export declare function pdfLogoPath(branding?: ResolvedBranding): string | null;
31
+ /** Whether the brand logo is an SVG that PDF output cannot embed. */
32
+ export declare function isSvgLogo(branding?: ResolvedBranding): boolean;
33
+ /**
34
+ * Escape a string for safe interpolation into HTML text/attribute context.
35
+ * Mirrors the escaping used across the existing reporters so brand-supplied
36
+ * values (e.g. names with `<` or `"`) cannot inject markup.
37
+ */
38
+ export declare function escapeHtml(text: string): string;
39
+ //# sourceMappingURL=branding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/reporters/branding.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAU9D,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,QAAQ,GAAG,SAAS,EACzB,MAAM,EAAE,QAAQ,GAAG,SAAS,EAC5B,OAAO,GAAE,MAAsB,GAC9B,gBAAgB,CAsBlB;AA4BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAKnE;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAEnE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAWtE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAItE;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS/C"}
@@ -0,0 +1,124 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import path from 'path';
3
+ /** Logo extensions we support, mapped to their resolved format + MIME type. */
4
+ const SUPPORTED_LOGO_FORMATS = {
5
+ '.png': { format: 'png', mime: 'image/png' },
6
+ '.jpg': { format: 'jpg', mime: 'image/jpeg' },
7
+ '.jpeg': { format: 'jpg', mime: 'image/jpeg' },
8
+ '.svg': { format: 'svg', mime: 'image/svg+xml' },
9
+ };
10
+ /** Default author shown when no brand name is provided. */
11
+ export const DEFAULT_BRAND_NAME = 'VLayer Automated Scan';
12
+ /**
13
+ * Resolve branding from CLI flags (which take precedence) and config, validating
14
+ * the logo. This never throws: an invalid or missing logo becomes a warning and
15
+ * is dropped so the scan/report always completes.
16
+ *
17
+ * @param cli Branding from CLI flags (`--brand-name`, `--brand-logo`).
18
+ * @param config Branding from the `branding` block in config.
19
+ * @param baseDir Directory used to resolve a relative logo path (defaults to cwd).
20
+ */
21
+ export function resolveBranding(cli, config, baseDir = process.cwd()) {
22
+ const warnings = [];
23
+ // CLI flag wins over config for each field independently.
24
+ const rawName = firstNonEmpty(cli?.name, config?.name);
25
+ const rawLogo = firstNonEmpty(cli?.logo, config?.logo);
26
+ const result = { warnings };
27
+ if (rawName) {
28
+ result.name = rawName.trim();
29
+ }
30
+ if (rawLogo) {
31
+ const logo = validateLogo(rawLogo, baseDir, warnings);
32
+ if (logo) {
33
+ result.logoPath = logo.absolutePath;
34
+ result.logoFormat = logo.format;
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ function validateLogo(logo, baseDir, warnings) {
40
+ const absolutePath = path.isAbsolute(logo) ? logo : path.resolve(baseDir, logo);
41
+ const ext = path.extname(absolutePath).toLowerCase();
42
+ const spec = SUPPORTED_LOGO_FORMATS[ext];
43
+ if (!spec) {
44
+ warnings.push(`Brand logo "${logo}" has an unsupported format (${ext || 'no extension'}). ` +
45
+ `Supported: .png, .jpg, .jpeg, .svg. Continuing without a logo.`);
46
+ return null;
47
+ }
48
+ if (!existsSync(absolutePath)) {
49
+ warnings.push(`Brand logo not found at "${absolutePath}". Continuing without a logo.`);
50
+ return null;
51
+ }
52
+ return { absolutePath, format: spec.format };
53
+ }
54
+ /**
55
+ * Footer line shown on every page. With branding it reads
56
+ * "Prepared by {name} · Powered by VLayer"; without, just "Powered by VLayer".
57
+ */
58
+ export function brandFooterText(branding) {
59
+ if (branding?.name) {
60
+ return `Prepared by ${branding.name} · Powered by VLayer`;
61
+ }
62
+ return 'Powered by VLayer';
63
+ }
64
+ /** Author label for the cover ("Prepared by ..."), falling back to the default. */
65
+ export function brandPreparedBy(branding) {
66
+ return branding?.name || DEFAULT_BRAND_NAME;
67
+ }
68
+ /**
69
+ * Read the logo and return a base64 data URI for embedding in HTML `<img src>`.
70
+ * Returns null if the logo can't be read (the report still renders).
71
+ */
72
+ export function logoDataUri(branding) {
73
+ if (!branding?.logoPath || !branding.logoFormat)
74
+ return null;
75
+ const ext = path.extname(branding.logoPath).toLowerCase();
76
+ const mime = SUPPORTED_LOGO_FORMATS[ext]?.mime;
77
+ if (!mime)
78
+ return null;
79
+ try {
80
+ const data = readFileSync(branding.logoPath);
81
+ return `data:${mime};base64,${data.toString('base64')}`;
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ /**
88
+ * Whether the logo can be drawn into a PDF via pdfkit. pdfkit's `image()`
89
+ * supports PNG and JPEG but not SVG, so SVG logos are skipped in PDFs.
90
+ */
91
+ export function pdfLogoPath(branding) {
92
+ if (!branding?.logoPath)
93
+ return null;
94
+ if (branding.logoFormat === 'svg')
95
+ return null;
96
+ return branding.logoPath;
97
+ }
98
+ /** Whether the brand logo is an SVG that PDF output cannot embed. */
99
+ export function isSvgLogo(branding) {
100
+ return branding?.logoFormat === 'svg';
101
+ }
102
+ /**
103
+ * Escape a string for safe interpolation into HTML text/attribute context.
104
+ * Mirrors the escaping used across the existing reporters so brand-supplied
105
+ * values (e.g. names with `<` or `"`) cannot inject markup.
106
+ */
107
+ export function escapeHtml(text) {
108
+ const map = {
109
+ '&': '&amp;',
110
+ '<': '&lt;',
111
+ '>': '&gt;',
112
+ '"': '&quot;',
113
+ "'": '&#039;',
114
+ };
115
+ return text.replace(/[&<>"']/g, m => map[m]);
116
+ }
117
+ function firstNonEmpty(...values) {
118
+ for (const v of values) {
119
+ if (typeof v === 'string' && v.trim().length > 0)
120
+ return v;
121
+ }
122
+ return undefined;
123
+ }
124
+ //# sourceMappingURL=branding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branding.js","sourceRoot":"","sources":["../../src/reporters/branding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Presentation-layer helpers for the HTML/PDF reports.
3
+ *
4
+ * IMPORTANT: this module is purely cosmetic. It never mutates findings, never
5
+ * changes detection, and is NOT used to build the JSON output. It only decides
6
+ * how rows are *grouped and labelled* in the rendered reports. The underlying
7
+ * findings (and the JSON downstream tools depend on) are unchanged — every
8
+ * finding still exists, it is just shown once per file:line when several rules
9
+ * fire on the same location.
10
+ */
11
+ import type { Finding, Severity } from '../types.js';
12
+ /**
13
+ * A finding is "proposed" when its HIPAA reference cites the NPRM (the proposed
14
+ * 2026 Security Rule), not a current obligation. Such findings are still shown,
15
+ * but they must not inflate a group's headline severity.
16
+ */
17
+ export declare function isProposedFinding(f: Finding): boolean;
18
+ /**
19
+ * Split findings into CURRENT obligations and PROPOSED (NPRM) ones. Proposed
20
+ * findings are rendered in their own "Upcoming Requirements" subsection and are
21
+ * excluded from the current-severity Scan Summary, so a proposed rule is never
22
+ * shown as a current red/critical violation.
23
+ */
24
+ export declare function partitionFindingsByStatus(findings: Finding[]): {
25
+ current: Finding[];
26
+ proposed: Finding[];
27
+ };
28
+ /** Stable ordering for the proposed-requirements list: by file, line, title. */
29
+ export declare function sortProposedFindings(findings: Finding[]): Finding[];
30
+ /** One screen entry: findings that share a (file, line, rule category). */
31
+ export interface LocationGroup {
32
+ key: string;
33
+ file: string;
34
+ line: number | undefined;
35
+ /** Rule category shared by all members (the "family"). */
36
+ category: string;
37
+ /** Headline severity — highest among CURRENT (non-proposed) members. */
38
+ severity: Severity;
39
+ /** Members, sorted highest-severity first then by title. */
40
+ members: Finding[];
41
+ }
42
+ /**
43
+ * Group findings by (file + line + rule category). Several rules that flag the
44
+ * same line for the SAME reason (e.g. all PHI-in-logs on route.ts:36) collapse
45
+ * into one entry; unrelated rules on the same line (e.g. MFA + backup) stay as
46
+ * separate rows. A single-member group renders as a normal row.
47
+ *
48
+ * The headline severity reflects only CURRENT requirements — proposed (NPRM)
49
+ * findings never raise it (they stay listed inside with their own badge). When
50
+ * every member is proposed, the highest proposed severity is used.
51
+ *
52
+ * Groups are ordered by group severity (critical first), then file, line, and
53
+ * category. The total member count always equals the input length — nothing is
54
+ * dropped.
55
+ */
56
+ export declare function groupFindingsByLocation(findings: Finding[]): LocationGroup[];
57
+ /** Count location-groups by their group severity (for summary/filter labels). */
58
+ export declare function countGroupsBySeverity(groups: LocationGroup[]): Record<Severity, number>;
59
+ /**
60
+ * Normalize any hipaaReference string to one canonical style:
61
+ * "45 CFR §164.312(c) — Integrity Controls".
62
+ *
63
+ * Handles the three styles currently emitted by the scanners:
64
+ * - already-full "45 CFR §164.312(c) - Integrity Controls"
65
+ * - bare section "§164.502, §164.514"
66
+ * - NPRM-prefixed "NPRM §164.312(d) - Person or Entity Authentication"
67
+ * → kept distinct as a proposed rule:
68
+ * "45 CFR §164.312(d) — Person or Entity Authentication (NPRM — proposed rule)"
69
+ *
70
+ * Multi-section refs (comma-separated) are expanded into each canonical ref,
71
+ * joined with "; ". The original string is never mutated on the finding object.
72
+ */
73
+ export declare function formatHipaaRef(raw: string | undefined | null): string;
74
+ //# sourceMappingURL=finding-presentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-presentation.d.ts","sourceRoot":"","sources":["../../src/reporters/finding-presentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAUrD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG;IAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CAO1G;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAInE;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,EAAE,QAAQ,CAAC;IACnB,4DAA4D;IAC5D,OAAO,EAAE,OAAO,EAAE,CAAC;CACpB;AAWD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAoC5E;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAIvF;AAyDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAOrE"}