verification-layer 0.24.5 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) 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/compliance-score.d.ts +1 -0
  5. package/dist/compliance-score.d.ts.map +1 -1
  6. package/dist/compliance-score.js +1 -1
  7. package/dist/compliance-score.js.map +1 -1
  8. package/dist/exclusions.d.ts +13 -0
  9. package/dist/exclusions.d.ts.map +1 -0
  10. package/dist/exclusions.js +27 -0
  11. package/dist/exclusions.js.map +1 -0
  12. package/dist/reporters/auditor-report.d.ts +2 -1
  13. package/dist/reporters/auditor-report.d.ts.map +1 -1
  14. package/dist/reporters/auditor-report.js +239 -21
  15. package/dist/reporters/auditor-report.js.map +1 -1
  16. package/dist/reporters/branding.d.ts +39 -0
  17. package/dist/reporters/branding.d.ts.map +1 -0
  18. package/dist/reporters/branding.js +124 -0
  19. package/dist/reporters/branding.js.map +1 -0
  20. package/dist/reporters/finding-presentation.d.ts +84 -0
  21. package/dist/reporters/finding-presentation.d.ts.map +1 -0
  22. package/dist/reporters/finding-presentation.js +217 -0
  23. package/dist/reporters/finding-presentation.js.map +1 -0
  24. package/dist/reporters/index.d.ts.map +1 -1
  25. package/dist/reporters/index.js +34 -0
  26. package/dist/reporters/index.js.map +1 -1
  27. package/dist/reporters/scan-pdf-report.d.ts +23 -0
  28. package/dist/reporters/scan-pdf-report.d.ts.map +1 -0
  29. package/dist/reporters/scan-pdf-report.js +325 -0
  30. package/dist/reporters/scan-pdf-report.js.map +1 -0
  31. package/dist/scan.d.ts +11 -0
  32. package/dist/scan.d.ts.map +1 -1
  33. package/dist/scan.js +46 -1
  34. package/dist/scan.js.map +1 -1
  35. package/dist/scanners/authentication/index.d.ts.map +1 -1
  36. package/dist/scanners/authentication/index.js +30 -23
  37. package/dist/scanners/authentication/index.js.map +1 -1
  38. package/dist/scanners/credentials/index.d.ts.map +1 -1
  39. package/dist/scanners/credentials/index.js +7 -2
  40. package/dist/scanners/credentials/index.js.map +1 -1
  41. package/dist/scanners/credentials/index.test.js +3 -3
  42. package/dist/scanners/credentials/patterns.d.ts.map +1 -1
  43. package/dist/scanners/credentials/patterns.js +3 -3
  44. package/dist/scanners/credentials/patterns.js.map +1 -1
  45. package/dist/scanners/hipaa2026/index.d.ts.map +1 -1
  46. package/dist/scanners/hipaa2026/index.js +7 -19
  47. package/dist/scanners/hipaa2026/index.js.map +1 -1
  48. package/dist/scanners/hipaa2026/index.test.js +2 -2
  49. package/dist/scanners/hipaa2026/patterns.d.ts.map +1 -1
  50. package/dist/scanners/hipaa2026/patterns.js +18 -5
  51. package/dist/scanners/hipaa2026/patterns.js.map +1 -1
  52. package/dist/scanners/operational/index.d.ts.map +1 -1
  53. package/dist/scanners/operational/index.js +24 -24
  54. package/dist/scanners/operational/index.js.map +1 -1
  55. package/dist/scanners/rbac/index.test.js +3 -0
  56. package/dist/scanners/rbac/index.test.js.map +1 -1
  57. package/dist/scanners/rbac/patterns.d.ts.map +1 -1
  58. package/dist/scanners/rbac/patterns.js +10 -3
  59. package/dist/scanners/rbac/patterns.js.map +1 -1
  60. package/dist/scanners/utils.d.ts +44 -0
  61. package/dist/scanners/utils.d.ts.map +1 -0
  62. package/dist/scanners/utils.js +77 -0
  63. package/dist/scanners/utils.js.map +1 -0
  64. package/dist/types.d.ts +38 -1
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +2 -2
  67. package/dist/scan-code.d.ts +0 -12
  68. package/dist/scan-code.d.ts.map +0 -1
  69. package/dist/scan-code.js +0 -34
  70. package/dist/scan-code.js.map +0 -1
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Default exclusion globs for vlayer's OWN generated output artifacts.
3
+ *
4
+ * Without these, a second scan re-reads the report/baseline files vlayer wrote
5
+ * on a previous run and flags their text as if it were source code (e.g. the
6
+ * literal "Weak cryptography: DES encryption" inside a JSON report). On the demo
7
+ * this produced 62/95 false positives. These globs are added to the file-discovery
8
+ * ignore list by default and can be disabled with `--include-own-artifacts`.
9
+ *
10
+ * Patterns are prefixed with a globstar so they match at any depth in the tree.
11
+ */
12
+ export declare const DEFAULT_VLAYER_OUTPUT_EXCLUDES: readonly string[];
13
+ //# sourceMappingURL=exclusions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclusions.d.ts","sourceRoot":"","sources":["../src/exclusions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,EAAE,SAAS,MAAM,EAc3D,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Default exclusion globs for vlayer's OWN generated output artifacts.
3
+ *
4
+ * Without these, a second scan re-reads the report/baseline files vlayer wrote
5
+ * on a previous run and flags their text as if it were source code (e.g. the
6
+ * literal "Weak cryptography: DES encryption" inside a JSON report). On the demo
7
+ * this produced 62/95 false positives. These globs are added to the file-discovery
8
+ * ignore list by default and can be disabled with `--include-own-artifacts`.
9
+ *
10
+ * Patterns are prefixed with a globstar so they match at any depth in the tree.
11
+ */
12
+ export const DEFAULT_VLAYER_OUTPUT_EXCLUDES = [
13
+ // Standard report outputs (`vlayer scan/report -o`)
14
+ '**/vlayer-report.json',
15
+ '**/vlayer-report.html',
16
+ '**/vlayer-report.md',
17
+ '**/vlayer-report.pdf',
18
+ // Auditor report outputs (`vlayer report` / `vlayer audit --generate-report`)
19
+ '**/vlayer-audit-report.html',
20
+ '**/vlayer-audit-report.pdf',
21
+ // Baseline + metadata vlayer writes into the project
22
+ '**/.vlayer-baseline.json',
23
+ '**/.vlayer/**',
24
+ // Curated sample reports directory (publishable report artifacts)
25
+ '**/samples/**',
26
+ ];
27
+ //# sourceMappingURL=exclusions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclusions.js","sourceRoot":"","sources":["../src/exclusions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAsB;IAC/D,oDAAoD;IACpD,uBAAuB;IACvB,uBAAuB;IACvB,qBAAqB;IACrB,sBAAsB;IACtB,8EAA8E;IAC9E,6BAA6B;IAC7B,4BAA4B;IAC5B,qDAAqD;IACrD,0BAA0B;IAC1B,eAAe;IACf,kEAAkE;IAClE,eAAe;CAChB,CAAC"}
@@ -1,9 +1,10 @@
1
- import type { ScanResult } from '../types.js';
1
+ import type { ScanResult, ResolvedBranding } from '../types.js';
2
2
  interface AuditorReportOptions {
3
3
  organizationName?: string;
4
4
  reportPeriod?: string;
5
5
  auditorName?: string;
6
6
  includeBaseline?: boolean;
7
+ branding?: ResolvedBranding;
7
8
  }
8
9
  /**
9
10
  * Generate auditor-ready HTML report with compliance score and SHA256 hash
@@ -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":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAahE,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,CAsmBhC"}
@@ -1,17 +1,59 @@
1
1
  import { createHash } from 'crypto';
2
+ import { resolve, basename } from 'path';
2
3
  import { generateComplianceScoreGauge, generateExecutiveSummary, generateEnhancedCSS } from './enhanced-html.js';
4
+ import { generateRecommendations } from '../compliance-score.js';
5
+ import { brandFooterText, brandPreparedBy, logoDataUri } from './branding.js';
6
+ import { groupFindingsByLocation, countFindingsBySeverity, formatHipaaRef, partitionFindingsByStatus, sortProposedFindings, toRelativeDisplayPath, } from './finding-presentation.js';
3
7
  /**
4
8
  * Generate auditor-ready HTML report with compliance score and SHA256 hash
5
9
  */
6
10
  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;
11
+ const { organizationName = 'Organization', reportPeriod = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' }), auditorName = 'VLayer Automated Scan', includeBaseline = false, branding, } = options;
12
+ // White-label branding. When a brand is supplied the cover shows the brand
13
+ // logo + "Prepared by {brand}", and a footer line repeats on every printed
14
+ // page. With no branding the report renders exactly as before (default VLayer).
15
+ const brandLogo = logoDataUri(branding);
16
+ const hasBrand = Boolean(branding?.name || brandLogo);
17
+ const preparedBy = brandPreparedBy(branding);
18
+ const footerLine = brandFooterText(branding);
8
19
  const timestamp = new Date().toISOString();
9
20
  const score = result.complianceScore;
10
21
  // Calculate findings
11
22
  const activeFindings = result.findings.filter(f => !f.isBaseline && !f.suppressed);
23
+ // Presentation-only: separate proposed (NPRM) findings from current ones, then
24
+ // collapse multiple rules on the same file:line into one grouped entry. The
25
+ // findings themselves are untouched (count is preserved).
26
+ const { current: currentFindings, proposed: proposedRaw } = partitionFindingsByStatus(activeFindings);
27
+ const proposedFindings = sortProposedFindings(proposedRaw);
28
+ const locationGroups = groupFindingsByLocation(currentFindings);
29
+ // Per-severity counts come from the RAW current findings (proposed excluded),
30
+ // so the summary cards, recommendations, and filter chips all show the SAME
31
+ // numbers. Location-grouping below is purely a display device for the list.
32
+ const severityCounts = countFindingsBySeverity(currentFindings);
12
33
  const acknowledgedFindings = result.findings.filter(f => f.acknowledged && !f.acknowledgment?.expired);
13
34
  const suppressedFindings = result.findings.filter(f => f.suppressed);
14
35
  const baselineFindings = result.findings.filter(f => f.isBaseline);
36
+ // Display-corrected score: the stored breakdown counts ALL active findings
37
+ // (it includes proposed/NPRM), which would show e.g. "8 critical" up top while
38
+ // the current-obligation list shows 7. Re-key the breakdown to the current
39
+ // findings so every rendered number reconciles, and regenerate the
40
+ // recommendation text from those same numbers. The compliance score value,
41
+ // grade and penalties are left untouched — only the displayed counts change.
42
+ const absTargetRoot = resolve(targetPath);
43
+ const displayTarget = basename(absTargetRoot) || targetPath;
44
+ const displayBreakdown = {
45
+ ...score.breakdown,
46
+ critical: severityCounts.critical,
47
+ high: severityCounts.high,
48
+ medium: severityCounts.medium,
49
+ low: severityCounts.low,
50
+ total: currentFindings.length,
51
+ };
52
+ const displayScore = {
53
+ ...score,
54
+ breakdown: displayBreakdown,
55
+ recommendations: generateRecommendations(displayBreakdown, score.score),
56
+ };
15
57
  const html = `<!DOCTYPE html>
16
58
  <html lang="en">
17
59
  <head>
@@ -50,6 +92,20 @@ export function generateAuditorReport(result, targetPath, options = {}) {
50
92
  font-weight: bold;
51
93
  }
52
94
 
95
+ .brand-logo {
96
+ max-height: 80px;
97
+ max-width: 240px;
98
+ margin: 0 auto 1rem;
99
+ display: block;
100
+ object-fit: contain;
101
+ }
102
+
103
+ .report-header .prepared-by {
104
+ opacity: 0.95;
105
+ font-size: 0.95rem;
106
+ margin-top: 0.5rem;
107
+ }
108
+
53
109
  .report-header h1 {
54
110
  font-size: 2rem;
55
111
  margin-bottom: 0.5rem;
@@ -151,6 +207,83 @@ export function generateAuditorReport(result, targetPath, options = {}) {
151
207
  .severity-high { background: #ea580c; }
152
208
  .severity-medium { background: #ca8a04; }
153
209
  .severity-low { background: #2563eb; }
210
+ .severity-info { background: #6b7280; }
211
+ /* Proposed (NPRM) — deliberately neutral: not a current violation. */
212
+ .severity-proposed { background: #64748b; }
213
+
214
+ .upcoming-subtitle {
215
+ font-size: 0.85rem;
216
+ font-weight: 500;
217
+ color: #64748b;
218
+ }
219
+
220
+ .upcoming-note {
221
+ background: #f1f5f9;
222
+ border-left: 3px solid #64748b;
223
+ padding: 0.75rem 1rem;
224
+ margin: 0.5rem 0 1rem;
225
+ font-size: 0.9rem;
226
+ color: #475569;
227
+ }
228
+
229
+ .upcoming-inline {
230
+ color: #475569;
231
+ font-weight: 600;
232
+ }
233
+
234
+ /* Consolidated multi-rule location entries */
235
+ .findings-count-note {
236
+ color: #4b5563;
237
+ font-size: 0.95rem;
238
+ margin: 0.25rem 0 0.75rem;
239
+ }
240
+
241
+ .finding-group td { vertical-align: top; }
242
+
243
+ .group-location {
244
+ font-weight: 600;
245
+ color: #374151;
246
+ margin-bottom: 0.5rem;
247
+ }
248
+
249
+ .group-location-path {
250
+ font-family: 'SF Mono', Monaco, 'Courier New', monospace;
251
+ font-size: 0.9rem;
252
+ }
253
+
254
+ .group-controls {
255
+ list-style: none;
256
+ margin: 0;
257
+ padding: 0;
258
+ }
259
+
260
+ .group-control {
261
+ display: grid;
262
+ grid-template-columns: 90px 1fr auto;
263
+ gap: 0.75rem;
264
+ align-items: baseline;
265
+ padding: 0.35rem 0;
266
+ border-top: 1px solid #f3f4f6;
267
+ }
268
+
269
+ .group-control:first-child { border-top: none; }
270
+ .group-control-sev { justify-self: start; }
271
+ .group-control-title { color: #1f2937; }
272
+
273
+ .group-control-ref {
274
+ color: #6b7280;
275
+ font-size: 0.85rem;
276
+ text-align: right;
277
+ white-space: normal;
278
+ }
279
+
280
+ @media (max-width: 640px) {
281
+ .group-control {
282
+ grid-template-columns: 1fr;
283
+ gap: 0.15rem;
284
+ }
285
+ .group-control-ref { text-align: left; }
286
+ }
154
287
 
155
288
  .evidence-box {
156
289
  background: #f9fafb;
@@ -176,6 +309,23 @@ export function generateAuditorReport(result, targetPath, options = {}) {
176
309
  color: #6b7280;
177
310
  }
178
311
 
312
+ /* Per-page footer: only visible when printing / exporting to PDF. */
313
+ .brand-page-footer { display: none; }
314
+ @media print {
315
+ .brand-page-footer {
316
+ display: block;
317
+ position: fixed;
318
+ bottom: 0;
319
+ left: 0;
320
+ right: 0;
321
+ text-align: center;
322
+ font-size: 0.7rem;
323
+ color: #6b7280;
324
+ padding: 0.4rem 0;
325
+ }
326
+ @page { margin-bottom: 2.2cm; }
327
+ }
328
+
179
329
  .report-hash {
180
330
  font-family: 'SF Mono', Monaco, 'Courier New', monospace;
181
331
  font-size: 0.75rem;
@@ -223,9 +373,12 @@ export function generateAuditorReport(result, targetPath, options = {}) {
223
373
  <body>
224
374
  <div class="container">
225
375
  <div class="report-header">
226
- <div class="logo-placeholder">VL</div>
376
+ ${brandLogo
377
+ ? `<img src="${brandLogo}" alt="${escapeHtml(preparedBy)} logo" class="brand-logo">`
378
+ : '<div class="logo-placeholder">VL</div>'}
227
379
  <h1>HIPAA Compliance Audit Report</h1>
228
- <div class="subtitle">${organizationName} - ${reportPeriod}</div>
380
+ <div class="subtitle">${escapeHtml(organizationName)} - ${escapeHtml(reportPeriod)}</div>
381
+ ${hasBrand ? `<div class="prepared-by">Prepared by ${escapeHtml(preparedBy)}</div>` : ''}
229
382
  </div>
230
383
 
231
384
  <div class="report-meta">
@@ -240,7 +393,7 @@ export function generateAuditorReport(result, targetPath, options = {}) {
240
393
  </div>
241
394
  <div class="meta-item">
242
395
  <div class="meta-label">Target Path</div>
243
- <div class="meta-value">${targetPath}</div>
396
+ <div class="meta-value">${escapeHtml(displayTarget)}</div>
244
397
  </div>
245
398
  <div class="meta-item">
246
399
  <div class="meta-label">Files Scanned</div>
@@ -250,18 +403,27 @@ export function generateAuditorReport(result, targetPath, options = {}) {
250
403
  </div>
251
404
 
252
405
  <div class="content">
253
- ${generateExecutiveSummary(score, result.scannedFiles, result.scanDuration)}
406
+ ${generateExecutiveSummary(displayScore, result.scannedFiles, result.scanDuration)}
254
407
 
255
- ${generateComplianceScoreGauge(score)}
408
+ ${generateComplianceScoreGauge(displayScore)}
256
409
 
257
410
  <h2>📋 Findings Summary</h2>
411
+ <p class="findings-count-note">
412
+ <strong>${currentFindings.length} findings grouped into ${locationGroups.length} location${locationGroups.length === 1 ? '' : 's'}</strong>
413
+ — multiple controls flagged at the same file &amp; line are shown together,
414
+ but each is counted individually. The chip counts below match the
415
+ Executive Summary.
416
+ ${proposedFindings.length > 0
417
+ ? `<br><span class="upcoming-inline">+ ${proposedFindings.length} upcoming requirement${proposedFindings.length === 1 ? '' : 's'} (NPRM — proposed rule)</span> — listed separately below, excluded from the counts above.`
418
+ : ''}
419
+ </p>
258
420
  <div class="filters">
259
421
  <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>
422
+ <button class="filter-btn active" onclick="filterFindings('all')">All (${currentFindings.length})</button>
423
+ <button class="filter-btn" onclick="filterFindings('critical')">Critical (${severityCounts.critical})</button>
424
+ <button class="filter-btn" onclick="filterFindings('high')">High (${severityCounts.high})</button>
425
+ <button class="filter-btn" onclick="filterFindings('medium')">Medium (${severityCounts.medium})</button>
426
+ <button class="filter-btn" onclick="filterFindings('low')">Low (${severityCounts.low})</button>
265
427
  </div>
266
428
  </div>
267
429
 
@@ -275,17 +437,69 @@ export function generateAuditorReport(result, targetPath, options = {}) {
275
437
  </tr>
276
438
  </thead>
277
439
  <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>
440
+ ${locationGroups.map(g => {
441
+ const fileLine = `${escapeHtml(toRelativeDisplayPath(g.file, absTargetRoot))}:${g.line ?? 'N/A'}`;
442
+ // A row matches a filter chip if ANY member has that severity, so the
443
+ // chip count (raw findings) and the rows it reveals stay consistent.
444
+ const memberSeverities = [...new Set(g.members.map(m => m.severity))].join(' ');
445
+ if (g.members.length === 1) {
446
+ const f = g.members[0];
447
+ return `
448
+ <tr class="finding-row" data-severity="${g.severity}" data-severities="${memberSeverities}">
449
+ <td><span class="severity-badge severity-${g.severity}">${g.severity}</span></td>
281
450
  <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('')}
451
+ <td style="font-family: monospace; font-size: 0.875rem;">${fileLine}</td>
452
+ <td>${escapeHtml(formatHipaaRef(f.hipaaReference))}</td>
453
+ </tr>`;
454
+ }
455
+ const controls = g.members.map(f => `
456
+ <li class="group-control">
457
+ <span class="severity-badge severity-${f.severity} group-control-sev">${f.severity}</span>
458
+ <span class="group-control-title">${escapeHtml(f.title)}</span>
459
+ <span class="group-control-ref">${escapeHtml(formatHipaaRef(f.hipaaReference))}</span>
460
+ </li>`).join('');
461
+ return `
462
+ <tr class="finding-row finding-group" data-severity="${g.severity}" data-severities="${memberSeverities}">
463
+ <td><span class="severity-badge severity-${g.severity}">${g.severity}</span></td>
464
+ <td colspan="3">
465
+ <div class="group-location"><span class="group-location-path">${fileLine}</span> · ${g.members.length} controls flagged at this location</div>
466
+ <ul class="group-controls">${controls}
467
+ </ul>
468
+ </td>
469
+ </tr>`;
470
+ }).join('')}
286
471
  </tbody>
287
472
  </table>
288
473
 
474
+ ${proposedFindings.length > 0 ? `
475
+ <h2>🕓 Upcoming Requirements <span class="upcoming-subtitle">(NPRM — proposed rule, not yet in effect)</span></h2>
476
+ <p class="upcoming-note">
477
+ These reference the proposed 2026 HIPAA Security Rule (NPRM). They are
478
+ <strong>not current obligations</strong> and are excluded from the severity counts above.
479
+ They will apply if and when the rule is finalized — treat them as forward-looking guidance.
480
+ </p>
481
+ <table class="findings-table">
482
+ <thead>
483
+ <tr>
484
+ <th>Status</th>
485
+ <th>Requirement</th>
486
+ <th>File</th>
487
+ <th>HIPAA Ref</th>
488
+ </tr>
489
+ </thead>
490
+ <tbody>
491
+ ${proposedFindings.map(f => `
492
+ <tr>
493
+ <td><span class="severity-badge severity-proposed">Proposed</span></td>
494
+ <td>${escapeHtml(f.title)}</td>
495
+ <td style="font-family: monospace; font-size: 0.875rem;">${escapeHtml(toRelativeDisplayPath(f.file, absTargetRoot))}:${f.line ?? 'N/A'}</td>
496
+ <td>${escapeHtml(formatHipaaRef(f.hipaaReference))}</td>
497
+ </tr>
498
+ `).join('')}
499
+ </tbody>
500
+ </table>
501
+ ` : ''}
502
+
289
503
  ${suppressedFindings.length > 0 ? `
290
504
  <h2>🔕 Suppression Audit Trail</h2>
291
505
  <p>The following findings have been suppressed with documented justifications:</p>
@@ -302,7 +516,7 @@ export function generateAuditorReport(result, targetPath, options = {}) {
302
516
  <tr>
303
517
  <td>${escapeHtml(f.title)}</td>
304
518
  <td>${f.suppression?.reason || 'No reason provided'}</td>
305
- <td style="font-family: monospace; font-size: 0.875rem;">${escapeHtml(f.file)}:${f.line || 'N/A'}</td>
519
+ <td style="font-family: monospace; font-size: 0.875rem;">${escapeHtml(toRelativeDisplayPath(f.file, absTargetRoot))}:${f.line || 'N/A'}</td>
306
520
  </tr>
307
521
  `).join('')}
308
522
  </tbody>
@@ -351,11 +565,14 @@ export function generateAuditorReport(result, targetPath, options = {}) {
351
565
  </div>
352
566
 
353
567
  <div class="report-footer">
354
- <p><strong>VLayer HIPAA Compliance Scanner</strong></p>
568
+ ${hasBrand
569
+ ? `<p><strong>${escapeHtml(footerLine)}</strong></p>`
570
+ : '<p><strong>VLayer HIPAA Compliance Scanner</strong></p>'}
355
571
  <p>Automated compliance scanning for healthcare applications</p>
356
572
  <p style="margin-top: 1rem; font-size: 0.875rem;">Generated: ${new Date(timestamp).toLocaleString()}</p>
357
573
  </div>
358
574
  </div>
575
+ ${hasBrand ? `<div class="brand-page-footer">${escapeHtml(footerLine)}</div>` : ''}
359
576
 
360
577
  <script>
361
578
  function filterFindings(severity) {
@@ -366,7 +583,8 @@ export function generateAuditorReport(result, targetPath, options = {}) {
366
583
  event.target.classList.add('active');
367
584
 
368
585
  rows.forEach(row => {
369
- if (severity === 'all' || row.dataset.severity === severity) {
586
+ const severities = (row.dataset.severities || row.dataset.severity || '').split(' ');
587
+ if (severity === 'all' || severities.indexOf(severity) !== -1) {
370
588
  row.style.display = '';
371
589
  } else {
372
590
  row.style.display = 'none';
@@ -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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"}