● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0

Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.

$ git clone https://shehzade.io/repos/contraband.git Copy
Source ZIP (1.93 MB)
Code 174 Commits 1
contraband / pkg / report / templates / dashboard.html
← Back to directory
1304 lines · 38.5 KB
Raw
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>Contraband Assessment Report — {{ .TargetHost }}</title>
7<style>
8 :root {
9 --bg-canvas: #080b11;
10 --bg-card: rgba(15, 23, 42, 0.78);
11 --bg-card-inner: rgba(30, 41, 59, 0.55);
12 --bg-input: rgba(15, 23, 42, 0.95);
13 --border-color: rgba(255, 255, 255, 0.08);
14 --border-focus: #38bdf8;
15 --text-primary: #f1f5f9;
16 --text-muted: #94a3b8;
17 --text-dim: #64748b;
18 --text-highlight: #ffffff;
19
20 --color-green: #10b981;
21 --color-green-glow: rgba(16, 185, 129, 0.25);
22 --color-sky: #38bdf8;
23 --color-sky-glow: rgba(56, 189, 248, 0.25);
24 --color-amber: #f59e0b;
25 --color-amber-glow: rgba(245, 158, 11, 0.25);
26 --color-rose: #f43f5e;
27 --color-rose-glow: rgba(244, 63, 94, 0.25);
28 --color-purple: #a855f7;
29 --color-purple-glow: rgba(168, 85, 247, 0.25);
30 }
31
32 * { box-sizing: border-box; margin: 0; padding: 0; }
33
34 body {
35 background-color: var(--bg-canvas);
36 background-image:
37 radial-gradient(ellipse at 15% -5%, rgba(56, 189, 248, 0.07) 0%, transparent 60%),
38 radial-gradient(ellipse at 85% -5%, rgba(244, 63, 94, 0.05) 0%, transparent 60%),
39 radial-gradient(ellipse at 50% 100%, rgba(16, 185, 129, 0.04) 0%, transparent 60%);
40 background-attachment: fixed;
41 color: var(--text-primary);
42 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", "Helvetica Neue", Arial, sans-serif;
43 -webkit-font-smoothing: antialiased;
44 -moz-osx-font-smoothing: grayscale;
45 line-height: 1.5;
46 padding: 28px 24px 64px;
47 }
48
49 code, pre, .mono {
50 font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
51 }
52
53 .container { max-width: 1440px; margin: 0 auto; }
54
55 /* Header */
56 header {
57 display: flex;
58 justify-content: space-between;
59 align-items: center;
60 padding-bottom: 22px;
61 border-bottom: 1px solid var(--border-color);
62 margin-bottom: 26px;
63 flex-wrap: wrap;
64 gap: 16px;
65 }
66
67 .brand {
68 display: flex;
69 align-items: center;
70 gap: 14px;
71 }
72
73 .brand-icon {
74 width: 40px;
75 height: 40px;
76 background: linear-gradient(135deg, rgba(56, 189, 248, 0.2), rgba(244, 63, 94, 0.2));
77 border: 1px solid rgba(56, 189, 248, 0.4);
78 border-radius: 10px;
79 display: flex;
80 align-items: center;
81 justify-content: center;
82 box-shadow: 0 0 16px rgba(56, 189, 248, 0.2);
83 }
84
85 .brand-icon svg {
86 width: 24px;
87 height: 24px;
88 fill: none;
89 stroke: var(--color-sky);
90 stroke-width: 2;
91 }
92
93 .brand h1 {
94 font-size: 22px;
95 font-weight: 800;
96 color: var(--text-highlight);
97 letter-spacing: 0.08em;
98 text-transform: uppercase;
99 }
100
101 .brand .tagline {
102 font-size: 12px;
103 color: var(--text-muted);
104 letter-spacing: -0.01em;
105 }
106
107 .meta-pills {
108 display: flex;
109 gap: 10px;
110 flex-wrap: wrap;
111 align-items: center;
112 }
113
114 .pill {
115 background: var(--bg-card);
116 backdrop-filter: blur(12px);
117 -webkit-backdrop-filter: blur(12px);
118 border: 1px solid var(--border-color);
119 border-radius: 8px;
120 padding: 6px 14px;
121 font-size: 12px;
122 color: var(--text-muted);
123 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
124 }
125
126 .pill strong {
127 color: var(--text-highlight);
128 font-weight: 600;
129 }
130
131 .pill.version {
132 border-color: rgba(56, 189, 248, 0.3);
133 color: var(--color-sky);
134 background: rgba(56, 189, 248, 0.08);
135 }
136
137 /* Grid Layouts */
138 .grid-2 {
139 display: grid;
140 grid-template-columns: 340px 1fr;
141 gap: 20px;
142 margin-bottom: 24px;
143 }
144
145 .grid-4 {
146 display: grid;
147 grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
148 gap: 16px;
149 margin-bottom: 24px;
150 }
151
152 @media (max-width: 960px) {
153 .grid-2 { grid-template-columns: 1fr; }
154 }
155
156 /* Cards */
157 .card {
158 background: var(--bg-card);
159 backdrop-filter: blur(16px);
160 -webkit-backdrop-filter: blur(16px);
161 border: 1px solid var(--border-color);
162 border-radius: 12px;
163 padding: 22px;
164 position: relative;
165 box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05);
166 transition: border-color 0.2s ease, box-shadow 0.2s ease;
167 }
168
169 .card-header {
170 display: flex;
171 justify-content: space-between;
172 align-items: center;
173 margin-bottom: 18px;
174 padding-bottom: 12px;
175 border-bottom: 1px solid var(--border-color);
176 }
177
178 .card-header h2 {
179 font-size: 14px;
180 font-weight: 700;
181 color: var(--text-highlight);
182 display: flex;
183 align-items: center;
184 gap: 10px;
185 letter-spacing: 0.03em;
186 text-transform: uppercase;
187 }
188
189 .card-header h2 svg {
190 width: 17px;
191 height: 17px;
192 stroke: var(--color-sky);
193 }
194
195 /* Gauge Component */
196 .gauge-box {
197 text-align: center;
198 position: relative;
199 display: flex;
200 flex-direction: column;
201 align-items: center;
202 justify-content: center;
203 }
204
205 .gauge-svg {
206 width: 280px;
207 height: 155px;
208 overflow: visible;
209 }
210
211 .gauge-bg {
212 fill: none;
213 stroke: rgba(255, 255, 255, 0.06);
214 stroke-width: 16;
215 stroke-linecap: round;
216 }
217
218 .gauge-val {
219 fill: none;
220 stroke-width: 16;
221 stroke-linecap: round;
222 transition: stroke-dashoffset 1s cubic-bezier(0.16, 1, 0.3, 1);
223 }
224
225 .gauge-needle-group {
226 transform-origin: 150px 140px;
227 transform-box: view-box;
228 transition: transform 1s cubic-bezier(0.16, 1, 0.3, 1);
229 }
230
231 .gauge-needle {
232 stroke: #ffffff;
233 stroke-width: 3.5;
234 stroke-linecap: round;
235 filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.5));
236 }
237
238 .gauge-needle-poly {
239 fill: #ffffff;
240 filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.75));
241 }
242
243 .gauge-score {
244 font-size: 32px;
245 font-weight: 800;
246 color: var(--text-highlight);
247 margin-top: -8px;
248 letter-spacing: -0.02em;
249 font-variant-numeric: tabular-nums;
250 }
251
252 .posture-badge {
253 display: inline-flex;
254 align-items: center;
255 gap: 6px;
256 padding: 5px 14px;
257 border-radius: 9999px;
258 font-size: 11px;
259 font-weight: 700;
260 text-transform: uppercase;
261 letter-spacing: 0.06em;
262 margin-top: 8px;
263 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
264 }
265
266 .posture-badge::before {
267 content: "";
268 width: 6px;
269 height: 6px;
270 border-radius: 50%;
271 }
272
273 .posture-hardened {
274 background: rgba(16, 185, 129, 0.12);
275 color: var(--color-green);
276 border: 1px solid rgba(16, 185, 129, 0.35);
277 }
278 .posture-hardened::before {
279 background: var(--color-green);
280 box-shadow: 0 0 6px var(--color-green);
281 }
282
283 .posture-moderate {
284 background: rgba(245, 158, 11, 0.12);
285 color: var(--color-amber);
286 border: 1px solid rgba(245, 158, 11, 0.35);
287 }
288 .posture-moderate::before {
289 background: var(--color-amber);
290 box-shadow: 0 0 6px var(--color-amber);
291 }
292
293 .posture-critical {
294 background: rgba(244, 63, 94, 0.12);
295 color: var(--color-rose);
296 border: 1px solid rgba(244, 63, 94, 0.35);
297 }
298 .posture-critical::before {
299 background: var(--color-rose);
300 box-shadow: 0 0 6px var(--color-rose);
301 }
302
303 /* Category breakdown bars inside gauge card */
304 .category-bars {
305 margin-top: 18px;
306 display: flex;
307 flex-direction: column;
308 gap: 12px;
309 width: 100%;
310 text-align: left;
311 }
312
313 .cat-item { font-size: 11px; }
314
315 .cat-head {
316 display: flex;
317 justify-content: space-between;
318 margin-bottom: 5px;
319 color: var(--text-muted);
320 font-weight: 500;
321 }
322
323 .cat-head strong {
324 color: var(--text-primary);
325 font-variant-numeric: tabular-nums;
326 }
327
328 .cat-bar-bg {
329 background: rgba(255, 255, 255, 0.05);
330 height: 6px;
331 border-radius: 9999px;
332 overflow: hidden;
333 }
334
335 .cat-bar-fill {
336 height: 100%;
337 border-radius: 9999px;
338 transition: width 0.8s ease;
339 }
340
341 /* KPI Summary */
342 .kpi-card {
343 display: flex;
344 flex-direction: column;
345 justify-content: space-between;
346 transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease;
347 }
348
349 .kpi-card:hover {
350 transform: translateY(-2px);
351 box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.08);
352 }
353
354 .kpi-title {
355 font-size: 11px;
356 color: var(--text-muted);
357 text-transform: uppercase;
358 letter-spacing: 0.08em;
359 font-weight: 600;
360 }
361
362 .kpi-val {
363 font-size: 28px;
364 font-weight: 800;
365 color: var(--text-highlight);
366 margin: 8px 0 4px;
367 letter-spacing: -0.02em;
368 font-variant-numeric: tabular-nums;
369 }
370
371 .kpi-sub {
372 font-size: 11px;
373 color: var(--text-dim);
374 }
375
376 /* Badges */
377 .badge {
378 display: inline-flex;
379 align-items: center;
380 gap: 5px;
381 padding: 3px 9px;
382 border-radius: 6px;
383 font-size: 11px;
384 font-weight: 700;
385 text-transform: uppercase;
386 letter-spacing: 0.04em;
387 line-height: 1.2;
388 white-space: nowrap;
389 }
390
391 .badge::before {
392 content: "";
393 width: 5px;
394 height: 5px;
395 border-radius: 50%;
396 }
397
398 .badge-success {
399 background: rgba(244, 63, 94, 0.12);
400 color: #fda4af;
401 border: 1px solid rgba(244, 63, 94, 0.35);
402 }
403 .badge-success::before {
404 background: var(--color-rose);
405 box-shadow: 0 0 5px var(--color-rose);
406 }
407
408 .badge-blocked-l4 {
409 background: rgba(16, 185, 129, 0.12);
410 color: #6ee7b7;
411 border: 1px solid rgba(16, 185, 129, 0.35);
412 }
413 .badge-blocked-l4::before {
414 background: var(--color-green);
415 box-shadow: 0 0 5px var(--color-green);
416 }
417
418 .badge-blocked-l7 {
419 background: rgba(56, 189, 248, 0.12);
420 color: #7dd3fc;
421 border: 1px solid rgba(56, 189, 248, 0.35);
422 }
423 .badge-blocked-l7::before {
424 background: var(--color-sky);
425 box-shadow: 0 0 5px var(--color-sky);
426 }
427
428 .badge-blocked-dlp {
429 background: rgba(245, 158, 11, 0.12);
430 color: #fcd34d;
431 border: 1px solid rgba(245, 158, 11, 0.35);
432 }
433 .badge-blocked-dlp::before {
434 background: var(--color-amber);
435 box-shadow: 0 0 5px var(--color-amber);
436 }
437
438 .badge-unknown {
439 background: rgba(148, 163, 184, 0.12);
440 color: #cbd5e1;
441 border: 1px solid rgba(148, 163, 184, 0.3);
442 }
443
444 /* Heatmap */
445 .heatmap-controls {
446 display: flex;
447 gap: 8px;
448 margin-bottom: 14px;
449 flex-wrap: wrap;
450 align-items: center;
451 }
452
453 .search-input {
454 background: var(--bg-input);
455 border: 1px solid var(--border-color);
456 border-radius: 8px;
457 padding: 7px 14px;
458 color: var(--text-highlight);
459 font-size: 12px;
460 outline: none;
461 transition: all 0.2s ease;
462 }
463
464 .search-input:focus {
465 border-color: var(--color-sky);
466 box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.2);
467 }
468
469 .btn-filter {
470 background: var(--bg-card-inner);
471 border: 1px solid var(--border-color);
472 border-radius: 8px;
473 padding: 7px 12px;
474 color: var(--text-muted);
475 font-size: 11px;
476 font-weight: 600;
477 cursor: pointer;
478 transition: all 0.2s ease;
479 user-select: none;
480 }
481
482 .btn-filter.active, .btn-filter:hover {
483 background: rgba(255, 255, 255, 0.08);
484 color: var(--text-highlight);
485 border-color: rgba(255, 255, 255, 0.2);
486 }
487
488 .btn-filter.active {
489 border-color: var(--color-sky);
490 color: var(--color-sky);
491 }
492
493 .heatmap-grid {
494 display: grid;
495 grid-template-columns: repeat(auto-fill, minmax(46px, 1fr));
496 gap: 7px;
497 max-height: 290px;
498 overflow-y: auto;
499 padding: 6px;
500 background: rgba(8, 11, 17, 0.7);
501 border-radius: 8px;
502 border: 1px solid var(--border-color);
503 }
504
505 .port-cell {
506 aspect-ratio: 1;
507 display: flex;
508 flex-direction: column;
509 align-items: center;
510 justify-content: center;
511 font-size: 11px;
512 font-weight: 700;
513 font-variant-numeric: tabular-nums;
514 border-radius: 6px;
515 cursor: pointer;
516 position: relative;
517 transition: transform 0.15s ease, box-shadow 0.15s ease;
518 user-select: none;
519 }
520
521 .port-cell:hover {
522 transform: scale(1.18);
523 z-index: 20;
524 box-shadow: 0 0 12px currentColor;
525 }
526
527 /* Support both verdict-* and cell-* classes for robust rendering */
528 .verdict-success, .cell-success {
529 background: rgba(244, 63, 94, 0.18);
530 border: 1px solid rgba(244, 63, 94, 0.6);
531 color: #fda4af;
532 }
533
534 .verdict-blocked-l4, .cell-blocked-l4 {
535 background: rgba(16, 185, 129, 0.14);
536 border: 1px solid rgba(16, 185, 129, 0.5);
537 color: #6ee7b7;
538 }
539
540 .verdict-blocked-l7, .cell-blocked-l7 {
541 background: rgba(56, 189, 248, 0.14);
542 border: 1px solid rgba(56, 189, 248, 0.5);
543 color: #7dd3fc;
544 }
545
546 .verdict-blocked-dlp, .cell-blocked-dlp {
547 background: rgba(245, 158, 11, 0.14);
548 border: 1px solid rgba(245, 158, 11, 0.5);
549 color: #fcd34d;
550 }
551
552 .verdict-unknown, .cell-unknown {
553 background: rgba(148, 163, 184, 0.1);
554 border: 1px solid rgba(148, 163, 184, 0.3);
555 color: #cbd5e1;
556 }
557
558 /* Tooltip */
559 .tooltip {
560 visibility: hidden;
561 position: absolute;
562 bottom: 125%;
563 left: 50%;
564 transform: translateX(-50%);
565 background: #020617;
566 color: #f8fafc;
567 padding: 8px 12px;
568 border-radius: 8px;
569 font-size: 11px;
570 white-space: nowrap;
571 z-index: 30;
572 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.1);
573 pointer-events: none;
574 line-height: 1.4;
575 }
576
577 .tooltip::after {
578 content: "";
579 position: absolute;
580 top: 100%;
581 left: 50%;
582 transform: translateX(-50%);
583 border-width: 5px;
584 border-style: solid;
585 border-color: #020617 transparent transparent transparent;
586 }
587
588 .port-cell:hover .tooltip { visibility: visible; }
589
590 /* Tables */
591 .table-wrap {
592 overflow-x: auto;
593 border-radius: 8px;
594 border: 1px solid var(--border-color);
595 }
596
597 table {
598 width: 100%;
599 border-collapse: collapse;
600 font-size: 12px;
601 text-align: left;
602 }
603
604 th {
605 background: rgba(15, 23, 42, 0.95);
606 color: var(--text-muted);
607 font-weight: 700;
608 padding: 11px 14px;
609 border-bottom: 1px solid var(--border-color);
610 letter-spacing: 0.04em;
611 text-transform: uppercase;
612 font-size: 11px;
613 position: sticky;
614 top: 0;
615 backdrop-filter: blur(8px);
616 }
617
618 td {
619 padding: 11px 14px;
620 border-bottom: 1px solid var(--border-color);
621 color: var(--text-primary);
622 }
623
624 tr:last-child td {
625 border-bottom: none;
626 }
627
628 tr:hover td {
629 background: rgba(255, 255, 255, 0.025);
630 }
631
632 tr.selected td {
633 background: rgba(56, 189, 248, 0.12) !important;
634 }
635
636 /* Expandable Forensic Drawer */
637 .drawer-row { display: none; }
638 .drawer-row.open { display: table-row; }
639 .drawer-content {
640 background: rgba(8, 11, 17, 0.95);
641 padding: 18px;
642 border-radius: 8px;
643 margin: 8px 12px;
644 border: 1px solid rgba(255, 255, 255, 0.08);
645 box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
646 }
647
648 .waterfall-bar-group {
649 display: flex;
650 height: 8px;
651 border-radius: 4px;
652 overflow: hidden;
653 margin: 10px 0;
654 background: rgba(255, 255, 255, 0.05);
655 }
656
657 .waterfall-tcp { background: var(--color-green); }
658 .waterfall-tls { background: var(--color-sky); }
659 .waterfall-transfer { background: var(--color-amber); }
660
661 /* Credentials Vault */
662 .cred-card {
663 background: rgba(8, 11, 17, 0.7);
664 border: 1px solid var(--border-color);
665 border-radius: 8px;
666 padding: 14px 16px;
667 margin-bottom: 12px;
668 }
669
670 .cred-header {
671 display: flex;
672 justify-content: space-between;
673 align-items: center;
674 margin-bottom: 10px;
675 font-size: 12px;
676 }
677
678 .hashcat-syntax {
679 background: #020617;
680 border: 1px solid rgba(255, 255, 255, 0.06);
681 padding: 12px 14px;
682 border-radius: 6px;
683 word-break: break-all;
684 font-size: 11px;
685 line-height: 1.6;
686 color: #e2e8f0;
687 }
688
689 /* Tabs */
690 .tabs {
691 display: flex;
692 gap: 6px;
693 border-bottom: 1px solid var(--border-color);
694 margin-bottom: 18px;
695 overflow-x: auto;
696 }
697
698 .tab-btn {
699 background: none;
700 border: none;
701 border-bottom: 2px solid transparent;
702 color: var(--text-muted);
703 padding: 9px 16px;
704 font-size: 12px;
705 font-weight: 600;
706 cursor: pointer;
707 transition: all 0.2s ease;
708 white-space: nowrap;
709 }
710
711 .tab-btn.active {
712 color: var(--color-sky);
713 border-bottom-color: var(--color-sky);
714 }
715
716 .tab-pane { display: none; }
717 .tab-pane.active { display: block; }
718
719 /* Code blocks & Buttons */
720 .code-block {
721 background: #020617;
722 border: 1px solid rgba(255, 255, 255, 0.08);
723 border-radius: 8px;
724 padding: 16px;
725 font-size: 11px;
726 color: #cbd5e1;
727 overflow-x: auto;
728 max-height: 380px;
729 line-height: 1.5;
730 position: relative;
731 white-space: pre;
732 }
733
734 .btn-copy {
735 position: absolute;
736 top: 10px;
737 right: 10px;
738 background: rgba(30, 41, 59, 0.85);
739 border: 1px solid rgba(255, 255, 255, 0.12);
740 border-radius: 6px;
741 color: var(--text-muted);
742 font-size: 11px;
743 font-weight: 600;
744 padding: 5px 10px;
745 cursor: pointer;
746 transition: all 0.2s ease;
747 }
748
749 .btn-copy:hover {
750 color: var(--text-highlight);
751 border-color: var(--color-sky);
752 background: rgba(56, 189, 248, 0.15);
753 }
754
755 /* Efficacy Matrix */
756 .matrix-table th, .matrix-table td { text-align: center; }
757 .matrix-table th:first-child, .matrix-table td:first-child { text-align: left; }
758
759 /* Empty state */
760 .empty-state {
761 text-align: center;
762 padding: 32px 20px;
763 color: var(--text-muted);
764 }
765
766 .empty-state svg {
767 width: 40px;
768 height: 40px;
769 stroke: var(--color-green);
770 margin-bottom: 10px;
771 }
772</style>
773</head>
774<body>
775<div class="container">
776
777 <!-- Header -->
778 <header>
779 <div class="brand">
780 <div class="brand-icon">
781 <svg viewBox="0 0 24 24">
782 <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
783 </svg>
784 </div>
785 <div>
786 <h1>CONTRABAND</h1>
787 <div class="tagline">Automated Egress Assessment & Data Smuggling Forensics</div>
788 </div>
789 </div>
790 <div class="meta-pills">
791 <div class="pill">Target: <strong>{{ .TargetHost }}</strong></div>
792 <div class="pill">Generated: <strong>{{ formatRFC3339MS .GeneratedAt }}</strong></div>
793 <div class="pill">Duration: <strong>{{ .DurationText }}</strong></div>
794 <div class="pill version">v{{ .Version }}</div>
795 </div>
796 </header>
797
798 <!-- Section 1: Executive Summary & Gauge -->
799 <div class="grid-2">
800 <!-- Semi-Circle Gauge Card -->
801 <div class="card gauge-box">
802 <div class="card-header" style="width: 100%;">
803 <h2>
804 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
805 <circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
806 </svg>
807 Egress Exposure Index
808 </h2>
809 </div>
810 <svg class="gauge-svg" viewBox="0 0 300 160">
811 <defs>
812 <linearGradient id="gaugeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
813 <stop offset="0%" stop-color="#22c55e"/>
814 <stop offset="25%" stop-color="#38bdf8"/>
815 <stop offset="50%" stop-color="#f59e0b"/>
816 <stop offset="75%" stop-color="#f97316"/>
817 <stop offset="100%" stop-color="#ef4444"/>
818 </linearGradient>
819 <filter id="gaugeGlow" x="-20%" y="-20%" width="140%" height="140%">
820 <feGaussianBlur stdDeviation="3" result="blur" />
821 <feComposite in="SourceGraphic" in2="blur" operator="over" />
822 </filter>
823 </defs>
824 <!-- Background Track -->
825 <path class="gauge-bg" d="M 50 140 A 100 100 0 0 1 250 140" />
826 <!-- Dynamic Colored Track -->
827 <path class="gauge-val" stroke="url(#gaugeGrad)" filter="url(#gaugeGlow)"
828 d="M 50 140 A 100 100 0 0 1 250 140"
829 stroke-dasharray="314.16"
830 stroke-dashoffset="{{ calcGaugeOffset .Summary.EEIScore }}" />
831 <!-- Needle rotating from base 0-degree vector (238, 140) around pivot (150, 140) -->
832 <g class="gauge-needle-group" transform="rotate({{ calcNeedleAngle .Summary.EEIScore }} 150 140)">
833 <polygon class="gauge-needle-poly" points="144,138.2 234,139.3 238,140 234,140.7 144,141.8 138,140" />
834 <circle cx="150" cy="140" r="7" fill="#0f172a" stroke="#ffffff" stroke-width="2" />
835 <circle cx="150" cy="140" r="3" fill="#38bdf8" />
836 </g>
837 </svg>
838 <div class="gauge-score">{{ printf "%.1f" .Summary.EEIScore }}%</div>
839 {{ if eq .Summary.Posture "Hardened" }}
840 <div class="posture-badge posture-hardened">Hardened Perimeter</div>
841 {{ else if eq .Summary.Posture "Moderate" }}
842 <div class="posture-badge posture-moderate">Moderate Exposure</div>
843 {{ else }}
844 <div class="posture-badge posture-critical">Critical Exposure</div>
845 {{ end }}
846
847 <div class="category-bars">
848 <div class="cat-item">
849 <div class="cat-head"><span>L4 Ports (Sport 30%)</span><strong>{{ printf "%.1f" .Summary.Sport }}%</strong></div>
850 <div class="cat-bar-bg"><div class="cat-bar-fill" style="width: {{ .Summary.Sport }}%; background: var(--color-green);"></div></div>
851 </div>
852 <div class="cat-item">
853 <div class="cat-head"><span>L7 Protocols (Sproto 40%)</span><strong>{{ printf "%.1f" .Summary.Sproto }}%</strong></div>
854 <div class="cat-bar-bg"><div class="cat-bar-fill" style="width: {{ .Summary.Sproto }}%; background: var(--color-sky);"></div></div>
855 </div>
856 <div class="cat-item">
857 <div class="cat-head"><span>DLP Efficacy (Sdlp 30%)</span><strong>{{ printf "%.1f" .Summary.Sdlp }}%</strong></div>
858 <div class="cat-bar-bg"><div class="cat-bar-fill" style="width: {{ .Summary.Sdlp }}%; background: var(--color-amber);"></div></div>
859 </div>
860 </div>
861 </div>
862
863 <!-- KPI Summary Grid -->
864 <div>
865 <div class="grid-4" style="margin-bottom: 16px;">
866 <div class="card kpi-card">
867 <div class="kpi-title">Probes Tested</div>
868 <div class="kpi-val">{{ .Summary.TotalProbes }}</div>
869 <div class="kpi-sub">{{ .Summary.SuccessCount }} successful / open</div>
870 </div>
871 <div class="card kpi-card">
872 <div class="kpi-title">L4 Blocked</div>
873 <div class="kpi-val" style="color: var(--color-green);">{{ .Summary.BlockedL4 }}</div>
874 <div class="kpi-sub">Firewall drops & resets</div>
875 </div>
876 <div class="card kpi-card">
877 <div class="kpi-title">L7 Blocked</div>
878 <div class="kpi-val" style="color: var(--color-sky);">{{ .Summary.BlockedL7 }}</div>
879 <div class="kpi-sub">Proxy & DPI rejections</div>
880 </div>
881 <div class="card kpi-card">
882 <div class="kpi-title">DLP Intercepted</div>
883 <div class="kpi-val" style="color: var(--color-amber);">{{ .Summary.BlockedDLP }}</div>
884 <div class="kpi-sub">Content filters triggered</div>
885 </div>
886 </div>
887 <div class="grid-4">
888 <div class="card kpi-card">
889 <div class="kpi-title">Exfiltrated Volume</div>
890 <div class="kpi-val">{{ formatBytes .Summary.TotalBytesSent }}</div>
891 <div class="kpi-sub">{{ formatBytes .Summary.TotalBytesRecv }} received back</div>
892 </div>
893 <div class="card kpi-card">
894 <div class="kpi-title">Average Latency</div>
895 <div class="kpi-val">{{ formatMS .Summary.AvgLatencyMS }}</div>
896 <div class="kpi-sub">Per probe round-trip</div>
897 </div>
898 <div class="card kpi-card">
899 <div class="kpi-title">Credentials Vault</div>
900 <div class="kpi-val" style="color: {{ if gt .Summary.TotalCredentials 0 }}var(--color-rose){{ else }}var(--color-green){{ end }};">
901 {{ .Summary.TotalCredentials }}
902 </div>
903 <div class="kpi-sub">Harvested hashes & keys</div>
904 </div>
905 <div class="card kpi-card">
906 <div class="kpi-title">Findings Total</div>
907 <div class="kpi-val">{{ len .Findings }}</div>
908 <div class="kpi-sub">SARIF rules flagged</div>
909 </div>
910 </div>
911 </div>
912 </div>
913
914 <!-- Section 2: Layer 4 Egress Port Matrix Heatmap -->
915 <div class="card" style="margin-bottom: 24px;">
916 <div class="card-header">
917 <h2>
918 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
919 <rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
920 <rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
921 <line x1="6" y1="6" x2="6.01" y2="6"/>
922 <line x1="6" y1="18" x2="6.01" y2="18"/>
923 </svg>
924 Layer 4 Egress Port Heatmap
925 </h2>
926 <div class="heatmap-controls">
927 <input type="text" id="portSearch" class="search-input" placeholder="Search port (e.g. 443)..." onkeyup="filterHeatmap()">
928 <button class="btn-filter active" onclick="setHeatmapFilter('all', this)">All</button>
929 <button class="btn-filter" onclick="setHeatmapFilter('EXFIL_SUCCESS', this)">Open (Success)</button>
930 <button class="btn-filter" onclick="setHeatmapFilter('BLOCKED_L4', this)">Blocked L4</button>
931 <button class="btn-filter" onclick="setHeatmapFilter('BLOCKED_L7', this)">Blocked L7</button>
932 <button class="btn-filter" onclick="setHeatmapFilter('BLOCKED_DLP', this)">Blocked DLP</button>
933 </div>
934 </div>
935 <div class="heatmap-grid" id="heatmapGrid">
936 {{ range .Probes }}
937 {{ if gt .TargetPort 0 }}
938 <div class="port-cell {{ verdictClass .Verdict }}" data-port="{{ .TargetPort }}" data-verdict="{{ .Verdict }}" onclick="jumpToProbe('{{ .ID }}')">
939 {{ .TargetPort }}
940 <div class="tooltip">
941 <strong>Port {{ .TargetPort }}/{{ .Protocol }}</strong><br>
942 Verdict: {{ .Verdict }}<br>
943 Latency: {{ formatMS (divDurMS .Timestamps.TotalDuration) }}<br>
944 {{ if .ErrorMessage }}Error: {{ .ErrorMessage }}{{ end }}
945 </div>
946 </div>
947 {{ end }}
948 {{ end }}
949 </div>
950 </div>
951
952 <!-- Section 3: Layer 7 Protocol & uTLS Inspection Breakdown -->
953 <div class="grid-2" style="margin-bottom: 24px;">
954 <!-- Protocol Matrix -->
955 <div class="card">
956 <div class="card-header">
957 <h2>
958 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
959 <polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
960 </svg>
961 Layer 7 Protocol Smuggling
962 </h2>
963 </div>
964 <div class="table-wrap">
965 <table>
966 <thead>
967 <tr><th>Protocol</th><th>Port</th><th>Verdict</th><th>Latency</th></tr>
968 </thead>
969 <tbody>
970 {{ range .Probes }}
971 {{ if and (ne .Protocol "tcp") (ne .Protocol "udp") }}
972 <tr>
973 <td><strong>{{ .Protocol }}</strong></td>
974 <td>{{ .TargetPort }}</td>
975 <td>{{ verdictBadge .Verdict }}</td>
976 <td>{{ formatMS (divDurMS .Timestamps.TotalDuration) }}</td>
977 </tr>
978 {{ end }}
979 {{ end }}
980 </tbody>
981 </table>
982 </div>
983 </div>
984
985 <!-- uTLS JA3/JA4 Fingerprint Table -->
986 <div class="card">
987 <div class="card-header">
988 <h2>
989 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
990 <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
991 <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
992 </svg>
993 uTLS & JA4 Fingerprints
994 </h2>
995 </div>
996 <div class="table-wrap">
997 <table>
998 <thead>
999 <tr><th>Profile</th><th>JA4 Fingerprint</th><th>Verdict</th></tr>
1000 </thead>
1001 <tbody>
1002 {{ range .Probes }}
1003 {{ if .JA4String }}
1004 <tr>
1005 <td><strong>{{ .TLSProfile }}</strong></td>
1006 <td>
1007 <code class="mono" style="font-size: 11px;">{{ truncateHash .JA4String 24 }}</code>
1008 <button class="btn-copy" style="position:static; margin-left: 6px;" onclick="copyText('{{ .JA4String }}', this)">Copy</button>
1009 </td>
1010 <td>{{ verdictBadge .Verdict }}</td>
1011 </tr>
1012 {{ end }}
1013 {{ end }}
1014 </tbody>
1015 </table>
1016 </div>
1017 </div>
1018 </div>
1019
1020 <!-- Section 4: DLP & Steganography Efficacy Scorecard -->
1021 <div class="card" style="margin-bottom: 24px;">
1022 <div class="card-header">
1023 <h2>
1024 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
1025 <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
1026 </svg>
1027 Synthetic DLP & Steganography Efficacy Scorecard
1028 </h2>
1029 </div>
1030 <div class="table-wrap">
1031 <table class="matrix-table">
1032 <thead>
1033 <tr>
1034 <th>Payload Type</th>
1035 <th>Carrier</th>
1036 <th>Volume</th>
1037 <th>Throughput</th>
1038 <th>Payload SHA-256</th>
1039 <th>Verdict</th>
1040 </tr>
1041 </thead>
1042 <tbody>
1043 {{ range .Probes }}
1044 {{ if .PayloadType }}
1045 <tr>
1046 <td><strong>{{ .PayloadType }}</strong></td>
1047 <td>{{ .CarrierType }}</td>
1048 <td>{{ formatBytes .TargetVolume }}</td>
1049 <td>{{ printf "%.1f" .ThroughputBps }} KB/s</td>
1050 <td><code class="mono">{{ truncateHash .PayloadHash 16 }}</code></td>
1051 <td>{{ verdictBadge .Verdict }}</td>
1052 </tr>
1053 {{ end }}
1054 {{ end }}
1055 </tbody>
1056 </table>
1057 </div>
1058 </div>
1059
1060 <!-- Section 5: Captured Credentials Vault -->
1061 <div class="card" style="margin-bottom: 24px;">
1062 <div class="card-header">
1063 <h2>
1064 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
1065 <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>
1066 </svg>
1067 Captured Credentials Vault (Hashcat Mode 5600)
1068 </h2>
1069 </div>
1070 {{ if eq (len .Credentials) 0 }}
1071 <div class="empty-state">
1072 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
1073 <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
1074 </svg>
1075 <p>No credentials or NetNTLMv2 authentication hashes were exposed during assessment.</p>
1076 </div>
1077 {{ else }}
1078 {{ range .Credentials }}
1079 <div class="cred-card">
1080 <div class="cred-header">
1081 <div>
1082 <strong>{{ .Protocol }}</strong> &bull; User: <span class="mono">{{ .Username }}</span> &bull; Domain: <span class="mono">{{ .Domain }}</span>
1083 </div>
1084 <button class="btn-copy" style="position:static;" onclick="copyText('{{ .Hash }}', this)">Copy Hashcat Format</button>
1085 </div>
1086 <div class="hashcat-syntax mono">
1087 {{ .Hash }}
1088 </div>
1089 </div>
1090 {{ end }}
1091 {{ end }}
1092 </div>
1093
1094 <!-- Section 6: Deep Chronological Forensic Event Log -->
1095 <div class="card" style="margin-bottom: 24px;">
1096 <div class="card-header">
1097 <h2>
1098 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
1099 <line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/>
1100 <line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>
1101 </svg>
1102 Forensic Event Log
1103 </h2>
1104 <div>
1105 <input type="text" id="logSearch" class="search-input" placeholder="Search event log..." onkeyup="filterLogTable()">
1106 </div>
1107 </div>
1108 <div class="table-wrap">
1109 <table id="logTable">
1110 <thead>
1111 <tr>
1112 <th>Time (UTC)</th>
1113 <th>Stage</th>
1114 <th>Proto</th>
1115 <th>Port</th>
1116 <th>Verdict</th>
1117 <th>Latency</th>
1118 <th>Action</th>
1119 </tr>
1120 </thead>
1121 <tbody>
1122 {{ range $i, $p := .Probes }}
1123 <tr id="probe-{{ $p.ID }}">
1124 <td>{{ formatRFC3339MS $p.Timestamp }}</td>
1125 <td><span class="mono">{{ $p.Stage }}</span></td>
1126 <td><strong>{{ $p.Protocol }}</strong></td>
1127 <td>{{ $p.TargetPort }}</td>
1128 <td>{{ verdictBadge $p.Verdict }}</td>
1129 <td>{{ formatMS (divDurMS $p.Timestamps.TotalDuration) }}</td>
1130 <td>
1131 <button class="btn-filter" style="padding: 3px 10px; font-size: 11px;" onclick="toggleDrawer('drawer-{{ $i }}')">Inspect</button>
1132 </td>
1133 </tr>
1134 <!-- Expandable Forensic Drawer -->
1135 <tr id="drawer-{{ $i }}" class="drawer-row">
1136 <td colspan="7">
1137 <div class="drawer-content">
1138 <h4 style="margin-bottom: 8px; font-size: 12px; color: var(--text-highlight);">Forensic Timing Breakdown (ms)</h4>
1139 <div class="waterfall-bar-group">
1140 <div class="waterfall-tcp" style="width: 25%;" title="TCP Handshake"></div>
1141 <div class="waterfall-tls" style="width: 35%;" title="TLS Handshake"></div>
1142 <div class="waterfall-transfer" style="width: 40%;" title="Payload Transfer"></div>
1143 </div>
1144 <div style="display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); margin-bottom: 12px; flex-wrap: wrap;">
1145 <span>TCP: <strong>{{ formatMS (divDurMS $p.Timestamps.TCPDuration) }}</strong></span>
1146 <span>TLS: <strong>{{ formatMS (divDurMS $p.Timestamps.TLSDuration) }}</strong></span>
1147 <span>Transfer: <strong>{{ formatMS (divDurMS $p.Timestamps.TransferDuration) }}</strong></span>
1148 <span>Total: <strong>{{ formatMS (divDurMS $p.Timestamps.TotalDuration) }}</strong></span>
1149 </div>
1150 {{ if $p.PayloadHash }}
1151 <div style="font-size: 11px; margin-bottom: 8px;">
1152 Payload SHA-256: <code class="mono">{{ $p.PayloadHash }}</code>
1153 </div>
1154 {{ end }}
1155 {{ if $p.RawEvidence }}
1156 <h4 style="margin-bottom: 6px; font-size: 12px; color: var(--text-highlight);">Bounded Raw Evidence Hex Dump</h4>
1157 <div class="code-block mono" style="max-height: 180px;">{{ $p.RawEvidence }}</div>
1158 {{ end }}
1159 </div>
1160 </td>
1161 </tr>
1162 {{ end }}
1163 </tbody>
1164 </table>
1165 </div>
1166 </div>
1167
1168 <!-- Section 7: Multi-Vendor Firewall Remediation Rules -->
1169 <div class="card">
1170 <div class="card-header">
1171 <h2>
1172 <svg viewBox="0 0 24 24" fill="none" stroke-width="2">
1173 <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
1174 <path d="M9 12l2 2 4-4"/>
1175 </svg>
1176 Firewall Remediation Rules
1177 </h2>
1178 </div>
1179 <div class="tabs">
1180 <button class="tab-btn active" onclick="showTab('iptables', this)">Linux iptables</button>
1181 <button class="tab-btn" onclick="showTab('nftables', this)">Linux nftables</button>
1182 <button class="tab-btn" onclick="showTab('panos', this)">Palo Alto (PAN-OS)</button>
1183 <button class="tab-btn" onclick="showTab('fortigate', this)">Fortinet FortiGate</button>
1184 <button class="tab-btn" onclick="showTab('cisco_asa', this)">Cisco ASA</button>
1185 </div>
1186
1187 <div id="tab-iptables" class="tab-pane active">
1188 <div style="position: relative;">
1189 <button class="btn-copy" onclick="copyBlock('code-iptables', this)">Copy iptables</button>
1190 <div id="code-iptables" class="code-block mono">{{ .Remediation.IPTablesRules }}</div>
1191 </div>
1192 </div>
1193 <div id="tab-nftables" class="tab-pane">
1194 <div style="position: relative;">
1195 <button class="btn-copy" onclick="copyBlock('code-nftables', this)">Copy nftables</button>
1196 <div id="code-nftables" class="code-block mono">{{ .Remediation.NFTablesRules }}</div>
1197 </div>
1198 </div>
1199 <div id="tab-panos" class="tab-pane">
1200 <div style="position: relative;">
1201 <button class="btn-copy" onclick="copyBlock('code-panos', this)">Copy PAN-OS</button>
1202 <div id="code-panos" class="code-block mono">{{ .Remediation.PANOSRules }}</div>
1203 </div>
1204 </div>
1205 <div id="tab-fortigate" class="tab-pane">
1206 <div style="position: relative;">
1207 <button class="btn-copy" onclick="copyBlock('code-fortigate', this)">Copy FortiGate</button>
1208 <div id="code-fortigate" class="code-block mono">{{ .Remediation.FortiGateRules }}</div>
1209 </div>
1210 </div>
1211 <div id="tab-cisco_asa" class="tab-pane">
1212 <div style="position: relative;">
1213 <button class="btn-copy" onclick="copyBlock('code-cisco_asa', this)">Copy Cisco ASA</button>
1214 <div id="code-cisco_asa" class="code-block mono">{{ .Remediation.CiscoASARules }}</div>
1215 </div>
1216 </div>
1217 </div>
1218
1219</div>
1220
1221<!-- Vanilla JS Interactions (Zero external JS, completely self-contained) -->
1222<script>
1223 function toggleDrawer(id) {
1224 var el = document.getElementById(id);
1225 if (!el) return;
1226 if (el.classList.contains('open')) {
1227 el.classList.remove('open');
1228 } else {
1229 el.classList.add('open');
1230 }
1231 }
1232
1233 function showTab(vendor, btn) {
1234 document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
1235 document.querySelectorAll('.tab-pane').forEach(function(p) { p.classList.remove('active'); });
1236 btn.classList.add('active');
1237 var pane = document.getElementById('tab-' + vendor);
1238 if (pane) pane.classList.add('active');
1239 }
1240
1241 function copyText(text, btn) {
1242 navigator.clipboard.writeText(text).then(function() {
1243 var orig = btn.innerText;
1244 btn.innerText = 'Copied! ✓';
1245 btn.style.color = '#10b981';
1246 btn.style.borderColor = '#10b981';
1247 setTimeout(function() {
1248 btn.innerText = orig;
1249 btn.style.color = '';
1250 btn.style.borderColor = '';
1251 }, 1600);
1252 });
1253 }
1254
1255 function copyBlock(id, btn) {
1256 var el = document.getElementById(id);
1257 if (el) copyText(el.innerText, btn);
1258 }
1259
1260 function setHeatmapFilter(verdict, btn) {
1261 document.querySelectorAll('.heatmap-controls .btn-filter').forEach(function(b) { b.classList.remove('active'); });
1262 btn.classList.add('active');
1263 var cells = document.querySelectorAll('.port-cell');
1264 cells.forEach(function(cell) {
1265 if (verdict === 'all' || cell.getAttribute('data-verdict') === verdict) {
1266 cell.style.display = 'flex';
1267 } else {
1268 cell.style.display = 'none';
1269 }
1270 });
1271 }
1272
1273 function filterHeatmap() {
1274 var val = document.getElementById('portSearch').value.trim();
1275 var cells = document.querySelectorAll('.port-cell');
1276 cells.forEach(function(cell) {
1277 if (!val || cell.getAttribute('data-port').indexOf(val) !== -1) {
1278 cell.style.display = 'flex';
1279 } else {
1280 cell.style.display = 'none';
1281 }
1282 });
1283 }
1284
1285 function jumpToProbe(id) {
1286 var row = document.getElementById('probe-' + id);
1287 if (row) {
1288 row.scrollIntoView({ behavior: 'smooth', block: 'center' });
1289 row.classList.add('selected');
1290 setTimeout(function() { row.classList.remove('selected'); }, 2000);
1291 }
1292 }
1293
1294 function filterLogTable() {
1295 var val = document.getElementById('logSearch').value.toLowerCase();
1296 var rows = document.querySelectorAll('#logTable tbody tr:not(.drawer-row)');
1297 rows.forEach(function(row) {
1298 var text = row.innerText.toLowerCase();
1299 row.style.display = (!val || text.indexOf(val) !== -1) ? '' : 'none';
1300 });
1301 }
1302</script>
1303</body>
1304</html>