hackmyagent 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +1395 -73
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ HackMyAgent helps you secure AI agent deployments with 100+ security checks
|
|
|
30
30
|
across credential exposure, MCP configurations, prompt injection defenses,
|
|
31
31
|
and infrastructure hardening.
|
|
32
32
|
|
|
33
|
-
Documentation: https://github.com/
|
|
33
|
+
Documentation: https://github.com/opena2a-org/hackmyagent
|
|
34
34
|
|
|
35
35
|
Examples:
|
|
36
36
|
$ hackmyagent check @anthropic/claude-mcp Verify skill before installing
|
|
@@ -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
|
|
406
|
-
|
|
407
|
-
|
|
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
|
|
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>
|
|
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>${
|
|
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
|
-
<
|
|
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="
|
|
434
|
-
<
|
|
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 {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
.
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
.
|
|
472
|
-
|
|
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
|
-
|
|
477
|
-
.
|
|
478
|
-
|
|
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
|
-
<
|
|
484
|
-
<
|
|
485
|
-
|
|
486
|
-
|
|
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>
|
|
492
965
|
</div>
|
|
493
|
-
<div class="
|
|
494
|
-
<div class="
|
|
495
|
-
<div class="
|
|
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>
|
|
969
|
+
</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
|
-
|
|
498
|
-
|
|
499
|
-
<
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
<
|
|
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
|
-
<
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
}
|
|
@@ -726,6 +1307,11 @@ Examples:
|
|
|
726
1307
|
.action(async (directory, options) => {
|
|
727
1308
|
try {
|
|
728
1309
|
const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
|
|
1310
|
+
// Check if directory exists
|
|
1311
|
+
if (!require('fs').existsSync(targetDir)) {
|
|
1312
|
+
console.error(`Error: Directory '${targetDir}' does not exist.`);
|
|
1313
|
+
process.exit(1);
|
|
1314
|
+
}
|
|
729
1315
|
// Parse ignore list
|
|
730
1316
|
const ignoreList = options.ignore
|
|
731
1317
|
? options.ignore.split(',').map((s) => s.trim()).filter(Boolean)
|
|
@@ -1251,7 +1837,7 @@ Examples:
|
|
|
1251
1837
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
1252
1838
|
.option('--delay <ms>', 'Delay between requests in milliseconds', '1000')
|
|
1253
1839
|
.option('--stop-on-success', 'Stop after first successful attack')
|
|
1254
|
-
.option('-f, --format <format>', 'Output format: text, json, sarif', 'text')
|
|
1840
|
+
.option('-f, --format <format>', 'Output format: text, json, sarif, html', 'text')
|
|
1255
1841
|
.option('-o, --output <file>', 'Write output to file')
|
|
1256
1842
|
.option('-v, --verbose', 'Show detailed output for each payload')
|
|
1257
1843
|
.action(async (targetUrl, options) => {
|
|
@@ -1300,7 +1886,7 @@ Examples:
|
|
|
1300
1886
|
systemPrompt: options.systemPrompt,
|
|
1301
1887
|
};
|
|
1302
1888
|
// Validate format
|
|
1303
|
-
const validFormats = ['text', 'json', 'sarif'];
|
|
1889
|
+
const validFormats = ['text', 'json', 'sarif', 'html'];
|
|
1304
1890
|
const format = options.format || 'text';
|
|
1305
1891
|
if (!validFormats.includes(format)) {
|
|
1306
1892
|
console.error(`Error: Invalid format '${format}'. Use: ${validFormats.join(', ')}`);
|
|
@@ -1334,6 +1920,9 @@ Examples:
|
|
|
1334
1920
|
case 'sarif':
|
|
1335
1921
|
output = generateAttackSarif(report);
|
|
1336
1922
|
break;
|
|
1923
|
+
case 'html':
|
|
1924
|
+
output = generateAttackHtmlReport(report);
|
|
1925
|
+
break;
|
|
1337
1926
|
default: // text
|
|
1338
1927
|
printAttackReport(report, options.verbose ?? false);
|
|
1339
1928
|
output = '';
|
|
@@ -1461,5 +2050,738 @@ function generateAttackSarif(report) {
|
|
|
1461
2050
|
}],
|
|
1462
2051
|
}, null, 2);
|
|
1463
2052
|
}
|
|
2053
|
+
// Generate HTML report for attack results
|
|
2054
|
+
function generateAttackHtmlReport(report) {
|
|
2055
|
+
// Risk grade based on score
|
|
2056
|
+
const getGrade = (score) => {
|
|
2057
|
+
if (score <= 10)
|
|
2058
|
+
return { letter: 'A', color: '#22c55e' };
|
|
2059
|
+
if (score <= 25)
|
|
2060
|
+
return { letter: 'B', color: '#84cc16' };
|
|
2061
|
+
if (score <= 50)
|
|
2062
|
+
return { letter: 'C', color: '#eab308' };
|
|
2063
|
+
if (score <= 70)
|
|
2064
|
+
return { letter: 'D', color: '#f97316' };
|
|
2065
|
+
return { letter: 'F', color: '#ef4444' };
|
|
2066
|
+
};
|
|
2067
|
+
const grade = getGrade(report.riskScore);
|
|
2068
|
+
const ratingColor = {
|
|
2069
|
+
'critical': '#ef4444',
|
|
2070
|
+
'high': '#f97316',
|
|
2071
|
+
'medium': '#eab308',
|
|
2072
|
+
'low': '#22c55e',
|
|
2073
|
+
'secure': '#22c55e',
|
|
2074
|
+
};
|
|
2075
|
+
const ratingBg = {
|
|
2076
|
+
'critical': 'rgba(239, 68, 68, 0.15)',
|
|
2077
|
+
'high': 'rgba(249, 115, 22, 0.15)',
|
|
2078
|
+
'medium': 'rgba(234, 179, 8, 0.15)',
|
|
2079
|
+
'low': 'rgba(34, 197, 94, 0.15)',
|
|
2080
|
+
'secure': 'rgba(34, 197, 94, 0.15)',
|
|
2081
|
+
};
|
|
2082
|
+
// SVG icons
|
|
2083
|
+
const icons = {
|
|
2084
|
+
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>',
|
|
2085
|
+
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>',
|
|
2086
|
+
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>',
|
|
2087
|
+
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>',
|
|
2088
|
+
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>',
|
|
2089
|
+
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>',
|
|
2090
|
+
};
|
|
2091
|
+
// Category abbreviations
|
|
2092
|
+
const categoryAbbrev = {
|
|
2093
|
+
'prompt-injection': 'PI',
|
|
2094
|
+
'jailbreak': 'JB',
|
|
2095
|
+
'data-exfiltration': 'DE',
|
|
2096
|
+
'capability-abuse': 'CA',
|
|
2097
|
+
'context-manipulation': 'CM',
|
|
2098
|
+
};
|
|
2099
|
+
// Donut chart for attack results
|
|
2100
|
+
const donutRadius = 60;
|
|
2101
|
+
const donutStroke = 12;
|
|
2102
|
+
const donutCircumference = 2 * Math.PI * donutRadius;
|
|
2103
|
+
const total = report.summary.total || 1;
|
|
2104
|
+
const successPct = report.summary.successful / total;
|
|
2105
|
+
const blockedPct = report.summary.blocked / total;
|
|
2106
|
+
const inconclusivePct = report.summary.inconclusive / total;
|
|
2107
|
+
const successDash = donutCircumference * successPct;
|
|
2108
|
+
const blockedDash = donutCircumference * blockedPct;
|
|
2109
|
+
const inconclusiveDash = donutCircumference * inconclusivePct;
|
|
2110
|
+
// Calculate offsets for each segment
|
|
2111
|
+
const successOffset = 0;
|
|
2112
|
+
const blockedOffset = successDash;
|
|
2113
|
+
const inconclusiveOffset = successDash + blockedDash;
|
|
2114
|
+
const donutSvg = `
|
|
2115
|
+
<svg width="160" height="160" viewBox="0 0 160 160">
|
|
2116
|
+
<!-- Background circle -->
|
|
2117
|
+
<circle cx="80" cy="80" r="${donutRadius}" fill="none" stroke="#334155" stroke-width="${donutStroke}"/>
|
|
2118
|
+
<!-- Inconclusive segment (gray) -->
|
|
2119
|
+
${inconclusivePct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
|
|
2120
|
+
stroke="#64748b" stroke-width="${donutStroke}"
|
|
2121
|
+
stroke-dasharray="${inconclusiveDash} ${donutCircumference}"
|
|
2122
|
+
stroke-dashoffset="${-inconclusiveOffset}"
|
|
2123
|
+
transform="rotate(-90 80 80)"/>` : ''}
|
|
2124
|
+
<!-- Blocked segment (green) -->
|
|
2125
|
+
${blockedPct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
|
|
2126
|
+
stroke="#22c55e" stroke-width="${donutStroke}"
|
|
2127
|
+
stroke-dasharray="${blockedDash} ${donutCircumference}"
|
|
2128
|
+
stroke-dashoffset="${-blockedOffset}"
|
|
2129
|
+
transform="rotate(-90 80 80)"/>` : ''}
|
|
2130
|
+
<!-- Successful segment (red) -->
|
|
2131
|
+
${successPct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
|
|
2132
|
+
stroke="#ef4444" stroke-width="${donutStroke}"
|
|
2133
|
+
stroke-dasharray="${successDash} ${donutCircumference}"
|
|
2134
|
+
stroke-dashoffset="${-successOffset}"
|
|
2135
|
+
transform="rotate(-90 80 80)"/>` : ''}
|
|
2136
|
+
<!-- Center text -->
|
|
2137
|
+
<text x="80" y="75" text-anchor="middle" fill="#f1f5f9" font-size="24" font-weight="700">${report.summary.total}</text>
|
|
2138
|
+
<text x="80" y="95" text-anchor="middle" fill="#94a3b8" font-size="12">attacks</text>
|
|
2139
|
+
</svg>`;
|
|
2140
|
+
// Generate category breakdown rows
|
|
2141
|
+
const categoryRows = Object.entries(report.summary.byCategory)
|
|
2142
|
+
.filter(([_, stats]) => stats.total > 0)
|
|
2143
|
+
.map(([cat, stats]) => {
|
|
2144
|
+
const catInfo = hackmyagent_core_1.ATTACK_CATEGORIES[cat];
|
|
2145
|
+
const abbrev = categoryAbbrev[cat];
|
|
2146
|
+
const successRate = stats.total > 0 ? Math.round((stats.successful / stats.total) * 100) : 0;
|
|
2147
|
+
const barColor = stats.successful === 0 ? '#22c55e' : successRate > 50 ? '#ef4444' : '#eab308';
|
|
2148
|
+
const statusIcon = stats.successful === 0 ? icons.check : icons.x;
|
|
2149
|
+
const statusClass = stats.successful === 0 ? 'status-pass' : 'status-fail';
|
|
2150
|
+
// Get results for this category
|
|
2151
|
+
const catResults = report.results.filter(r => r.payload.category === cat);
|
|
2152
|
+
const resultRows = catResults.map(r => {
|
|
2153
|
+
const resultIcon = r.success ? icons.x : r.blocked ? icons.check : icons.warning;
|
|
2154
|
+
const resultClass = r.success ? 'status-fail' : r.blocked ? 'status-pass' : 'status-warn';
|
|
2155
|
+
const sevColor = r.payload.severity === 'critical' ? '#ef4444' :
|
|
2156
|
+
r.payload.severity === 'high' ? '#f97316' :
|
|
2157
|
+
r.payload.severity === 'medium' ? '#eab308' : '#22c55e';
|
|
2158
|
+
return `
|
|
2159
|
+
<tr class="attack-row ${r.success ? 'failed' : ''}">
|
|
2160
|
+
<td class="status-cell"><span class="${resultClass}">${resultIcon}</span></td>
|
|
2161
|
+
<td class="id-cell"><code>${r.payload.id}</code></td>
|
|
2162
|
+
<td class="name-cell">${escapeHtml(r.payload.name)}</td>
|
|
2163
|
+
<td class="severity-cell"><span class="severity-badge" style="color: ${sevColor}; background: ${sevColor}20;">${r.payload.severity.toUpperCase()}</span></td>
|
|
2164
|
+
<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>
|
|
2165
|
+
</tr>`;
|
|
2166
|
+
}).join('');
|
|
2167
|
+
return `
|
|
2168
|
+
<div class="category" id="cat-${abbrev}">
|
|
2169
|
+
<div class="category-header" onclick="toggleCategory('${abbrev}')">
|
|
2170
|
+
<span class="category-abbrev">[${abbrev}]</span>
|
|
2171
|
+
<span class="category-icon ${statusClass}">${statusIcon}</span>
|
|
2172
|
+
<span class="category-name">${escapeHtml(catInfo.name)}</span>
|
|
2173
|
+
<div class="category-meta">
|
|
2174
|
+
<span class="category-score">${stats.successful}/${stats.total} successful</span>
|
|
2175
|
+
<div class="mini-bar"><div class="mini-fill" style="width: ${successRate}%; background: ${barColor};"></div></div>
|
|
2176
|
+
<span class="chevron">▼</span>
|
|
2177
|
+
</div>
|
|
2178
|
+
</div>
|
|
2179
|
+
<div class="category-content">
|
|
2180
|
+
<table class="attacks-table">
|
|
2181
|
+
<thead><tr><th></th><th>ID</th><th>Attack</th><th>Severity</th><th>Result</th></tr></thead>
|
|
2182
|
+
<tbody>${resultRows}</tbody>
|
|
2183
|
+
</table>
|
|
2184
|
+
</div>
|
|
2185
|
+
</div>`;
|
|
2186
|
+
}).join('');
|
|
2187
|
+
// Successful attacks detail section
|
|
2188
|
+
const successfulAttacks = report.results.filter(r => r.success);
|
|
2189
|
+
const successfulDetailsHtml = successfulAttacks.length > 0 ? successfulAttacks.map(r => {
|
|
2190
|
+
const sevColor = r.payload.severity === 'critical' ? '#ef4444' :
|
|
2191
|
+
r.payload.severity === 'high' ? '#f97316' :
|
|
2192
|
+
r.payload.severity === 'medium' ? '#eab308' : '#22c55e';
|
|
2193
|
+
return `
|
|
2194
|
+
<div class="attack-detail">
|
|
2195
|
+
<div class="attack-detail-header">
|
|
2196
|
+
<code class="attack-id">${r.payload.id}</code>
|
|
2197
|
+
<span class="attack-name">${escapeHtml(r.payload.name)}</span>
|
|
2198
|
+
<span class="severity-badge" style="color: ${sevColor}; background: ${sevColor}20;">${r.payload.severity.toUpperCase()}</span>
|
|
2199
|
+
</div>
|
|
2200
|
+
<div class="attack-detail-meta">
|
|
2201
|
+
${r.payload.oasbControl ? `<span class="meta-tag">OASB ${r.payload.oasbControl}</span>` : ''}
|
|
2202
|
+
${r.payload.cwe ? `<span class="meta-tag">CWE-${r.payload.cwe}</span>` : ''}
|
|
2203
|
+
<span class="meta-tag">${hackmyagent_core_1.ATTACK_CATEGORIES[r.payload.category].name}</span>
|
|
2204
|
+
</div>
|
|
2205
|
+
<div class="attack-detail-body">
|
|
2206
|
+
<div class="detail-section">
|
|
2207
|
+
<strong>Description:</strong> ${escapeHtml(r.payload.description)}
|
|
2208
|
+
</div>
|
|
2209
|
+
<div class="detail-section evidence">
|
|
2210
|
+
<strong>Evidence:</strong> ${escapeHtml(r.evidence)}
|
|
2211
|
+
</div>
|
|
2212
|
+
<div class="detail-section remediation">
|
|
2213
|
+
<strong>Remediation:</strong> ${escapeHtml(r.payload.remediation)}
|
|
2214
|
+
</div>
|
|
2215
|
+
</div>
|
|
2216
|
+
</div>`;
|
|
2217
|
+
}).join('') : '<div class="no-attacks">No successful attacks detected.</div>';
|
|
2218
|
+
return `<!DOCTYPE html>
|
|
2219
|
+
<html lang="en">
|
|
2220
|
+
<head>
|
|
2221
|
+
<meta charset="UTF-8">
|
|
2222
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2223
|
+
<title>HackMyAgent Attack Report | ${report.riskRating.toUpperCase()}</title>
|
|
2224
|
+
<style>
|
|
2225
|
+
:root {
|
|
2226
|
+
--bg-primary: #0a0f1a;
|
|
2227
|
+
--bg-secondary: #111827;
|
|
2228
|
+
--bg-tertiary: #1f2937;
|
|
2229
|
+
--text-primary: #f1f5f9;
|
|
2230
|
+
--text-secondary: #94a3b8;
|
|
2231
|
+
--text-muted: #64748b;
|
|
2232
|
+
--border: #334155;
|
|
2233
|
+
--accent: #3b82f6;
|
|
2234
|
+
--success: #22c55e;
|
|
2235
|
+
--warning: #eab308;
|
|
2236
|
+
--danger: #ef4444;
|
|
2237
|
+
}
|
|
2238
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2239
|
+
body {
|
|
2240
|
+
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2241
|
+
background: var(--bg-primary);
|
|
2242
|
+
color: var(--text-primary);
|
|
2243
|
+
line-height: 1.6;
|
|
2244
|
+
padding: 2rem;
|
|
2245
|
+
font-size: 14px;
|
|
2246
|
+
}
|
|
2247
|
+
.container { max-width: 1400px; margin: 0 auto; }
|
|
2248
|
+
|
|
2249
|
+
/* Header */
|
|
2250
|
+
.header {
|
|
2251
|
+
display: flex;
|
|
2252
|
+
justify-content: space-between;
|
|
2253
|
+
align-items: center;
|
|
2254
|
+
margin-bottom: 2rem;
|
|
2255
|
+
padding: 1.5rem 2rem;
|
|
2256
|
+
background: var(--bg-secondary);
|
|
2257
|
+
border-radius: 12px;
|
|
2258
|
+
border: 1px solid var(--border);
|
|
2259
|
+
}
|
|
2260
|
+
.header-left h1 {
|
|
2261
|
+
font-size: 1.5rem;
|
|
2262
|
+
font-weight: 700;
|
|
2263
|
+
display: flex;
|
|
2264
|
+
align-items: center;
|
|
2265
|
+
gap: 0.75rem;
|
|
2266
|
+
}
|
|
2267
|
+
.header-left .meta { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.25rem; }
|
|
2268
|
+
.header-icon { display: inline-flex; margin-right: 0.5rem; }
|
|
2269
|
+
.header-icon .icon { width: 24px; height: 24px; color: var(--danger); }
|
|
2270
|
+
.header-right { display: flex; align-items: center; gap: 1rem; }
|
|
2271
|
+
.rating-badge {
|
|
2272
|
+
display: inline-block;
|
|
2273
|
+
padding: 0.375rem 1rem;
|
|
2274
|
+
border-radius: 6px;
|
|
2275
|
+
font-weight: 600;
|
|
2276
|
+
font-size: 0.875rem;
|
|
2277
|
+
background: ${ratingBg[report.riskRating]};
|
|
2278
|
+
color: ${ratingColor[report.riskRating]};
|
|
2279
|
+
border: 1px solid ${ratingColor[report.riskRating]}40;
|
|
2280
|
+
}
|
|
2281
|
+
.intensity-tag {
|
|
2282
|
+
display: inline-block;
|
|
2283
|
+
padding: 0.375rem 1rem;
|
|
2284
|
+
background: var(--accent);
|
|
2285
|
+
color: white;
|
|
2286
|
+
border-radius: 6px;
|
|
2287
|
+
font-size: 0.875rem;
|
|
2288
|
+
font-weight: 600;
|
|
2289
|
+
text-transform: capitalize;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
/* SVG Icons */
|
|
2293
|
+
.icon { width: 16px; height: 16px; display: inline-block; vertical-align: middle; }
|
|
2294
|
+
.status-pass { color: var(--success); }
|
|
2295
|
+
.status-fail { color: var(--danger); }
|
|
2296
|
+
.status-warn { color: var(--warning); }
|
|
2297
|
+
.category-icon { display: flex; align-items: center; }
|
|
2298
|
+
.category-icon .icon { width: 18px; height: 18px; }
|
|
2299
|
+
.footer-btn .icon { width: 14px; height: 14px; margin-right: 0.375rem; }
|
|
2300
|
+
|
|
2301
|
+
/* Dashboard grid */
|
|
2302
|
+
.dashboard {
|
|
2303
|
+
display: grid;
|
|
2304
|
+
grid-template-columns: 280px 200px 1fr;
|
|
2305
|
+
gap: 1.5rem;
|
|
2306
|
+
margin-bottom: 2rem;
|
|
2307
|
+
}
|
|
2308
|
+
@media (max-width: 1200px) {
|
|
2309
|
+
.dashboard { grid-template-columns: 1fr 1fr; }
|
|
2310
|
+
.summary-section { grid-column: span 2; }
|
|
2311
|
+
}
|
|
2312
|
+
@media (max-width: 768px) {
|
|
2313
|
+
.dashboard { grid-template-columns: 1fr; }
|
|
2314
|
+
.summary-section { grid-column: span 1; }
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
/* Risk Score card */
|
|
2318
|
+
.score-card {
|
|
2319
|
+
background: var(--bg-secondary);
|
|
2320
|
+
border-radius: 12px;
|
|
2321
|
+
padding: 1.25rem;
|
|
2322
|
+
border: 1px solid var(--border);
|
|
2323
|
+
}
|
|
2324
|
+
.score-header {
|
|
2325
|
+
display: flex;
|
|
2326
|
+
align-items: center;
|
|
2327
|
+
gap: 1rem;
|
|
2328
|
+
margin-bottom: 1.25rem;
|
|
2329
|
+
padding-bottom: 1rem;
|
|
2330
|
+
border-bottom: 1px solid var(--border);
|
|
2331
|
+
}
|
|
2332
|
+
.score-grade {
|
|
2333
|
+
width: 56px;
|
|
2334
|
+
height: 56px;
|
|
2335
|
+
border-radius: 12px;
|
|
2336
|
+
border: 2px solid;
|
|
2337
|
+
display: flex;
|
|
2338
|
+
align-items: center;
|
|
2339
|
+
justify-content: center;
|
|
2340
|
+
}
|
|
2341
|
+
.grade-letter { font-size: 1.75rem; font-weight: 800; }
|
|
2342
|
+
.score-main { flex: 1; }
|
|
2343
|
+
.score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
|
|
2344
|
+
.score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
|
|
2345
|
+
|
|
2346
|
+
.score-stats { margin-top: 1rem; }
|
|
2347
|
+
.stat-row {
|
|
2348
|
+
display: flex;
|
|
2349
|
+
align-items: center;
|
|
2350
|
+
justify-content: space-between;
|
|
2351
|
+
padding: 0.5rem 0;
|
|
2352
|
+
border-bottom: 1px solid var(--border);
|
|
2353
|
+
}
|
|
2354
|
+
.stat-row:last-child { border-bottom: none; }
|
|
2355
|
+
.stat-label { color: var(--text-secondary); font-size: 0.85rem; }
|
|
2356
|
+
.stat-value { font-weight: 600; }
|
|
2357
|
+
.stat-value.danger { color: var(--danger); }
|
|
2358
|
+
.stat-value.success { color: var(--success); }
|
|
2359
|
+
.stat-value.muted { color: var(--text-muted); }
|
|
2360
|
+
|
|
2361
|
+
/* Donut chart section */
|
|
2362
|
+
.donut-section {
|
|
2363
|
+
background: var(--bg-secondary);
|
|
2364
|
+
border-radius: 12px;
|
|
2365
|
+
padding: 1.25rem;
|
|
2366
|
+
border: 1px solid var(--border);
|
|
2367
|
+
display: flex;
|
|
2368
|
+
flex-direction: column;
|
|
2369
|
+
align-items: center;
|
|
2370
|
+
}
|
|
2371
|
+
.donut-section h3 {
|
|
2372
|
+
font-size: 0.85rem;
|
|
2373
|
+
color: var(--text-secondary);
|
|
2374
|
+
text-transform: uppercase;
|
|
2375
|
+
letter-spacing: 0.05em;
|
|
2376
|
+
margin-bottom: 1rem;
|
|
2377
|
+
width: 100%;
|
|
2378
|
+
}
|
|
2379
|
+
.donut-legend {
|
|
2380
|
+
display: flex;
|
|
2381
|
+
flex-direction: column;
|
|
2382
|
+
gap: 0.5rem;
|
|
2383
|
+
margin-top: 1rem;
|
|
2384
|
+
width: 100%;
|
|
2385
|
+
}
|
|
2386
|
+
.legend-item {
|
|
2387
|
+
display: flex;
|
|
2388
|
+
align-items: center;
|
|
2389
|
+
gap: 0.5rem;
|
|
2390
|
+
font-size: 0.8rem;
|
|
2391
|
+
color: var(--text-secondary);
|
|
2392
|
+
}
|
|
2393
|
+
.legend-dot {
|
|
2394
|
+
width: 10px;
|
|
2395
|
+
height: 10px;
|
|
2396
|
+
border-radius: 50%;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
/* Summary section */
|
|
2400
|
+
.summary-section {
|
|
2401
|
+
background: var(--bg-secondary);
|
|
2402
|
+
border-radius: 12px;
|
|
2403
|
+
padding: 1.5rem;
|
|
2404
|
+
border: 1px solid var(--border);
|
|
2405
|
+
}
|
|
2406
|
+
.summary-section h3 {
|
|
2407
|
+
font-size: 0.85rem;
|
|
2408
|
+
color: var(--text-secondary);
|
|
2409
|
+
text-transform: uppercase;
|
|
2410
|
+
letter-spacing: 0.05em;
|
|
2411
|
+
margin-bottom: 1rem;
|
|
2412
|
+
}
|
|
2413
|
+
.severity-breakdown {
|
|
2414
|
+
display: flex;
|
|
2415
|
+
gap: 1rem;
|
|
2416
|
+
flex-wrap: wrap;
|
|
2417
|
+
}
|
|
2418
|
+
.severity-item {
|
|
2419
|
+
display: flex;
|
|
2420
|
+
align-items: center;
|
|
2421
|
+
gap: 0.5rem;
|
|
2422
|
+
padding: 0.5rem 1rem;
|
|
2423
|
+
background: var(--bg-tertiary);
|
|
2424
|
+
border-radius: 6px;
|
|
2425
|
+
}
|
|
2426
|
+
.severity-count { font-size: 1.25rem; font-weight: 700; }
|
|
2427
|
+
.severity-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; }
|
|
2428
|
+
|
|
2429
|
+
/* Categories */
|
|
2430
|
+
.categories-section {
|
|
2431
|
+
margin-bottom: 2rem;
|
|
2432
|
+
}
|
|
2433
|
+
.categories-header {
|
|
2434
|
+
display: flex;
|
|
2435
|
+
justify-content: space-between;
|
|
2436
|
+
align-items: center;
|
|
2437
|
+
margin-bottom: 1rem;
|
|
2438
|
+
}
|
|
2439
|
+
.categories-header h2 { font-size: 1.1rem; }
|
|
2440
|
+
.expand-all {
|
|
2441
|
+
background: var(--bg-tertiary);
|
|
2442
|
+
border: 1px solid var(--border);
|
|
2443
|
+
color: var(--text-secondary);
|
|
2444
|
+
padding: 0.5rem 1rem;
|
|
2445
|
+
border-radius: 6px;
|
|
2446
|
+
cursor: pointer;
|
|
2447
|
+
font-size: 0.8rem;
|
|
2448
|
+
}
|
|
2449
|
+
.expand-all:hover { background: var(--border); }
|
|
2450
|
+
|
|
2451
|
+
.category {
|
|
2452
|
+
background: var(--bg-secondary);
|
|
2453
|
+
border-radius: 8px;
|
|
2454
|
+
margin-bottom: 0.75rem;
|
|
2455
|
+
border: 1px solid var(--border);
|
|
2456
|
+
overflow: hidden;
|
|
2457
|
+
}
|
|
2458
|
+
.category-header {
|
|
2459
|
+
display: flex;
|
|
2460
|
+
align-items: center;
|
|
2461
|
+
gap: 0.75rem;
|
|
2462
|
+
padding: 1rem 1.25rem;
|
|
2463
|
+
cursor: pointer;
|
|
2464
|
+
transition: background 0.15s;
|
|
2465
|
+
}
|
|
2466
|
+
.category-header:hover { background: var(--bg-tertiary); }
|
|
2467
|
+
.category-abbrev {
|
|
2468
|
+
font-family: monospace;
|
|
2469
|
+
font-size: 0.85rem;
|
|
2470
|
+
color: var(--accent);
|
|
2471
|
+
font-weight: 600;
|
|
2472
|
+
}
|
|
2473
|
+
.category-icon { font-size: 1.1rem; }
|
|
2474
|
+
.category-name { flex: 1; font-weight: 500; }
|
|
2475
|
+
.category-meta { display: flex; align-items: center; gap: 0.75rem; }
|
|
2476
|
+
.category-score { color: var(--text-secondary); font-size: 0.85rem; font-weight: 500; }
|
|
2477
|
+
.mini-bar { width: 60px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
|
|
2478
|
+
.mini-fill { height: 100%; border-radius: 3px; }
|
|
2479
|
+
.chevron {
|
|
2480
|
+
color: var(--text-muted);
|
|
2481
|
+
font-size: 0.7rem;
|
|
2482
|
+
transition: transform 0.2s;
|
|
2483
|
+
margin-left: 0.5rem;
|
|
2484
|
+
}
|
|
2485
|
+
.category.collapsed .chevron { transform: rotate(-90deg); }
|
|
2486
|
+
.category.collapsed .category-content { display: none; }
|
|
2487
|
+
|
|
2488
|
+
.category-content { border-top: 1px solid var(--border); }
|
|
2489
|
+
.attacks-table { width: 100%; border-collapse: collapse; }
|
|
2490
|
+
.attacks-table th {
|
|
2491
|
+
padding: 0.75rem 1rem;
|
|
2492
|
+
text-align: left;
|
|
2493
|
+
background: var(--bg-primary);
|
|
2494
|
+
color: var(--text-muted);
|
|
2495
|
+
font-weight: 500;
|
|
2496
|
+
font-size: 0.75rem;
|
|
2497
|
+
text-transform: uppercase;
|
|
2498
|
+
letter-spacing: 0.03em;
|
|
2499
|
+
}
|
|
2500
|
+
.attacks-table td {
|
|
2501
|
+
padding: 0.875rem 1rem;
|
|
2502
|
+
border-top: 1px solid var(--border);
|
|
2503
|
+
vertical-align: middle;
|
|
2504
|
+
}
|
|
2505
|
+
.status-cell { width: 40px; text-align: center; }
|
|
2506
|
+
.id-cell { width: 80px; }
|
|
2507
|
+
.id-cell code {
|
|
2508
|
+
background: var(--bg-tertiary);
|
|
2509
|
+
padding: 0.2rem 0.5rem;
|
|
2510
|
+
border-radius: 4px;
|
|
2511
|
+
font-size: 0.8rem;
|
|
2512
|
+
color: var(--accent);
|
|
2513
|
+
}
|
|
2514
|
+
.name-cell { width: 40%; }
|
|
2515
|
+
.severity-cell { width: 80px; }
|
|
2516
|
+
.result-cell { width: 100px; }
|
|
2517
|
+
.attack-row.failed { background: rgba(239, 68, 68, 0.05); }
|
|
2518
|
+
|
|
2519
|
+
.severity-badge {
|
|
2520
|
+
padding: 0.2rem 0.6rem;
|
|
2521
|
+
border-radius: 4px;
|
|
2522
|
+
font-size: 0.7rem;
|
|
2523
|
+
font-weight: 600;
|
|
2524
|
+
text-transform: uppercase;
|
|
2525
|
+
}
|
|
2526
|
+
.result-tag {
|
|
2527
|
+
padding: 0.2rem 0.6rem;
|
|
2528
|
+
border-radius: 4px;
|
|
2529
|
+
font-size: 0.7rem;
|
|
2530
|
+
font-weight: 600;
|
|
2531
|
+
}
|
|
2532
|
+
.result-tag.pass { background: rgba(34, 197, 94, 0.2); color: var(--success); }
|
|
2533
|
+
.result-tag.fail { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
|
|
2534
|
+
.result-tag.warn { background: rgba(234, 179, 8, 0.2); color: var(--warning); }
|
|
2535
|
+
|
|
2536
|
+
/* Successful attacks detail */
|
|
2537
|
+
.details-section {
|
|
2538
|
+
margin-bottom: 2rem;
|
|
2539
|
+
}
|
|
2540
|
+
.details-section h2 {
|
|
2541
|
+
font-size: 1.1rem;
|
|
2542
|
+
margin-bottom: 1rem;
|
|
2543
|
+
display: flex;
|
|
2544
|
+
align-items: center;
|
|
2545
|
+
gap: 0.5rem;
|
|
2546
|
+
}
|
|
2547
|
+
.details-section h2 .icon { color: var(--danger); }
|
|
2548
|
+
.attack-detail {
|
|
2549
|
+
background: var(--bg-secondary);
|
|
2550
|
+
border-radius: 8px;
|
|
2551
|
+
margin-bottom: 1rem;
|
|
2552
|
+
border: 1px solid var(--border);
|
|
2553
|
+
border-left: 3px solid var(--danger);
|
|
2554
|
+
overflow: hidden;
|
|
2555
|
+
}
|
|
2556
|
+
.attack-detail-header {
|
|
2557
|
+
display: flex;
|
|
2558
|
+
align-items: center;
|
|
2559
|
+
gap: 1rem;
|
|
2560
|
+
padding: 1rem 1.25rem;
|
|
2561
|
+
background: rgba(239, 68, 68, 0.05);
|
|
2562
|
+
}
|
|
2563
|
+
.attack-id {
|
|
2564
|
+
background: var(--bg-tertiary);
|
|
2565
|
+
padding: 0.2rem 0.5rem;
|
|
2566
|
+
border-radius: 4px;
|
|
2567
|
+
font-size: 0.85rem;
|
|
2568
|
+
color: var(--danger);
|
|
2569
|
+
}
|
|
2570
|
+
.attack-name { flex: 1; font-weight: 500; }
|
|
2571
|
+
.attack-detail-meta {
|
|
2572
|
+
display: flex;
|
|
2573
|
+
gap: 0.5rem;
|
|
2574
|
+
padding: 0.75rem 1.25rem;
|
|
2575
|
+
background: var(--bg-tertiary);
|
|
2576
|
+
border-bottom: 1px solid var(--border);
|
|
2577
|
+
}
|
|
2578
|
+
.meta-tag {
|
|
2579
|
+
padding: 0.2rem 0.5rem;
|
|
2580
|
+
background: var(--bg-secondary);
|
|
2581
|
+
border-radius: 4px;
|
|
2582
|
+
font-size: 0.75rem;
|
|
2583
|
+
color: var(--text-muted);
|
|
2584
|
+
}
|
|
2585
|
+
.attack-detail-body { padding: 1rem 1.25rem; }
|
|
2586
|
+
.detail-section {
|
|
2587
|
+
margin-bottom: 0.75rem;
|
|
2588
|
+
font-size: 0.9rem;
|
|
2589
|
+
color: var(--text-secondary);
|
|
2590
|
+
}
|
|
2591
|
+
.detail-section:last-child { margin-bottom: 0; }
|
|
2592
|
+
.detail-section strong { color: var(--text-primary); margin-right: 0.5rem; }
|
|
2593
|
+
.detail-section.evidence {
|
|
2594
|
+
padding: 0.75rem;
|
|
2595
|
+
background: rgba(239, 68, 68, 0.1);
|
|
2596
|
+
border-radius: 6px;
|
|
2597
|
+
border-left: 3px solid var(--danger);
|
|
2598
|
+
}
|
|
2599
|
+
.detail-section.remediation {
|
|
2600
|
+
padding: 0.75rem;
|
|
2601
|
+
background: var(--bg-tertiary);
|
|
2602
|
+
border-radius: 6px;
|
|
2603
|
+
border-left: 3px solid var(--accent);
|
|
2604
|
+
}
|
|
2605
|
+
.no-attacks {
|
|
2606
|
+
padding: 2rem;
|
|
2607
|
+
text-align: center;
|
|
2608
|
+
color: var(--success);
|
|
2609
|
+
background: var(--bg-secondary);
|
|
2610
|
+
border-radius: 8px;
|
|
2611
|
+
border: 1px solid var(--border);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
/* Footer */
|
|
2615
|
+
.footer {
|
|
2616
|
+
display: flex;
|
|
2617
|
+
justify-content: space-between;
|
|
2618
|
+
align-items: center;
|
|
2619
|
+
margin-top: 2rem;
|
|
2620
|
+
padding: 1.5rem;
|
|
2621
|
+
background: var(--bg-secondary);
|
|
2622
|
+
border-radius: 12px;
|
|
2623
|
+
border: 1px solid var(--border);
|
|
2624
|
+
color: var(--text-muted);
|
|
2625
|
+
font-size: 0.85rem;
|
|
2626
|
+
}
|
|
2627
|
+
.footer a { color: var(--accent); text-decoration: none; }
|
|
2628
|
+
.footer a:hover { text-decoration: underline; }
|
|
2629
|
+
.footer-actions { display: flex; gap: 1rem; }
|
|
2630
|
+
.footer-btn {
|
|
2631
|
+
display: flex;
|
|
2632
|
+
align-items: center;
|
|
2633
|
+
padding: 0.5rem 1rem;
|
|
2634
|
+
background: var(--bg-tertiary);
|
|
2635
|
+
border: 1px solid var(--border);
|
|
2636
|
+
border-radius: 6px;
|
|
2637
|
+
color: var(--text-primary);
|
|
2638
|
+
cursor: pointer;
|
|
2639
|
+
font-size: 0.8rem;
|
|
2640
|
+
}
|
|
2641
|
+
.footer-btn:hover { background: var(--border); }
|
|
2642
|
+
|
|
2643
|
+
/* Print styles */
|
|
2644
|
+
@media print {
|
|
2645
|
+
body { background: white; color: black; padding: 1rem; }
|
|
2646
|
+
.container { max-width: 100%; }
|
|
2647
|
+
.header, .score-card, .donut-section, .summary-section, .category, .attack-detail, .footer {
|
|
2648
|
+
background: white;
|
|
2649
|
+
border: 1px solid #ddd;
|
|
2650
|
+
break-inside: avoid;
|
|
2651
|
+
}
|
|
2652
|
+
.category.collapsed .category-content { display: block !important; }
|
|
2653
|
+
.chevron, .expand-all, .footer-actions { display: none; }
|
|
2654
|
+
.category-header { cursor: default; }
|
|
2655
|
+
.attack-row.failed { background: #fff0f0; }
|
|
2656
|
+
:root {
|
|
2657
|
+
--bg-primary: white;
|
|
2658
|
+
--bg-secondary: white;
|
|
2659
|
+
--bg-tertiary: #f5f5f5;
|
|
2660
|
+
--text-primary: black;
|
|
2661
|
+
--text-secondary: #555;
|
|
2662
|
+
--text-muted: #888;
|
|
2663
|
+
--border: #ddd;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
</style>
|
|
2667
|
+
</head>
|
|
2668
|
+
<body>
|
|
2669
|
+
<div class="container">
|
|
2670
|
+
<header class="header">
|
|
2671
|
+
<div class="header-left">
|
|
2672
|
+
<h1><span class="header-icon">${icons.sword}</span>HackMyAgent Attack Report</h1>
|
|
2673
|
+
<div class="meta">Target: ${escapeHtml(report.target || 'Local Simulation')} • ${new Date(report.endTime).toLocaleString()}</div>
|
|
2674
|
+
</div>
|
|
2675
|
+
<div class="header-right">
|
|
2676
|
+
<div class="rating-badge">${report.riskRating.toUpperCase()} RISK</div>
|
|
2677
|
+
<div class="intensity-tag">${report.intensity}</div>
|
|
2678
|
+
</div>
|
|
2679
|
+
</header>
|
|
2680
|
+
|
|
2681
|
+
<div class="dashboard">
|
|
2682
|
+
<div class="score-card">
|
|
2683
|
+
<div class="score-header">
|
|
2684
|
+
<div class="score-grade" style="background: ${grade.color}20; border-color: ${grade.color};">
|
|
2685
|
+
<span class="grade-letter" style="color: ${grade.color};">${grade.letter}</span>
|
|
2686
|
+
</div>
|
|
2687
|
+
<div class="score-main">
|
|
2688
|
+
<div class="score-pct">${report.riskScore}/100</div>
|
|
2689
|
+
<div class="score-label">Risk Score</div>
|
|
2690
|
+
</div>
|
|
2691
|
+
</div>
|
|
2692
|
+
<div class="score-stats">
|
|
2693
|
+
<div class="stat-row">
|
|
2694
|
+
<span class="stat-label">Successful Attacks</span>
|
|
2695
|
+
<span class="stat-value danger">${report.summary.successful}</span>
|
|
2696
|
+
</div>
|
|
2697
|
+
<div class="stat-row">
|
|
2698
|
+
<span class="stat-label">Blocked Attacks</span>
|
|
2699
|
+
<span class="stat-value success">${report.summary.blocked}</span>
|
|
2700
|
+
</div>
|
|
2701
|
+
<div class="stat-row">
|
|
2702
|
+
<span class="stat-label">Inconclusive</span>
|
|
2703
|
+
<span class="stat-value muted">${report.summary.inconclusive}</span>
|
|
2704
|
+
</div>
|
|
2705
|
+
<div class="stat-row">
|
|
2706
|
+
<span class="stat-label">Duration</span>
|
|
2707
|
+
<span class="stat-value">${report.duration}ms</span>
|
|
2708
|
+
</div>
|
|
2709
|
+
</div>
|
|
2710
|
+
</div>
|
|
2711
|
+
|
|
2712
|
+
<div class="donut-section">
|
|
2713
|
+
<h3>Attack Results</h3>
|
|
2714
|
+
${donutSvg}
|
|
2715
|
+
<div class="donut-legend">
|
|
2716
|
+
<div class="legend-item"><span class="legend-dot" style="background: #ef4444;"></span> Successful (${report.summary.successful})</div>
|
|
2717
|
+
<div class="legend-item"><span class="legend-dot" style="background: #22c55e;"></span> Blocked (${report.summary.blocked})</div>
|
|
2718
|
+
<div class="legend-item"><span class="legend-dot" style="background: #64748b;"></span> Inconclusive (${report.summary.inconclusive})</div>
|
|
2719
|
+
</div>
|
|
2720
|
+
</div>
|
|
2721
|
+
|
|
2722
|
+
<div class="summary-section">
|
|
2723
|
+
<h3>Severity Breakdown (Successful Attacks)</h3>
|
|
2724
|
+
<div class="severity-breakdown">
|
|
2725
|
+
<div class="severity-item">
|
|
2726
|
+
<span class="severity-count" style="color: #ef4444;">${report.summary.bySeverity.critical || 0}</span>
|
|
2727
|
+
<span class="severity-label">Critical</span>
|
|
2728
|
+
</div>
|
|
2729
|
+
<div class="severity-item">
|
|
2730
|
+
<span class="severity-count" style="color: #f97316;">${report.summary.bySeverity.high || 0}</span>
|
|
2731
|
+
<span class="severity-label">High</span>
|
|
2732
|
+
</div>
|
|
2733
|
+
<div class="severity-item">
|
|
2734
|
+
<span class="severity-count" style="color: #eab308;">${report.summary.bySeverity.medium || 0}</span>
|
|
2735
|
+
<span class="severity-label">Medium</span>
|
|
2736
|
+
</div>
|
|
2737
|
+
<div class="severity-item">
|
|
2738
|
+
<span class="severity-count" style="color: #22c55e;">${report.summary.bySeverity.low || 0}</span>
|
|
2739
|
+
<span class="severity-label">Low</span>
|
|
2740
|
+
</div>
|
|
2741
|
+
</div>
|
|
2742
|
+
</div>
|
|
2743
|
+
</div>
|
|
2744
|
+
|
|
2745
|
+
<div class="categories-section">
|
|
2746
|
+
<div class="categories-header">
|
|
2747
|
+
<h2>Category Breakdown</h2>
|
|
2748
|
+
<button class="expand-all" onclick="toggleAll()">Expand/Collapse All</button>
|
|
2749
|
+
</div>
|
|
2750
|
+
${categoryRows}
|
|
2751
|
+
</div>
|
|
2752
|
+
|
|
2753
|
+
<div class="details-section">
|
|
2754
|
+
<h2>${icons.x} Successful Attacks Detail</h2>
|
|
2755
|
+
${successfulDetailsHtml}
|
|
2756
|
+
</div>
|
|
2757
|
+
|
|
2758
|
+
<footer class="footer">
|
|
2759
|
+
<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>
|
|
2760
|
+
<div class="footer-actions">
|
|
2761
|
+
<button class="footer-btn" onclick="window.print()">${icons.print} Print Report</button>
|
|
2762
|
+
</div>
|
|
2763
|
+
</footer>
|
|
2764
|
+
</div>
|
|
2765
|
+
|
|
2766
|
+
<script>
|
|
2767
|
+
function toggleCategory(id) {
|
|
2768
|
+
const cat = document.getElementById('cat-' + id);
|
|
2769
|
+
cat.classList.toggle('collapsed');
|
|
2770
|
+
}
|
|
2771
|
+
function toggleAll() {
|
|
2772
|
+
const cats = document.querySelectorAll('.category');
|
|
2773
|
+
const allCollapsed = Array.from(cats).every(c => c.classList.contains('collapsed'));
|
|
2774
|
+
cats.forEach(c => {
|
|
2775
|
+
if (allCollapsed) {
|
|
2776
|
+
c.classList.remove('collapsed');
|
|
2777
|
+
} else {
|
|
2778
|
+
c.classList.add('collapsed');
|
|
2779
|
+
}
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
</script>
|
|
2783
|
+
</body>
|
|
2784
|
+
</html>`;
|
|
2785
|
+
}
|
|
1464
2786
|
program.parse();
|
|
1465
2787
|
//# sourceMappingURL=index.js.map
|