hackmyagent 0.3.2 → 0.3.3

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/dist/index.js CHANGED
@@ -401,116 +401,697 @@ function generateHtmlReport(result) {
401
401
  'Passing': '#eab308',
402
402
  'Needs Improvement': '#f97316',
403
403
  'Failing': '#ef4444',
404
- }[result.rating];
405
- const categoryRows = result.categories.map(cat => {
406
- const statusIcon = cat.failed === 0 ? '' : cat.passed > 0 ? '⚠️' : '❌';
407
- const barWidth = cat.compliance;
404
+ }[result.rating] || '#94a3b8';
405
+ const ratingBg = {
406
+ 'Certified': 'rgba(34, 197, 94, 0.15)',
407
+ 'Compliant': 'rgba(34, 197, 94, 0.15)',
408
+ 'Passing': 'rgba(234, 179, 8, 0.15)',
409
+ 'Needs Improvement': 'rgba(249, 115, 22, 0.15)',
410
+ 'Failing': 'rgba(239, 68, 68, 0.15)',
411
+ }[result.rating] || 'rgba(148, 163, 184, 0.15)';
412
+ // Generate donut chart SVG
413
+ const donutRadius = 70;
414
+ const donutStroke = 14;
415
+ const donutCircumference = 2 * Math.PI * donutRadius;
416
+ const donutOffset = donutCircumference * (1 - result.compliance / 100);
417
+ const complianceColor = result.compliance >= 90 ? '#22c55e' : result.compliance >= 70 ? '#eab308' : '#ef4444';
418
+ // Generate radar chart data points
419
+ const radarCategories = result.categories.slice(0, 10); // Max 10 for radar
420
+ const radarPoints = [];
421
+ const radarLabels = [];
422
+ const radarCenter = 120;
423
+ const radarRadius = 90;
424
+ // Category name abbreviations for radar chart labels
425
+ const categoryAbbreviations = {
426
+ 'Identity & Provenance': 'Identity',
427
+ 'Capability & Authorization': 'Capability',
428
+ 'Input Security': 'Input',
429
+ 'Output Security': 'Output',
430
+ 'Credential Protection': 'Credentials',
431
+ 'Supply Chain Integrity': 'Supply Chain',
432
+ 'Agent-to-Agent Security': 'A2A Security',
433
+ 'Memory & Context Integrity': 'Memory',
434
+ 'Operational Security': 'Operations',
435
+ 'Monitoring & Response': 'Monitoring',
436
+ };
437
+ radarCategories.forEach((cat, i) => {
438
+ const angle = (Math.PI * 2 * i) / radarCategories.length - Math.PI / 2;
439
+ // Use minimum 5% so 0% categories still show on the chart edge (not at center)
440
+ const value = Math.max(0.05, cat.compliance / 100);
441
+ const x = radarCenter + Math.cos(angle) * radarRadius * value;
442
+ const y = radarCenter + Math.sin(angle) * radarRadius * value;
443
+ radarPoints.push(`${x},${y}`);
444
+ // Label position (slightly outside)
445
+ const labelX = radarCenter + Math.cos(angle) * (radarRadius + 20);
446
+ const labelY = radarCenter + Math.sin(angle) * (radarRadius + 20);
447
+ const shortName = categoryAbbreviations[cat.category] || cat.category.split(' ')[0];
448
+ radarLabels.push(`<text x="${labelX}" y="${labelY}" text-anchor="middle" dominant-baseline="middle" fill="#94a3b8" font-size="10" font-weight="500">${escapeHtml(shortName)}</text>`);
449
+ });
450
+ // Generate radar grid lines
451
+ const radarGrid = [0.25, 0.5, 0.75, 1].map(scale => {
452
+ const points = radarCategories.map((_, i) => {
453
+ const angle = (Math.PI * 2 * i) / radarCategories.length - Math.PI / 2;
454
+ const x = radarCenter + Math.cos(angle) * radarRadius * scale;
455
+ const y = radarCenter + Math.sin(angle) * radarRadius * scale;
456
+ return `${x},${y}`;
457
+ }).join(' ');
458
+ return `<polygon points="${points}" fill="none" stroke="#334155" stroke-width="1"/>`;
459
+ }).join('');
460
+ // Radar axis lines
461
+ const radarAxes = radarCategories.map((_, i) => {
462
+ const angle = (Math.PI * 2 * i) / radarCategories.length - Math.PI / 2;
463
+ const x = radarCenter + Math.cos(angle) * radarRadius;
464
+ const y = radarCenter + Math.sin(angle) * radarRadius;
465
+ return `<line x1="${radarCenter}" y1="${radarCenter}" x2="${x}" y2="${y}" stroke="#334155" stroke-width="1"/>`;
466
+ }).join('');
467
+ // Collect all controls for statistics
468
+ const allControls = result.categories.flatMap(cat => cat.controls);
469
+ const failedControls = allControls.filter(ctrl => ctrl.status === 'failed');
470
+ const passedControls = allControls.filter(ctrl => ctrl.status === 'passed');
471
+ const unverifiedControls = allControls.filter(ctrl => ctrl.status === 'unverified');
472
+ // Level breakdown stats
473
+ const levelStats = {
474
+ L1: { passed: 0, failed: 0, total: 0 },
475
+ L2: { passed: 0, failed: 0, total: 0 },
476
+ L3: { passed: 0, failed: 0, total: 0 },
477
+ };
478
+ allControls.forEach(ctrl => {
479
+ const lvl = ctrl.level;
480
+ if (levelStats[lvl]) {
481
+ levelStats[lvl].total++;
482
+ if (ctrl.status === 'passed')
483
+ levelStats[lvl].passed++;
484
+ if (ctrl.status === 'failed')
485
+ levelStats[lvl].failed++;
486
+ }
487
+ });
488
+ // Find worst category
489
+ const worstCategory = result.categories
490
+ .filter(cat => cat.passed + cat.failed > 0)
491
+ .sort((a, b) => a.compliance - b.compliance)[0];
492
+ // Security grade based on compliance
493
+ const getGrade = (pct) => {
494
+ if (pct >= 95)
495
+ return { letter: 'A+', color: '#22c55e' };
496
+ if (pct >= 90)
497
+ return { letter: 'A', color: '#22c55e' };
498
+ if (pct >= 85)
499
+ return { letter: 'B+', color: '#84cc16' };
500
+ if (pct >= 80)
501
+ return { letter: 'B', color: '#84cc16' };
502
+ if (pct >= 75)
503
+ return { letter: 'C+', color: '#eab308' };
504
+ if (pct >= 70)
505
+ return { letter: 'C', color: '#eab308' };
506
+ if (pct >= 60)
507
+ return { letter: 'D', color: '#f97316' };
508
+ return { letter: 'F', color: '#ef4444' };
509
+ };
510
+ const grade = getGrade(result.compliance);
511
+ // Generate executive summary items
512
+ const executiveSummary = failedControls.length === 0
513
+ ? '<div class="exec-item success"><span class="exec-icon">✓</span><span>All controls passing at this level</span></div>'
514
+ : failedControls.slice(0, 5).map(ctrl => `<div class="exec-item critical"><span class="exec-icon">!</span><span><strong>${ctrl.controlId}</strong>: ${escapeHtml(ctrl.name)}</span></div>`).join('') + (failedControls.length > 5 ? `<div class="exec-item warning"><span class="exec-icon">+</span><span>${failedControls.length - 5} more issues not shown</span></div>` : '');
515
+ // SVG icons for professional look (no emojis)
516
+ const icons = {
517
+ check: '<svg class="icon icon-check" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
518
+ x: '<svg class="icon icon-x" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>',
519
+ warning: '<svg class="icon icon-warning" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>',
520
+ circle: '<svg class="icon icon-circle" viewBox="0 0 20 20" fill="currentColor"><circle cx="10" cy="10" r="4"/></svg>',
521
+ shield: '<svg class="icon icon-shield" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>',
522
+ print: '<svg class="icon icon-print" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 4v3H4a2 2 0 00-2 2v3a2 2 0 002 2h1v2a2 2 0 002 2h6a2 2 0 002-2v-2h1a2 2 0 002-2V9a2 2 0 00-2-2h-1V4a2 2 0 00-2-2H7a2 2 0 00-2 2zm8 0H7v3h6V4zm0 8H7v4h6v-4z" clip-rule="evenodd"/></svg>',
523
+ };
524
+ // Category rows with collapsible sections
525
+ const categoryRows = result.categories.map((cat, catIndex) => {
526
+ const statusIcon = cat.failed === 0 ? icons.check : cat.passed > 0 ? icons.warning : icons.x;
527
+ const statusClass = cat.failed === 0 ? 'status-pass' : cat.passed > 0 ? 'status-warn' : 'status-fail';
408
528
  const barColor = cat.compliance >= 90 ? '#22c55e' : cat.compliance >= 70 ? '#eab308' : '#ef4444';
409
529
  const controlRows = cat.controls.map(ctrl => {
410
- const statusEmoji = ctrl.status === 'passed' ? '✅' : ctrl.status === 'failed' ? '❌' : '⚪';
530
+ const statusSvg = ctrl.status === 'passed' ? icons.check : ctrl.status === 'failed' ? icons.x : icons.circle;
531
+ const ctrlStatusClass = ctrl.status === 'passed' ? 'status-pass' : ctrl.status === 'failed' ? 'status-fail' : 'status-unverified';
411
532
  const findingsList = ctrl.findings.length > 0
412
533
  ? `<ul class="findings">${ctrl.findings.map(f => `<li>${escapeHtml(f)}</li>`).join('')}</ul>`
413
534
  : '';
414
535
  const remediation = ctrl.remediation
415
- ? `<div class="remediation"><strong>Fix:</strong> ${escapeHtml(ctrl.remediation)}</div>`
536
+ ? `<div class="remediation"><strong>Remediation:</strong> ${escapeHtml(ctrl.remediation)}</div>`
416
537
  : '';
417
538
  return `
418
539
  <tr class="control-row ${ctrl.status}">
419
- <td>${statusEmoji}</td>
420
- <td>${ctrl.controlId}</td>
421
- <td>${escapeHtml(ctrl.name)}</td>
422
- <td><span class="level-badge level-${ctrl.level.toLowerCase()}">${ctrl.level}</span></td>
423
- <td>${findingsList}${remediation}</td>
540
+ <td class="status-cell"><span class="${ctrlStatusClass}">${statusSvg}</span></td>
541
+ <td class="id-cell"><code>${ctrl.controlId}</code></td>
542
+ <td class="name-cell">${escapeHtml(ctrl.name)}</td>
543
+ <td class="level-cell"><span class="level-badge level-${ctrl.level.toLowerCase()}">${ctrl.level}</span></td>
544
+ <td class="details-cell">${findingsList}${remediation}</td>
424
545
  </tr>`;
425
546
  }).join('');
426
547
  return `
427
- <div class="category">
428
- <div class="category-header">
429
- <span class="category-icon">${statusIcon}</span>
548
+ <div class="category" id="cat-${catIndex}">
549
+ <div class="category-header" onclick="toggleCategory(${catIndex})">
550
+ <span class="category-icon ${statusClass}">${statusIcon}</span>
430
551
  <span class="category-name">${escapeHtml(cat.category)}</span>
431
- <span class="category-score">${cat.passed}/${cat.passed + cat.failed} (${cat.compliance}%)</span>
552
+ <div class="category-meta">
553
+ <span class="category-score">${cat.passed}/${cat.passed + cat.failed}</span>
554
+ <div class="mini-bar"><div class="mini-fill" style="width: ${cat.compliance}%; background: ${barColor};"></div></div>
555
+ <span class="category-percent">${cat.compliance}%</span>
556
+ <span class="chevron">▼</span>
557
+ </div>
432
558
  </div>
433
- <div class="progress-bar">
434
- <div class="progress-fill" style="width: ${barWidth}%; background: ${barColor};"></div>
559
+ <div class="category-content">
560
+ <table class="controls-table">
561
+ <thead><tr><th></th><th>Control ID</th><th>Control Name</th><th>Level</th><th>Details</th></tr></thead>
562
+ <tbody>${controlRows}</tbody>
563
+ </table>
435
564
  </div>
436
- <table class="controls-table">
437
- <thead><tr><th></th><th>ID</th><th>Control</th><th>Level</th><th>Details</th></tr></thead>
438
- <tbody>${controlRows}</tbody>
439
- </table>
440
565
  </div>`;
441
566
  }).join('');
567
+ // Level description
568
+ const levelDesc = {
569
+ 'L1': 'Essential baseline security every agent should implement',
570
+ 'L2': 'Defense-in-depth for production systems',
571
+ 'L3': 'Maximum security for high-risk or regulated environments'
572
+ }[result.level] || '';
442
573
  return `<!DOCTYPE html>
443
574
  <html lang="en">
444
575
  <head>
445
576
  <meta charset="UTF-8">
446
577
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
447
- <title>OASB-1 Compliance Report</title>
578
+ <title>OASB-1 Compliance Report | ${result.rating}</title>
448
579
  <style>
580
+ :root {
581
+ --bg-primary: #0a0f1a;
582
+ --bg-secondary: #111827;
583
+ --bg-tertiary: #1f2937;
584
+ --text-primary: #f1f5f9;
585
+ --text-secondary: #94a3b8;
586
+ --text-muted: #64748b;
587
+ --border: #334155;
588
+ --accent: #3b82f6;
589
+ --success: #22c55e;
590
+ --warning: #eab308;
591
+ --danger: #ef4444;
592
+ }
449
593
  * { box-sizing: border-box; margin: 0; padding: 0; }
450
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; line-height: 1.6; padding: 2rem; }
451
- .container { max-width: 1200px; margin: 0 auto; }
452
- .header { text-align: center; margin-bottom: 2rem; padding: 2rem; background: #1e293b; border-radius: 12px; }
453
- .header h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
454
- .header .version { color: #94a3b8; font-size: 0.875rem; }
455
- .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
456
- .summary-card { background: #1e293b; padding: 1.5rem; border-radius: 8px; text-align: center; }
457
- .summary-card .value { font-size: 2rem; font-weight: bold; }
458
- .summary-card .label { color: #94a3b8; font-size: 0.875rem; }
459
- .rating { color: ${ratingColor}; }
460
- .category { background: #1e293b; border-radius: 8px; margin-bottom: 1rem; overflow: hidden; }
461
- .category-header { display: flex; align-items: center; gap: 0.75rem; padding: 1rem; font-weight: 600; }
462
- .category-icon { font-size: 1.25rem; }
463
- .category-name { flex: 1; }
464
- .category-score { color: #94a3b8; }
465
- .progress-bar { height: 4px; background: #334155; }
466
- .progress-fill { height: 100%; transition: width 0.3s; }
467
- .controls-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
468
- .controls-table th, .controls-table td { padding: 0.75rem 1rem; text-align: left; border-top: 1px solid #334155; }
469
- .controls-table th { background: #0f172a; color: #94a3b8; font-weight: 500; }
470
- .control-row.failed { background: rgba(239, 68, 68, 0.1); }
471
- .control-row.unverified { opacity: 0.6; }
472
- .level-badge { padding: 0.125rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
594
+ body {
595
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
596
+ background: var(--bg-primary);
597
+ color: var(--text-primary);
598
+ line-height: 1.6;
599
+ padding: 2rem;
600
+ font-size: 14px;
601
+ }
602
+ .container { max-width: 1400px; margin: 0 auto; }
603
+
604
+ /* Header */
605
+ .header {
606
+ display: flex;
607
+ justify-content: space-between;
608
+ align-items: center;
609
+ margin-bottom: 2rem;
610
+ padding: 1.5rem 2rem;
611
+ background: var(--bg-secondary);
612
+ border-radius: 12px;
613
+ border: 1px solid var(--border);
614
+ }
615
+ .header-left h1 {
616
+ font-size: 1.5rem;
617
+ font-weight: 700;
618
+ display: flex;
619
+ align-items: center;
620
+ gap: 0.75rem;
621
+ }
622
+ .header-left .meta { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.25rem; }
623
+ .header-icon { display: inline-flex; margin-right: 0.5rem; }
624
+ .header-icon .icon { width: 24px; height: 24px; color: var(--accent); }
625
+ .header-right { display: flex; align-items: center; gap: 1rem; }
626
+ .rating-badge {
627
+ display: inline-block;
628
+ padding: 0.375rem 1rem;
629
+ border-radius: 6px;
630
+ font-weight: 600;
631
+ font-size: 0.875rem;
632
+ background: ${ratingBg};
633
+ color: ${ratingColor};
634
+ border: 1px solid ${ratingColor}40;
635
+ }
636
+ .level-tag {
637
+ display: inline-block;
638
+ padding: 0.375rem 1rem;
639
+ background: var(--accent);
640
+ color: white;
641
+ border-radius: 6px;
642
+ font-size: 0.875rem;
643
+ font-weight: 600;
644
+ }
645
+
646
+ /* SVG Icons */
647
+ .icon { width: 16px; height: 16px; display: inline-block; vertical-align: middle; }
648
+ .status-pass { color: var(--success); }
649
+ .status-fail { color: var(--danger); }
650
+ .status-warn { color: var(--warning); }
651
+ .status-unverified { color: var(--text-muted); }
652
+ .category-icon { display: flex; align-items: center; }
653
+ .category-icon .icon { width: 18px; height: 18px; }
654
+ .footer-btn .icon { width: 14px; height: 14px; margin-right: 0.375rem; }
655
+
656
+ /* Dashboard grid */
657
+ .dashboard {
658
+ display: grid;
659
+ grid-template-columns: 280px 1fr 300px;
660
+ gap: 1.5rem;
661
+ margin-bottom: 2rem;
662
+ }
663
+ @media (max-width: 1200px) {
664
+ .dashboard { grid-template-columns: 1fr 1fr; }
665
+ .radar-section { grid-column: span 2; }
666
+ }
667
+ @media (max-width: 768px) {
668
+ .dashboard { grid-template-columns: 1fr; }
669
+ .radar-section { grid-column: span 1; }
670
+ }
671
+
672
+ /* Score card - Prowler style */
673
+ .score-card {
674
+ background: var(--bg-secondary);
675
+ border-radius: 12px;
676
+ padding: 1.25rem;
677
+ border: 1px solid var(--border);
678
+ }
679
+ .score-header {
680
+ display: flex;
681
+ align-items: center;
682
+ gap: 1rem;
683
+ margin-bottom: 1.25rem;
684
+ padding-bottom: 1rem;
685
+ border-bottom: 1px solid var(--border);
686
+ }
687
+ .score-grade {
688
+ width: 56px;
689
+ height: 56px;
690
+ border-radius: 12px;
691
+ border: 2px solid;
692
+ display: flex;
693
+ align-items: center;
694
+ justify-content: center;
695
+ }
696
+ .grade-letter { font-size: 1.75rem; font-weight: 800; }
697
+ .score-main { flex: 1; }
698
+ .score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
699
+ .score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
700
+
701
+ .score-bars { margin-bottom: 1rem; }
702
+ .score-bar-row {
703
+ display: flex;
704
+ align-items: center;
705
+ gap: 0.75rem;
706
+ margin-bottom: 0.5rem;
707
+ }
708
+ .bar-label { width: 50px; font-size: 0.75rem; color: var(--text-secondary); }
709
+ .bar-track { flex: 1; height: 8px; background: var(--bg-tertiary); border-radius: 4px; overflow: hidden; }
710
+ .bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
711
+ .bar-pass { background: var(--success); }
712
+ .bar-fail { background: var(--danger); }
713
+ .bar-manual { background: var(--text-muted); }
714
+ .bar-count { width: 24px; font-size: 0.8rem; font-weight: 600; text-align: right; color: var(--text-primary); }
715
+
716
+ .level-breakdown {
717
+ display: flex;
718
+ gap: 0.75rem;
719
+ padding: 0.75rem;
720
+ background: var(--bg-tertiary);
721
+ border-radius: 8px;
722
+ margin-bottom: 1rem;
723
+ }
724
+ .level-row { display: flex; align-items: center; gap: 0.375rem; }
725
+ .level-stat { font-size: 0.8rem; color: var(--text-secondary); }
726
+
727
+ .worst-category {
728
+ display: flex;
729
+ align-items: center;
730
+ gap: 0.5rem;
731
+ padding: 0.625rem 0.75rem;
732
+ background: rgba(239, 68, 68, 0.1);
733
+ border-radius: 6px;
734
+ border-left: 3px solid var(--danger);
735
+ }
736
+ .worst-label { font-size: 0.7rem; color: var(--danger); text-transform: uppercase; font-weight: 600; }
737
+ .worst-name { flex: 1; font-size: 0.8rem; color: var(--text-primary); }
738
+ .worst-pct { font-size: 0.85rem; font-weight: 700; }
739
+
740
+ /* Radar chart */
741
+ .radar-section {
742
+ background: var(--bg-secondary);
743
+ border-radius: 12px;
744
+ padding: 1.5rem;
745
+ border: 1px solid var(--border);
746
+ }
747
+ .radar-section h3 {
748
+ font-size: 0.85rem;
749
+ color: var(--text-secondary);
750
+ text-transform: uppercase;
751
+ letter-spacing: 0.05em;
752
+ margin-bottom: 1rem;
753
+ }
754
+ .radar-container { display: flex; justify-content: center; }
755
+
756
+ /* Executive summary */
757
+ .exec-section {
758
+ background: var(--bg-secondary);
759
+ border-radius: 12px;
760
+ padding: 1.5rem;
761
+ border: 1px solid var(--border);
762
+ }
763
+ .exec-section h3 {
764
+ font-size: 0.85rem;
765
+ color: var(--text-secondary);
766
+ text-transform: uppercase;
767
+ letter-spacing: 0.05em;
768
+ margin-bottom: 1rem;
769
+ }
770
+ .exec-item {
771
+ display: flex;
772
+ align-items: flex-start;
773
+ gap: 0.75rem;
774
+ padding: 0.75rem;
775
+ margin-bottom: 0.5rem;
776
+ border-radius: 6px;
777
+ font-size: 0.85rem;
778
+ }
779
+ .exec-item.critical { background: rgba(239, 68, 68, 0.1); border-left: 3px solid var(--danger); }
780
+ .exec-item.warning { background: rgba(234, 179, 8, 0.1); border-left: 3px solid var(--warning); }
781
+ .exec-item.success { background: rgba(34, 197, 94, 0.1); border-left: 3px solid var(--success); }
782
+ .exec-icon {
783
+ width: 20px;
784
+ height: 20px;
785
+ border-radius: 50%;
786
+ display: flex;
787
+ align-items: center;
788
+ justify-content: center;
789
+ font-weight: 700;
790
+ font-size: 0.75rem;
791
+ flex-shrink: 0;
792
+ }
793
+ .exec-item.critical .exec-icon { background: var(--danger); color: white; }
794
+ .exec-item.warning .exec-icon { background: var(--warning); color: black; }
795
+ .exec-item.success .exec-icon { background: var(--success); color: white; }
796
+
797
+ /* Categories */
798
+ .categories-header {
799
+ display: flex;
800
+ justify-content: space-between;
801
+ align-items: center;
802
+ margin-bottom: 1rem;
803
+ }
804
+ .categories-header h2 { font-size: 1.1rem; }
805
+ .expand-all {
806
+ background: var(--bg-tertiary);
807
+ border: 1px solid var(--border);
808
+ color: var(--text-secondary);
809
+ padding: 0.5rem 1rem;
810
+ border-radius: 6px;
811
+ cursor: pointer;
812
+ font-size: 0.8rem;
813
+ }
814
+ .expand-all:hover { background: var(--border); }
815
+
816
+ .category {
817
+ background: var(--bg-secondary);
818
+ border-radius: 8px;
819
+ margin-bottom: 0.75rem;
820
+ border: 1px solid var(--border);
821
+ overflow: hidden;
822
+ }
823
+ .category-header {
824
+ display: flex;
825
+ align-items: center;
826
+ gap: 0.75rem;
827
+ padding: 1rem 1.25rem;
828
+ cursor: pointer;
829
+ transition: background 0.15s;
830
+ }
831
+ .category-header:hover { background: var(--bg-tertiary); }
832
+ .category-icon { font-size: 1.1rem; }
833
+ .category-name { flex: 1; font-weight: 500; }
834
+ .category-meta { display: flex; align-items: center; gap: 0.75rem; }
835
+ .category-score { color: var(--text-secondary); font-size: 0.85rem; font-weight: 500; }
836
+ .mini-bar { width: 60px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
837
+ .mini-fill { height: 100%; border-radius: 3px; }
838
+ .category-percent { color: var(--text-muted); font-size: 0.85rem; width: 40px; text-align: right; }
839
+ .chevron {
840
+ color: var(--text-muted);
841
+ font-size: 0.7rem;
842
+ transition: transform 0.2s;
843
+ margin-left: 0.5rem;
844
+ }
845
+ .category.collapsed .chevron { transform: rotate(-90deg); }
846
+ .category.collapsed .category-content { display: none; }
847
+
848
+ .category-content { border-top: 1px solid var(--border); }
849
+ .controls-table { width: 100%; border-collapse: collapse; }
850
+ .controls-table th {
851
+ padding: 0.75rem 1rem;
852
+ text-align: left;
853
+ background: var(--bg-primary);
854
+ color: var(--text-muted);
855
+ font-weight: 500;
856
+ font-size: 0.75rem;
857
+ text-transform: uppercase;
858
+ letter-spacing: 0.03em;
859
+ }
860
+ .controls-table td {
861
+ padding: 0.875rem 1rem;
862
+ border-top: 1px solid var(--border);
863
+ vertical-align: top;
864
+ }
865
+ .status-cell { width: 40px; text-align: center; }
866
+ .id-cell { width: 100px; }
867
+ .id-cell code {
868
+ background: var(--bg-tertiary);
869
+ padding: 0.2rem 0.5rem;
870
+ border-radius: 4px;
871
+ font-size: 0.8rem;
872
+ color: var(--accent);
873
+ }
874
+ .name-cell { width: 30%; }
875
+ .level-cell { width: 60px; }
876
+ .details-cell { color: var(--text-secondary); font-size: 0.85rem; }
877
+ .control-row.failed { background: rgba(239, 68, 68, 0.05); }
878
+ .control-row.unverified { opacity: 0.5; }
879
+
880
+ .level-badge {
881
+ padding: 0.2rem 0.6rem;
882
+ border-radius: 4px;
883
+ font-size: 0.7rem;
884
+ font-weight: 600;
885
+ text-transform: uppercase;
886
+ }
473
887
  .level-l1 { background: #7c3aed; color: white; }
474
888
  .level-l2 { background: #2563eb; color: white; }
475
889
  .level-l3 { background: #059669; color: white; }
476
- .findings { margin: 0.5rem 0; padding-left: 1.25rem; color: #f87171; }
477
- .remediation { margin-top: 0.5rem; padding: 0.5rem; background: #334155; border-radius: 4px; font-size: 0.8rem; }
478
- .footer { text-align: center; margin-top: 2rem; color: #64748b; font-size: 0.875rem; }
890
+
891
+ .findings {
892
+ margin: 0.25rem 0 0.5rem;
893
+ padding-left: 1.25rem;
894
+ color: #f87171;
895
+ list-style-type: disc;
896
+ }
897
+ .findings li { margin-bottom: 0.25rem; }
898
+ .remediation {
899
+ margin-top: 0.5rem;
900
+ padding: 0.625rem 0.875rem;
901
+ background: var(--bg-tertiary);
902
+ border-radius: 6px;
903
+ font-size: 0.8rem;
904
+ border-left: 3px solid var(--accent);
905
+ }
906
+
907
+ /* Footer */
908
+ .footer {
909
+ display: flex;
910
+ justify-content: space-between;
911
+ align-items: center;
912
+ margin-top: 2rem;
913
+ padding: 1.5rem;
914
+ background: var(--bg-secondary);
915
+ border-radius: 12px;
916
+ border: 1px solid var(--border);
917
+ color: var(--text-muted);
918
+ font-size: 0.85rem;
919
+ }
920
+ .footer a { color: var(--accent); text-decoration: none; }
921
+ .footer a:hover { text-decoration: underline; }
922
+ .footer-actions { display: flex; gap: 1rem; }
923
+ .footer-btn {
924
+ padding: 0.5rem 1rem;
925
+ background: var(--bg-tertiary);
926
+ border: 1px solid var(--border);
927
+ border-radius: 6px;
928
+ color: var(--text-primary);
929
+ cursor: pointer;
930
+ font-size: 0.8rem;
931
+ }
932
+ .footer-btn:hover { background: var(--border); }
933
+
934
+ /* Print styles */
935
+ @media print {
936
+ body { background: white; color: black; padding: 1rem; }
937
+ .container { max-width: 100%; }
938
+ .header, .donut-card, .radar-section, .exec-section, .category, .footer {
939
+ background: white;
940
+ border: 1px solid #ddd;
941
+ break-inside: avoid;
942
+ }
943
+ .category.collapsed .category-content { display: block !important; }
944
+ .chevron, .expand-all, .footer-actions { display: none; }
945
+ .category-header { cursor: default; }
946
+ .control-row.failed { background: #fff0f0; }
947
+ :root {
948
+ --bg-primary: white;
949
+ --bg-secondary: white;
950
+ --bg-tertiary: #f5f5f5;
951
+ --text-primary: black;
952
+ --text-secondary: #555;
953
+ --text-muted: #888;
954
+ --border: #ddd;
955
+ }
956
+ }
479
957
  </style>
480
958
  </head>
481
959
  <body>
482
960
  <div class="container">
483
- <div class="header">
484
- <h1>📋 ${escapeHtml(result.benchmark)}</h1>
485
- <div class="version">Version ${result.version} • Generated ${new Date(result.timestamp).toISOString()}</div>
486
- </div>
487
-
488
- <div class="summary">
489
- <div class="summary-card">
490
- <div class="value rating">${result.rating}</div>
491
- <div class="label">Rating</div>
961
+ <header class="header">
962
+ <div class="header-left">
963
+ <h1><span class="header-icon">${icons.shield}</span>${escapeHtml(result.benchmark)}</h1>
964
+ <div class="meta">Version ${result.version} • Generated ${new Date(result.timestamp).toLocaleString()}</div>
965
+ </div>
966
+ <div class="header-right">
967
+ <div class="rating-badge">${result.rating}</div>
968
+ <div class="level-tag">${result.level} — ${result.level === 'L1' ? 'Essential' : result.level === 'L2' ? 'Standard' : 'Hardened'}</div>
492
969
  </div>
493
- <div class="summary-card">
494
- <div class="value">${result.compliance}%</div>
495
- <div class="label">Compliance</div>
970
+ </header>
971
+
972
+ <div class="dashboard">
973
+ <div class="score-card">
974
+ <div class="score-header">
975
+ <div class="score-grade" style="background: ${grade.color}20; border-color: ${grade.color};">
976
+ <span class="grade-letter" style="color: ${grade.color};">${grade.letter}</span>
977
+ </div>
978
+ <div class="score-main">
979
+ <div class="score-pct">${result.compliance}%</div>
980
+ <div class="score-label">Security Score</div>
981
+ </div>
982
+ </div>
983
+
984
+ <div class="score-bars">
985
+ <div class="score-bar-row">
986
+ <span class="bar-label">Passed</span>
987
+ <div class="bar-track">
988
+ <div class="bar-fill bar-pass" style="width: ${allControls.length ? (passedControls.length / allControls.length * 100) : 0}%;"></div>
989
+ </div>
990
+ <span class="bar-count">${passedControls.length}</span>
991
+ </div>
992
+ <div class="score-bar-row">
993
+ <span class="bar-label">Failed</span>
994
+ <div class="bar-track">
995
+ <div class="bar-fill bar-fail" style="width: ${allControls.length ? (failedControls.length / allControls.length * 100) : 0}%;"></div>
996
+ </div>
997
+ <span class="bar-count">${failedControls.length}</span>
998
+ </div>
999
+ <div class="score-bar-row">
1000
+ <span class="bar-label">Manual</span>
1001
+ <div class="bar-track">
1002
+ <div class="bar-fill bar-manual" style="width: ${allControls.length ? (unverifiedControls.length / allControls.length * 100) : 0}%;"></div>
1003
+ </div>
1004
+ <span class="bar-count">${unverifiedControls.length}</span>
1005
+ </div>
1006
+ </div>
1007
+
1008
+ <div class="level-breakdown">
1009
+ <div class="level-row">
1010
+ <span class="level-badge level-l1">L1</span>
1011
+ <span class="level-stat">${levelStats.L1.passed}/${levelStats.L1.total}</span>
1012
+ </div>
1013
+ <div class="level-row">
1014
+ <span class="level-badge level-l2">L2</span>
1015
+ <span class="level-stat">${levelStats.L2.passed}/${levelStats.L2.total}</span>
1016
+ </div>
1017
+ <div class="level-row">
1018
+ <span class="level-badge level-l3">L3</span>
1019
+ <span class="level-stat">${levelStats.L3.passed}/${levelStats.L3.total}</span>
1020
+ </div>
1021
+ </div>
1022
+
1023
+ ${worstCategory && worstCategory.compliance < 100 ? `
1024
+ <div class="worst-category">
1025
+ <span class="worst-label">Needs Attention</span>
1026
+ <span class="worst-name">${escapeHtml(worstCategory.category)}</span>
1027
+ <span class="worst-pct" style="color: ${worstCategory.compliance < 50 ? '#ef4444' : '#eab308'};">${worstCategory.compliance}%</span>
1028
+ </div>` : ''}
496
1029
  </div>
497
- <div class="summary-card">
498
- <div class="value">${result.passedControls}/${result.passedControls + result.failedControls}</div>
499
- <div class="label">Controls Passed</div>
1030
+
1031
+ <div class="radar-section">
1032
+ <h3>Category Coverage</h3>
1033
+ <div class="radar-container">
1034
+ <svg width="240" height="240" viewBox="0 0 240 240">
1035
+ ${radarGrid}
1036
+ ${radarAxes}
1037
+ <polygon points="${radarPoints.join(' ')}" fill="${complianceColor}20" stroke="${complianceColor}" stroke-width="2"/>
1038
+ ${radarLabels.join('')}
1039
+ </svg>
1040
+ </div>
500
1041
  </div>
501
- <div class="summary-card">
502
- <div class="value">Level ${result.level.replace('L', '')}</div>
503
- <div class="label">${result.level === 'L1' ? 'Essential' : result.level === 'L2' ? 'Standard' : 'Hardened'}</div>
1042
+
1043
+ <div class="exec-section">
1044
+ <h3>Priority Issues</h3>
1045
+ ${executiveSummary}
1046
+ ${failedControls.length > 0 ? `<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); font-size: 0.8rem; color: var(--text-muted);">
1047
+ ${levelDesc}
1048
+ </div>` : ''}
504
1049
  </div>
505
1050
  </div>
506
1051
 
1052
+ <div class="categories-header">
1053
+ <h2>Control Details by Category</h2>
1054
+ <button class="expand-all" onclick="toggleAll()">Expand All</button>
1055
+ </div>
1056
+
507
1057
  ${categoryRows}
508
1058
 
509
- <div class="footer">
510
- Generated by <a href="https://hackmyagent.com" style="color: #60a5fa;">HackMyAgent</a> •
511
- <a href="https://oasb.ai" style="color: #60a5fa;">OASB-1 Specification</a>
512
- </div>
1059
+ <footer class="footer">
1060
+ <div>
1061
+ Generated by <a href="https://hackmyagent.com">HackMyAgent</a>
1062
+ <a href="https://oasb.ai">OASB-1 Specification</a>
1063
+ </div>
1064
+ <div class="footer-actions">
1065
+ <button class="footer-btn" onclick="window.print()">${icons.print} Print / PDF</button>
1066
+ </div>
1067
+ </footer>
513
1068
  </div>
1069
+
1070
+ <script>
1071
+ function toggleCategory(index) {
1072
+ const cat = document.getElementById('cat-' + index);
1073
+ cat.classList.toggle('collapsed');
1074
+ }
1075
+
1076
+ function toggleAll() {
1077
+ const categories = document.querySelectorAll('.category');
1078
+ const btn = document.querySelector('.expand-all');
1079
+ const allCollapsed = Array.from(categories).every(c => c.classList.contains('collapsed'));
1080
+
1081
+ categories.forEach(cat => {
1082
+ if (allCollapsed) {
1083
+ cat.classList.remove('collapsed');
1084
+ } else {
1085
+ cat.classList.add('collapsed');
1086
+ }
1087
+ });
1088
+
1089
+ btn.textContent = allCollapsed ? 'Collapse All' : 'Expand All';
1090
+ }
1091
+
1092
+ // Start with categories collapsed
1093
+ document.querySelectorAll('.category').forEach(cat => cat.classList.add('collapsed'));
1094
+ </script>
514
1095
  </body>
515
1096
  </html>`;
516
1097
  }
@@ -1251,7 +1832,7 @@ Examples:
1251
1832
  .option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
1252
1833
  .option('--delay <ms>', 'Delay between requests in milliseconds', '1000')
1253
1834
  .option('--stop-on-success', 'Stop after first successful attack')
1254
- .option('-f, --format <format>', 'Output format: text, json, sarif', 'text')
1835
+ .option('-f, --format <format>', 'Output format: text, json, sarif, html', 'text')
1255
1836
  .option('-o, --output <file>', 'Write output to file')
1256
1837
  .option('-v, --verbose', 'Show detailed output for each payload')
1257
1838
  .action(async (targetUrl, options) => {
@@ -1300,7 +1881,7 @@ Examples:
1300
1881
  systemPrompt: options.systemPrompt,
1301
1882
  };
1302
1883
  // Validate format
1303
- const validFormats = ['text', 'json', 'sarif'];
1884
+ const validFormats = ['text', 'json', 'sarif', 'html'];
1304
1885
  const format = options.format || 'text';
1305
1886
  if (!validFormats.includes(format)) {
1306
1887
  console.error(`Error: Invalid format '${format}'. Use: ${validFormats.join(', ')}`);
@@ -1334,6 +1915,9 @@ Examples:
1334
1915
  case 'sarif':
1335
1916
  output = generateAttackSarif(report);
1336
1917
  break;
1918
+ case 'html':
1919
+ output = generateAttackHtmlReport(report);
1920
+ break;
1337
1921
  default: // text
1338
1922
  printAttackReport(report, options.verbose ?? false);
1339
1923
  output = '';
@@ -1461,5 +2045,738 @@ function generateAttackSarif(report) {
1461
2045
  }],
1462
2046
  }, null, 2);
1463
2047
  }
2048
+ // Generate HTML report for attack results
2049
+ function generateAttackHtmlReport(report) {
2050
+ // Risk grade based on score
2051
+ const getGrade = (score) => {
2052
+ if (score <= 10)
2053
+ return { letter: 'A', color: '#22c55e' };
2054
+ if (score <= 25)
2055
+ return { letter: 'B', color: '#84cc16' };
2056
+ if (score <= 50)
2057
+ return { letter: 'C', color: '#eab308' };
2058
+ if (score <= 70)
2059
+ return { letter: 'D', color: '#f97316' };
2060
+ return { letter: 'F', color: '#ef4444' };
2061
+ };
2062
+ const grade = getGrade(report.riskScore);
2063
+ const ratingColor = {
2064
+ 'critical': '#ef4444',
2065
+ 'high': '#f97316',
2066
+ 'medium': '#eab308',
2067
+ 'low': '#22c55e',
2068
+ 'secure': '#22c55e',
2069
+ };
2070
+ const ratingBg = {
2071
+ 'critical': 'rgba(239, 68, 68, 0.15)',
2072
+ 'high': 'rgba(249, 115, 22, 0.15)',
2073
+ 'medium': 'rgba(234, 179, 8, 0.15)',
2074
+ 'low': 'rgba(34, 197, 94, 0.15)',
2075
+ 'secure': 'rgba(34, 197, 94, 0.15)',
2076
+ };
2077
+ // SVG icons
2078
+ const icons = {
2079
+ sword: '<svg class="icon icon-sword" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 17.5L3 6V3h3l11.5 11.5"/><path d="M13 19l6-6"/><path d="M16 16l4 4"/><path d="M19 21l2-2"/></svg>',
2080
+ shield: '<svg class="icon icon-shield" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>',
2081
+ check: '<svg class="icon icon-check" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
2082
+ x: '<svg class="icon icon-x" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>',
2083
+ warning: '<svg class="icon icon-warning" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>',
2084
+ print: '<svg class="icon icon-print" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 4v3H4a2 2 0 00-2 2v3a2 2 0 002 2h1v2a2 2 0 002 2h6a2 2 0 002-2v-2h1a2 2 0 002-2V9a2 2 0 00-2-2h-1V4a2 2 0 00-2-2H7a2 2 0 00-2 2zm8 0H7v3h6V4zm0 8H7v4h6v-4z" clip-rule="evenodd"/></svg>',
2085
+ };
2086
+ // Category abbreviations
2087
+ const categoryAbbrev = {
2088
+ 'prompt-injection': 'PI',
2089
+ 'jailbreak': 'JB',
2090
+ 'data-exfiltration': 'DE',
2091
+ 'capability-abuse': 'CA',
2092
+ 'context-manipulation': 'CM',
2093
+ };
2094
+ // Donut chart for attack results
2095
+ const donutRadius = 60;
2096
+ const donutStroke = 12;
2097
+ const donutCircumference = 2 * Math.PI * donutRadius;
2098
+ const total = report.summary.total || 1;
2099
+ const successPct = report.summary.successful / total;
2100
+ const blockedPct = report.summary.blocked / total;
2101
+ const inconclusivePct = report.summary.inconclusive / total;
2102
+ const successDash = donutCircumference * successPct;
2103
+ const blockedDash = donutCircumference * blockedPct;
2104
+ const inconclusiveDash = donutCircumference * inconclusivePct;
2105
+ // Calculate offsets for each segment
2106
+ const successOffset = 0;
2107
+ const blockedOffset = successDash;
2108
+ const inconclusiveOffset = successDash + blockedDash;
2109
+ const donutSvg = `
2110
+ <svg width="160" height="160" viewBox="0 0 160 160">
2111
+ <!-- Background circle -->
2112
+ <circle cx="80" cy="80" r="${donutRadius}" fill="none" stroke="#334155" stroke-width="${donutStroke}"/>
2113
+ <!-- Inconclusive segment (gray) -->
2114
+ ${inconclusivePct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
2115
+ stroke="#64748b" stroke-width="${donutStroke}"
2116
+ stroke-dasharray="${inconclusiveDash} ${donutCircumference}"
2117
+ stroke-dashoffset="${-inconclusiveOffset}"
2118
+ transform="rotate(-90 80 80)"/>` : ''}
2119
+ <!-- Blocked segment (green) -->
2120
+ ${blockedPct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
2121
+ stroke="#22c55e" stroke-width="${donutStroke}"
2122
+ stroke-dasharray="${blockedDash} ${donutCircumference}"
2123
+ stroke-dashoffset="${-blockedOffset}"
2124
+ transform="rotate(-90 80 80)"/>` : ''}
2125
+ <!-- Successful segment (red) -->
2126
+ ${successPct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
2127
+ stroke="#ef4444" stroke-width="${donutStroke}"
2128
+ stroke-dasharray="${successDash} ${donutCircumference}"
2129
+ stroke-dashoffset="${-successOffset}"
2130
+ transform="rotate(-90 80 80)"/>` : ''}
2131
+ <!-- Center text -->
2132
+ <text x="80" y="75" text-anchor="middle" fill="#f1f5f9" font-size="24" font-weight="700">${report.summary.total}</text>
2133
+ <text x="80" y="95" text-anchor="middle" fill="#94a3b8" font-size="12">attacks</text>
2134
+ </svg>`;
2135
+ // Generate category breakdown rows
2136
+ const categoryRows = Object.entries(report.summary.byCategory)
2137
+ .filter(([_, stats]) => stats.total > 0)
2138
+ .map(([cat, stats]) => {
2139
+ const catInfo = hackmyagent_core_1.ATTACK_CATEGORIES[cat];
2140
+ const abbrev = categoryAbbrev[cat];
2141
+ const successRate = stats.total > 0 ? Math.round((stats.successful / stats.total) * 100) : 0;
2142
+ const barColor = stats.successful === 0 ? '#22c55e' : successRate > 50 ? '#ef4444' : '#eab308';
2143
+ const statusIcon = stats.successful === 0 ? icons.check : icons.x;
2144
+ const statusClass = stats.successful === 0 ? 'status-pass' : 'status-fail';
2145
+ // Get results for this category
2146
+ const catResults = report.results.filter(r => r.payload.category === cat);
2147
+ const resultRows = catResults.map(r => {
2148
+ const resultIcon = r.success ? icons.x : r.blocked ? icons.check : icons.warning;
2149
+ const resultClass = r.success ? 'status-fail' : r.blocked ? 'status-pass' : 'status-warn';
2150
+ const sevColor = r.payload.severity === 'critical' ? '#ef4444' :
2151
+ r.payload.severity === 'high' ? '#f97316' :
2152
+ r.payload.severity === 'medium' ? '#eab308' : '#22c55e';
2153
+ return `
2154
+ <tr class="attack-row ${r.success ? 'failed' : ''}">
2155
+ <td class="status-cell"><span class="${resultClass}">${resultIcon}</span></td>
2156
+ <td class="id-cell"><code>${r.payload.id}</code></td>
2157
+ <td class="name-cell">${escapeHtml(r.payload.name)}</td>
2158
+ <td class="severity-cell"><span class="severity-badge" style="color: ${sevColor}; background: ${sevColor}20;">${r.payload.severity.toUpperCase()}</span></td>
2159
+ <td class="result-cell">${r.success ? '<span class="result-tag fail">Succeeded</span>' : r.blocked ? '<span class="result-tag pass">Blocked</span>' : '<span class="result-tag warn">Inconclusive</span>'}</td>
2160
+ </tr>`;
2161
+ }).join('');
2162
+ return `
2163
+ <div class="category" id="cat-${abbrev}">
2164
+ <div class="category-header" onclick="toggleCategory('${abbrev}')">
2165
+ <span class="category-abbrev">[${abbrev}]</span>
2166
+ <span class="category-icon ${statusClass}">${statusIcon}</span>
2167
+ <span class="category-name">${escapeHtml(catInfo.name)}</span>
2168
+ <div class="category-meta">
2169
+ <span class="category-score">${stats.successful}/${stats.total} successful</span>
2170
+ <div class="mini-bar"><div class="mini-fill" style="width: ${successRate}%; background: ${barColor};"></div></div>
2171
+ <span class="chevron">▼</span>
2172
+ </div>
2173
+ </div>
2174
+ <div class="category-content">
2175
+ <table class="attacks-table">
2176
+ <thead><tr><th></th><th>ID</th><th>Attack</th><th>Severity</th><th>Result</th></tr></thead>
2177
+ <tbody>${resultRows}</tbody>
2178
+ </table>
2179
+ </div>
2180
+ </div>`;
2181
+ }).join('');
2182
+ // Successful attacks detail section
2183
+ const successfulAttacks = report.results.filter(r => r.success);
2184
+ const successfulDetailsHtml = successfulAttacks.length > 0 ? successfulAttacks.map(r => {
2185
+ const sevColor = r.payload.severity === 'critical' ? '#ef4444' :
2186
+ r.payload.severity === 'high' ? '#f97316' :
2187
+ r.payload.severity === 'medium' ? '#eab308' : '#22c55e';
2188
+ return `
2189
+ <div class="attack-detail">
2190
+ <div class="attack-detail-header">
2191
+ <code class="attack-id">${r.payload.id}</code>
2192
+ <span class="attack-name">${escapeHtml(r.payload.name)}</span>
2193
+ <span class="severity-badge" style="color: ${sevColor}; background: ${sevColor}20;">${r.payload.severity.toUpperCase()}</span>
2194
+ </div>
2195
+ <div class="attack-detail-meta">
2196
+ ${r.payload.oasbControl ? `<span class="meta-tag">OASB ${r.payload.oasbControl}</span>` : ''}
2197
+ ${r.payload.cwe ? `<span class="meta-tag">CWE-${r.payload.cwe}</span>` : ''}
2198
+ <span class="meta-tag">${hackmyagent_core_1.ATTACK_CATEGORIES[r.payload.category].name}</span>
2199
+ </div>
2200
+ <div class="attack-detail-body">
2201
+ <div class="detail-section">
2202
+ <strong>Description:</strong> ${escapeHtml(r.payload.description)}
2203
+ </div>
2204
+ <div class="detail-section evidence">
2205
+ <strong>Evidence:</strong> ${escapeHtml(r.evidence)}
2206
+ </div>
2207
+ <div class="detail-section remediation">
2208
+ <strong>Remediation:</strong> ${escapeHtml(r.payload.remediation)}
2209
+ </div>
2210
+ </div>
2211
+ </div>`;
2212
+ }).join('') : '<div class="no-attacks">No successful attacks detected.</div>';
2213
+ return `<!DOCTYPE html>
2214
+ <html lang="en">
2215
+ <head>
2216
+ <meta charset="UTF-8">
2217
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2218
+ <title>HackMyAgent Attack Report | ${report.riskRating.toUpperCase()}</title>
2219
+ <style>
2220
+ :root {
2221
+ --bg-primary: #0a0f1a;
2222
+ --bg-secondary: #111827;
2223
+ --bg-tertiary: #1f2937;
2224
+ --text-primary: #f1f5f9;
2225
+ --text-secondary: #94a3b8;
2226
+ --text-muted: #64748b;
2227
+ --border: #334155;
2228
+ --accent: #3b82f6;
2229
+ --success: #22c55e;
2230
+ --warning: #eab308;
2231
+ --danger: #ef4444;
2232
+ }
2233
+ * { box-sizing: border-box; margin: 0; padding: 0; }
2234
+ body {
2235
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2236
+ background: var(--bg-primary);
2237
+ color: var(--text-primary);
2238
+ line-height: 1.6;
2239
+ padding: 2rem;
2240
+ font-size: 14px;
2241
+ }
2242
+ .container { max-width: 1400px; margin: 0 auto; }
2243
+
2244
+ /* Header */
2245
+ .header {
2246
+ display: flex;
2247
+ justify-content: space-between;
2248
+ align-items: center;
2249
+ margin-bottom: 2rem;
2250
+ padding: 1.5rem 2rem;
2251
+ background: var(--bg-secondary);
2252
+ border-radius: 12px;
2253
+ border: 1px solid var(--border);
2254
+ }
2255
+ .header-left h1 {
2256
+ font-size: 1.5rem;
2257
+ font-weight: 700;
2258
+ display: flex;
2259
+ align-items: center;
2260
+ gap: 0.75rem;
2261
+ }
2262
+ .header-left .meta { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.25rem; }
2263
+ .header-icon { display: inline-flex; margin-right: 0.5rem; }
2264
+ .header-icon .icon { width: 24px; height: 24px; color: var(--danger); }
2265
+ .header-right { display: flex; align-items: center; gap: 1rem; }
2266
+ .rating-badge {
2267
+ display: inline-block;
2268
+ padding: 0.375rem 1rem;
2269
+ border-radius: 6px;
2270
+ font-weight: 600;
2271
+ font-size: 0.875rem;
2272
+ background: ${ratingBg[report.riskRating]};
2273
+ color: ${ratingColor[report.riskRating]};
2274
+ border: 1px solid ${ratingColor[report.riskRating]}40;
2275
+ }
2276
+ .intensity-tag {
2277
+ display: inline-block;
2278
+ padding: 0.375rem 1rem;
2279
+ background: var(--accent);
2280
+ color: white;
2281
+ border-radius: 6px;
2282
+ font-size: 0.875rem;
2283
+ font-weight: 600;
2284
+ text-transform: capitalize;
2285
+ }
2286
+
2287
+ /* SVG Icons */
2288
+ .icon { width: 16px; height: 16px; display: inline-block; vertical-align: middle; }
2289
+ .status-pass { color: var(--success); }
2290
+ .status-fail { color: var(--danger); }
2291
+ .status-warn { color: var(--warning); }
2292
+ .category-icon { display: flex; align-items: center; }
2293
+ .category-icon .icon { width: 18px; height: 18px; }
2294
+ .footer-btn .icon { width: 14px; height: 14px; margin-right: 0.375rem; }
2295
+
2296
+ /* Dashboard grid */
2297
+ .dashboard {
2298
+ display: grid;
2299
+ grid-template-columns: 280px 200px 1fr;
2300
+ gap: 1.5rem;
2301
+ margin-bottom: 2rem;
2302
+ }
2303
+ @media (max-width: 1200px) {
2304
+ .dashboard { grid-template-columns: 1fr 1fr; }
2305
+ .summary-section { grid-column: span 2; }
2306
+ }
2307
+ @media (max-width: 768px) {
2308
+ .dashboard { grid-template-columns: 1fr; }
2309
+ .summary-section { grid-column: span 1; }
2310
+ }
2311
+
2312
+ /* Risk Score card */
2313
+ .score-card {
2314
+ background: var(--bg-secondary);
2315
+ border-radius: 12px;
2316
+ padding: 1.25rem;
2317
+ border: 1px solid var(--border);
2318
+ }
2319
+ .score-header {
2320
+ display: flex;
2321
+ align-items: center;
2322
+ gap: 1rem;
2323
+ margin-bottom: 1.25rem;
2324
+ padding-bottom: 1rem;
2325
+ border-bottom: 1px solid var(--border);
2326
+ }
2327
+ .score-grade {
2328
+ width: 56px;
2329
+ height: 56px;
2330
+ border-radius: 12px;
2331
+ border: 2px solid;
2332
+ display: flex;
2333
+ align-items: center;
2334
+ justify-content: center;
2335
+ }
2336
+ .grade-letter { font-size: 1.75rem; font-weight: 800; }
2337
+ .score-main { flex: 1; }
2338
+ .score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
2339
+ .score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
2340
+
2341
+ .score-stats { margin-top: 1rem; }
2342
+ .stat-row {
2343
+ display: flex;
2344
+ align-items: center;
2345
+ justify-content: space-between;
2346
+ padding: 0.5rem 0;
2347
+ border-bottom: 1px solid var(--border);
2348
+ }
2349
+ .stat-row:last-child { border-bottom: none; }
2350
+ .stat-label { color: var(--text-secondary); font-size: 0.85rem; }
2351
+ .stat-value { font-weight: 600; }
2352
+ .stat-value.danger { color: var(--danger); }
2353
+ .stat-value.success { color: var(--success); }
2354
+ .stat-value.muted { color: var(--text-muted); }
2355
+
2356
+ /* Donut chart section */
2357
+ .donut-section {
2358
+ background: var(--bg-secondary);
2359
+ border-radius: 12px;
2360
+ padding: 1.25rem;
2361
+ border: 1px solid var(--border);
2362
+ display: flex;
2363
+ flex-direction: column;
2364
+ align-items: center;
2365
+ }
2366
+ .donut-section h3 {
2367
+ font-size: 0.85rem;
2368
+ color: var(--text-secondary);
2369
+ text-transform: uppercase;
2370
+ letter-spacing: 0.05em;
2371
+ margin-bottom: 1rem;
2372
+ width: 100%;
2373
+ }
2374
+ .donut-legend {
2375
+ display: flex;
2376
+ flex-direction: column;
2377
+ gap: 0.5rem;
2378
+ margin-top: 1rem;
2379
+ width: 100%;
2380
+ }
2381
+ .legend-item {
2382
+ display: flex;
2383
+ align-items: center;
2384
+ gap: 0.5rem;
2385
+ font-size: 0.8rem;
2386
+ color: var(--text-secondary);
2387
+ }
2388
+ .legend-dot {
2389
+ width: 10px;
2390
+ height: 10px;
2391
+ border-radius: 50%;
2392
+ }
2393
+
2394
+ /* Summary section */
2395
+ .summary-section {
2396
+ background: var(--bg-secondary);
2397
+ border-radius: 12px;
2398
+ padding: 1.5rem;
2399
+ border: 1px solid var(--border);
2400
+ }
2401
+ .summary-section h3 {
2402
+ font-size: 0.85rem;
2403
+ color: var(--text-secondary);
2404
+ text-transform: uppercase;
2405
+ letter-spacing: 0.05em;
2406
+ margin-bottom: 1rem;
2407
+ }
2408
+ .severity-breakdown {
2409
+ display: flex;
2410
+ gap: 1rem;
2411
+ flex-wrap: wrap;
2412
+ }
2413
+ .severity-item {
2414
+ display: flex;
2415
+ align-items: center;
2416
+ gap: 0.5rem;
2417
+ padding: 0.5rem 1rem;
2418
+ background: var(--bg-tertiary);
2419
+ border-radius: 6px;
2420
+ }
2421
+ .severity-count { font-size: 1.25rem; font-weight: 700; }
2422
+ .severity-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; }
2423
+
2424
+ /* Categories */
2425
+ .categories-section {
2426
+ margin-bottom: 2rem;
2427
+ }
2428
+ .categories-header {
2429
+ display: flex;
2430
+ justify-content: space-between;
2431
+ align-items: center;
2432
+ margin-bottom: 1rem;
2433
+ }
2434
+ .categories-header h2 { font-size: 1.1rem; }
2435
+ .expand-all {
2436
+ background: var(--bg-tertiary);
2437
+ border: 1px solid var(--border);
2438
+ color: var(--text-secondary);
2439
+ padding: 0.5rem 1rem;
2440
+ border-radius: 6px;
2441
+ cursor: pointer;
2442
+ font-size: 0.8rem;
2443
+ }
2444
+ .expand-all:hover { background: var(--border); }
2445
+
2446
+ .category {
2447
+ background: var(--bg-secondary);
2448
+ border-radius: 8px;
2449
+ margin-bottom: 0.75rem;
2450
+ border: 1px solid var(--border);
2451
+ overflow: hidden;
2452
+ }
2453
+ .category-header {
2454
+ display: flex;
2455
+ align-items: center;
2456
+ gap: 0.75rem;
2457
+ padding: 1rem 1.25rem;
2458
+ cursor: pointer;
2459
+ transition: background 0.15s;
2460
+ }
2461
+ .category-header:hover { background: var(--bg-tertiary); }
2462
+ .category-abbrev {
2463
+ font-family: monospace;
2464
+ font-size: 0.85rem;
2465
+ color: var(--accent);
2466
+ font-weight: 600;
2467
+ }
2468
+ .category-icon { font-size: 1.1rem; }
2469
+ .category-name { flex: 1; font-weight: 500; }
2470
+ .category-meta { display: flex; align-items: center; gap: 0.75rem; }
2471
+ .category-score { color: var(--text-secondary); font-size: 0.85rem; font-weight: 500; }
2472
+ .mini-bar { width: 60px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
2473
+ .mini-fill { height: 100%; border-radius: 3px; }
2474
+ .chevron {
2475
+ color: var(--text-muted);
2476
+ font-size: 0.7rem;
2477
+ transition: transform 0.2s;
2478
+ margin-left: 0.5rem;
2479
+ }
2480
+ .category.collapsed .chevron { transform: rotate(-90deg); }
2481
+ .category.collapsed .category-content { display: none; }
2482
+
2483
+ .category-content { border-top: 1px solid var(--border); }
2484
+ .attacks-table { width: 100%; border-collapse: collapse; }
2485
+ .attacks-table th {
2486
+ padding: 0.75rem 1rem;
2487
+ text-align: left;
2488
+ background: var(--bg-primary);
2489
+ color: var(--text-muted);
2490
+ font-weight: 500;
2491
+ font-size: 0.75rem;
2492
+ text-transform: uppercase;
2493
+ letter-spacing: 0.03em;
2494
+ }
2495
+ .attacks-table td {
2496
+ padding: 0.875rem 1rem;
2497
+ border-top: 1px solid var(--border);
2498
+ vertical-align: middle;
2499
+ }
2500
+ .status-cell { width: 40px; text-align: center; }
2501
+ .id-cell { width: 80px; }
2502
+ .id-cell code {
2503
+ background: var(--bg-tertiary);
2504
+ padding: 0.2rem 0.5rem;
2505
+ border-radius: 4px;
2506
+ font-size: 0.8rem;
2507
+ color: var(--accent);
2508
+ }
2509
+ .name-cell { width: 40%; }
2510
+ .severity-cell { width: 80px; }
2511
+ .result-cell { width: 100px; }
2512
+ .attack-row.failed { background: rgba(239, 68, 68, 0.05); }
2513
+
2514
+ .severity-badge {
2515
+ padding: 0.2rem 0.6rem;
2516
+ border-radius: 4px;
2517
+ font-size: 0.7rem;
2518
+ font-weight: 600;
2519
+ text-transform: uppercase;
2520
+ }
2521
+ .result-tag {
2522
+ padding: 0.2rem 0.6rem;
2523
+ border-radius: 4px;
2524
+ font-size: 0.7rem;
2525
+ font-weight: 600;
2526
+ }
2527
+ .result-tag.pass { background: rgba(34, 197, 94, 0.2); color: var(--success); }
2528
+ .result-tag.fail { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
2529
+ .result-tag.warn { background: rgba(234, 179, 8, 0.2); color: var(--warning); }
2530
+
2531
+ /* Successful attacks detail */
2532
+ .details-section {
2533
+ margin-bottom: 2rem;
2534
+ }
2535
+ .details-section h2 {
2536
+ font-size: 1.1rem;
2537
+ margin-bottom: 1rem;
2538
+ display: flex;
2539
+ align-items: center;
2540
+ gap: 0.5rem;
2541
+ }
2542
+ .details-section h2 .icon { color: var(--danger); }
2543
+ .attack-detail {
2544
+ background: var(--bg-secondary);
2545
+ border-radius: 8px;
2546
+ margin-bottom: 1rem;
2547
+ border: 1px solid var(--border);
2548
+ border-left: 3px solid var(--danger);
2549
+ overflow: hidden;
2550
+ }
2551
+ .attack-detail-header {
2552
+ display: flex;
2553
+ align-items: center;
2554
+ gap: 1rem;
2555
+ padding: 1rem 1.25rem;
2556
+ background: rgba(239, 68, 68, 0.05);
2557
+ }
2558
+ .attack-id {
2559
+ background: var(--bg-tertiary);
2560
+ padding: 0.2rem 0.5rem;
2561
+ border-radius: 4px;
2562
+ font-size: 0.85rem;
2563
+ color: var(--danger);
2564
+ }
2565
+ .attack-name { flex: 1; font-weight: 500; }
2566
+ .attack-detail-meta {
2567
+ display: flex;
2568
+ gap: 0.5rem;
2569
+ padding: 0.75rem 1.25rem;
2570
+ background: var(--bg-tertiary);
2571
+ border-bottom: 1px solid var(--border);
2572
+ }
2573
+ .meta-tag {
2574
+ padding: 0.2rem 0.5rem;
2575
+ background: var(--bg-secondary);
2576
+ border-radius: 4px;
2577
+ font-size: 0.75rem;
2578
+ color: var(--text-muted);
2579
+ }
2580
+ .attack-detail-body { padding: 1rem 1.25rem; }
2581
+ .detail-section {
2582
+ margin-bottom: 0.75rem;
2583
+ font-size: 0.9rem;
2584
+ color: var(--text-secondary);
2585
+ }
2586
+ .detail-section:last-child { margin-bottom: 0; }
2587
+ .detail-section strong { color: var(--text-primary); margin-right: 0.5rem; }
2588
+ .detail-section.evidence {
2589
+ padding: 0.75rem;
2590
+ background: rgba(239, 68, 68, 0.1);
2591
+ border-radius: 6px;
2592
+ border-left: 3px solid var(--danger);
2593
+ }
2594
+ .detail-section.remediation {
2595
+ padding: 0.75rem;
2596
+ background: var(--bg-tertiary);
2597
+ border-radius: 6px;
2598
+ border-left: 3px solid var(--accent);
2599
+ }
2600
+ .no-attacks {
2601
+ padding: 2rem;
2602
+ text-align: center;
2603
+ color: var(--success);
2604
+ background: var(--bg-secondary);
2605
+ border-radius: 8px;
2606
+ border: 1px solid var(--border);
2607
+ }
2608
+
2609
+ /* Footer */
2610
+ .footer {
2611
+ display: flex;
2612
+ justify-content: space-between;
2613
+ align-items: center;
2614
+ margin-top: 2rem;
2615
+ padding: 1.5rem;
2616
+ background: var(--bg-secondary);
2617
+ border-radius: 12px;
2618
+ border: 1px solid var(--border);
2619
+ color: var(--text-muted);
2620
+ font-size: 0.85rem;
2621
+ }
2622
+ .footer a { color: var(--accent); text-decoration: none; }
2623
+ .footer a:hover { text-decoration: underline; }
2624
+ .footer-actions { display: flex; gap: 1rem; }
2625
+ .footer-btn {
2626
+ display: flex;
2627
+ align-items: center;
2628
+ padding: 0.5rem 1rem;
2629
+ background: var(--bg-tertiary);
2630
+ border: 1px solid var(--border);
2631
+ border-radius: 6px;
2632
+ color: var(--text-primary);
2633
+ cursor: pointer;
2634
+ font-size: 0.8rem;
2635
+ }
2636
+ .footer-btn:hover { background: var(--border); }
2637
+
2638
+ /* Print styles */
2639
+ @media print {
2640
+ body { background: white; color: black; padding: 1rem; }
2641
+ .container { max-width: 100%; }
2642
+ .header, .score-card, .donut-section, .summary-section, .category, .attack-detail, .footer {
2643
+ background: white;
2644
+ border: 1px solid #ddd;
2645
+ break-inside: avoid;
2646
+ }
2647
+ .category.collapsed .category-content { display: block !important; }
2648
+ .chevron, .expand-all, .footer-actions { display: none; }
2649
+ .category-header { cursor: default; }
2650
+ .attack-row.failed { background: #fff0f0; }
2651
+ :root {
2652
+ --bg-primary: white;
2653
+ --bg-secondary: white;
2654
+ --bg-tertiary: #f5f5f5;
2655
+ --text-primary: black;
2656
+ --text-secondary: #555;
2657
+ --text-muted: #888;
2658
+ --border: #ddd;
2659
+ }
2660
+ }
2661
+ </style>
2662
+ </head>
2663
+ <body>
2664
+ <div class="container">
2665
+ <header class="header">
2666
+ <div class="header-left">
2667
+ <h1><span class="header-icon">${icons.sword}</span>HackMyAgent Attack Report</h1>
2668
+ <div class="meta">Target: ${escapeHtml(report.target || 'Local Simulation')} • ${new Date(report.endTime).toLocaleString()}</div>
2669
+ </div>
2670
+ <div class="header-right">
2671
+ <div class="rating-badge">${report.riskRating.toUpperCase()} RISK</div>
2672
+ <div class="intensity-tag">${report.intensity}</div>
2673
+ </div>
2674
+ </header>
2675
+
2676
+ <div class="dashboard">
2677
+ <div class="score-card">
2678
+ <div class="score-header">
2679
+ <div class="score-grade" style="background: ${grade.color}20; border-color: ${grade.color};">
2680
+ <span class="grade-letter" style="color: ${grade.color};">${grade.letter}</span>
2681
+ </div>
2682
+ <div class="score-main">
2683
+ <div class="score-pct">${report.riskScore}/100</div>
2684
+ <div class="score-label">Risk Score</div>
2685
+ </div>
2686
+ </div>
2687
+ <div class="score-stats">
2688
+ <div class="stat-row">
2689
+ <span class="stat-label">Successful Attacks</span>
2690
+ <span class="stat-value danger">${report.summary.successful}</span>
2691
+ </div>
2692
+ <div class="stat-row">
2693
+ <span class="stat-label">Blocked Attacks</span>
2694
+ <span class="stat-value success">${report.summary.blocked}</span>
2695
+ </div>
2696
+ <div class="stat-row">
2697
+ <span class="stat-label">Inconclusive</span>
2698
+ <span class="stat-value muted">${report.summary.inconclusive}</span>
2699
+ </div>
2700
+ <div class="stat-row">
2701
+ <span class="stat-label">Duration</span>
2702
+ <span class="stat-value">${report.duration}ms</span>
2703
+ </div>
2704
+ </div>
2705
+ </div>
2706
+
2707
+ <div class="donut-section">
2708
+ <h3>Attack Results</h3>
2709
+ ${donutSvg}
2710
+ <div class="donut-legend">
2711
+ <div class="legend-item"><span class="legend-dot" style="background: #ef4444;"></span> Successful (${report.summary.successful})</div>
2712
+ <div class="legend-item"><span class="legend-dot" style="background: #22c55e;"></span> Blocked (${report.summary.blocked})</div>
2713
+ <div class="legend-item"><span class="legend-dot" style="background: #64748b;"></span> Inconclusive (${report.summary.inconclusive})</div>
2714
+ </div>
2715
+ </div>
2716
+
2717
+ <div class="summary-section">
2718
+ <h3>Severity Breakdown (Successful Attacks)</h3>
2719
+ <div class="severity-breakdown">
2720
+ <div class="severity-item">
2721
+ <span class="severity-count" style="color: #ef4444;">${report.summary.bySeverity.critical || 0}</span>
2722
+ <span class="severity-label">Critical</span>
2723
+ </div>
2724
+ <div class="severity-item">
2725
+ <span class="severity-count" style="color: #f97316;">${report.summary.bySeverity.high || 0}</span>
2726
+ <span class="severity-label">High</span>
2727
+ </div>
2728
+ <div class="severity-item">
2729
+ <span class="severity-count" style="color: #eab308;">${report.summary.bySeverity.medium || 0}</span>
2730
+ <span class="severity-label">Medium</span>
2731
+ </div>
2732
+ <div class="severity-item">
2733
+ <span class="severity-count" style="color: #22c55e;">${report.summary.bySeverity.low || 0}</span>
2734
+ <span class="severity-label">Low</span>
2735
+ </div>
2736
+ </div>
2737
+ </div>
2738
+ </div>
2739
+
2740
+ <div class="categories-section">
2741
+ <div class="categories-header">
2742
+ <h2>Category Breakdown</h2>
2743
+ <button class="expand-all" onclick="toggleAll()">Expand/Collapse All</button>
2744
+ </div>
2745
+ ${categoryRows}
2746
+ </div>
2747
+
2748
+ <div class="details-section">
2749
+ <h2>${icons.x} Successful Attacks Detail</h2>
2750
+ ${successfulDetailsHtml}
2751
+ </div>
2752
+
2753
+ <footer class="footer">
2754
+ <div>Generated by <a href="https://hackmyagent.com">HackMyAgent</a> v${hackmyagent_core_1.VERSION} • <a href="https://oasb.ai/attacks">oasb.ai/attacks</a></div>
2755
+ <div class="footer-actions">
2756
+ <button class="footer-btn" onclick="window.print()">${icons.print} Print Report</button>
2757
+ </div>
2758
+ </footer>
2759
+ </div>
2760
+
2761
+ <script>
2762
+ function toggleCategory(id) {
2763
+ const cat = document.getElementById('cat-' + id);
2764
+ cat.classList.toggle('collapsed');
2765
+ }
2766
+ function toggleAll() {
2767
+ const cats = document.querySelectorAll('.category');
2768
+ const allCollapsed = Array.from(cats).every(c => c.classList.contains('collapsed'));
2769
+ cats.forEach(c => {
2770
+ if (allCollapsed) {
2771
+ c.classList.remove('collapsed');
2772
+ } else {
2773
+ c.classList.add('collapsed');
2774
+ }
2775
+ });
2776
+ }
2777
+ </script>
2778
+ </body>
2779
+ </html>`;
2780
+ }
1464
2781
  program.parse();
1465
2782
  //# sourceMappingURL=index.js.map