aura-security 0.4.9 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/package.json +1 -1
- package/visualizer/docs.html +146 -5
- package/visualizer/index-minimal.html +332 -8
- package/visualizer/index.html +4 -256
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
|
24
24
|
import { join, resolve, basename } from 'path';
|
|
25
25
|
import { spawnSync } from 'child_process';
|
|
26
26
|
const AURA_URL = process.env.AURA_URL ?? 'http://127.0.0.1:3000';
|
|
27
|
-
const VERSION = '0.
|
|
27
|
+
const VERSION = '0.5.1';
|
|
28
28
|
// ANSI colors for terminal output
|
|
29
29
|
const colors = {
|
|
30
30
|
reset: '\x1b[0m',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-security",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Deterministic security auditing engine with optional AI advisory layer. Run as CLI, CI step, or service. AI does not make enforcement decisions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/visualizer/docs.html
CHANGED
|
@@ -349,6 +349,103 @@
|
|
|
349
349
|
margin: 0;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
/* Screenshot Comparison */
|
|
353
|
+
.screenshot-comparison {
|
|
354
|
+
display: grid;
|
|
355
|
+
grid-template-columns: 1fr 1fr;
|
|
356
|
+
gap: 2rem;
|
|
357
|
+
margin: 2rem 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.screenshot-card {
|
|
361
|
+
background: var(--bg-card);
|
|
362
|
+
border-radius: 16px;
|
|
363
|
+
overflow: hidden;
|
|
364
|
+
border: 1px solid var(--border);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.screenshot-label {
|
|
368
|
+
padding: 1rem 1.25rem;
|
|
369
|
+
display: flex;
|
|
370
|
+
align-items: center;
|
|
371
|
+
gap: 0.75rem;
|
|
372
|
+
font-weight: 600;
|
|
373
|
+
font-size: 0.9375rem;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.screenshot-card.bad .screenshot-label {
|
|
377
|
+
background: rgba(239, 68, 68, 0.1);
|
|
378
|
+
border-bottom: 2px solid var(--critical);
|
|
379
|
+
color: var(--critical);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.screenshot-card.good .screenshot-label {
|
|
383
|
+
background: rgba(34, 197, 94, 0.1);
|
|
384
|
+
border-bottom: 2px solid var(--success);
|
|
385
|
+
color: var(--success);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.screenshot-icon {
|
|
389
|
+
font-size: 1.25rem;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.screenshot-container {
|
|
393
|
+
position: relative;
|
|
394
|
+
background: #0d1117;
|
|
395
|
+
min-height: 280px;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.screenshot-container img {
|
|
399
|
+
width: 100%;
|
|
400
|
+
height: auto;
|
|
401
|
+
display: block;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.screenshot-placeholder {
|
|
405
|
+
display: flex;
|
|
406
|
+
flex-direction: column;
|
|
407
|
+
align-items: center;
|
|
408
|
+
justify-content: center;
|
|
409
|
+
height: 280px;
|
|
410
|
+
text-align: center;
|
|
411
|
+
padding: 2rem;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.screenshot-placeholder.bad {
|
|
415
|
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), rgba(249, 115, 22, 0.1));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.screenshot-placeholder.good {
|
|
419
|
+
background: linear-gradient(135deg, rgba(34, 197, 94, 0.1), rgba(6, 182, 212, 0.1));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.placeholder-icon {
|
|
423
|
+
font-size: 4rem;
|
|
424
|
+
margin-bottom: 1rem;
|
|
425
|
+
opacity: 0.8;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.placeholder-text {
|
|
429
|
+
color: var(--text-secondary);
|
|
430
|
+
font-size: 1rem;
|
|
431
|
+
line-height: 1.5;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.placeholder-text small {
|
|
435
|
+
opacity: 0.7;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.screenshot-caption {
|
|
439
|
+
padding: 1rem 1.25rem;
|
|
440
|
+
font-size: 0.8125rem;
|
|
441
|
+
color: var(--text-secondary);
|
|
442
|
+
border-top: 1px solid var(--border);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.screenshot-caption strong {
|
|
446
|
+
color: var(--text);
|
|
447
|
+
}
|
|
448
|
+
|
|
352
449
|
/* Comparison */
|
|
353
450
|
.comparison {
|
|
354
451
|
display: grid;
|
|
@@ -555,6 +652,9 @@
|
|
|
555
652
|
.comparison {
|
|
556
653
|
grid-template-columns: 1fr;
|
|
557
654
|
}
|
|
655
|
+
.screenshot-comparison {
|
|
656
|
+
grid-template-columns: 1fr;
|
|
657
|
+
}
|
|
558
658
|
.slop-grid {
|
|
559
659
|
grid-template-columns: 1fr;
|
|
560
660
|
}
|
|
@@ -797,6 +897,47 @@
|
|
|
797
897
|
<h2>Good vs Bad: Real Examples</h2>
|
|
798
898
|
<p>Here's what secure and insecure repositories look like when scanned with aurasecurity:</p>
|
|
799
899
|
|
|
900
|
+
<!-- 3D Visualizer Screenshots -->
|
|
901
|
+
<h3>3D Visualization Comparison</h3>
|
|
902
|
+
<p>See the difference at a glance - red means danger, green means safe:</p>
|
|
903
|
+
|
|
904
|
+
<div class="screenshot-comparison">
|
|
905
|
+
<div class="screenshot-card bad">
|
|
906
|
+
<div class="screenshot-label">
|
|
907
|
+
<span class="screenshot-icon bad">❌</span>
|
|
908
|
+
<span>Vulnerable Repository</span>
|
|
909
|
+
</div>
|
|
910
|
+
<div class="screenshot-container">
|
|
911
|
+
<img src="https://app.aurasecurity.io/screenshots/bad-repo.png" alt="3D view of vulnerable repository with red nodes" onerror="this.parentElement.innerHTML='<div class=\'screenshot-placeholder bad\'><div class=\'placeholder-icon\'>🔴</div><div class=\'placeholder-text\'>juice-shop scan result<br><small>Red node = 9 secrets found</small></div></div>'">
|
|
912
|
+
</div>
|
|
913
|
+
<div class="screenshot-caption">
|
|
914
|
+
<strong>juice-shop</strong> - Multiple red severity indicators orbiting the node. Each red sphere represents a critical or high finding.
|
|
915
|
+
</div>
|
|
916
|
+
</div>
|
|
917
|
+
|
|
918
|
+
<div class="screenshot-card good">
|
|
919
|
+
<div class="screenshot-label">
|
|
920
|
+
<span class="screenshot-icon good">✅</span>
|
|
921
|
+
<span>Clean Repository</span>
|
|
922
|
+
</div>
|
|
923
|
+
<div class="screenshot-container">
|
|
924
|
+
<img src="https://app.aurasecurity.io/screenshots/good-repo.png" alt="3D view of clean repository with green node" onerror="this.parentElement.innerHTML='<div class=\'screenshot-placeholder good\'><div class=\'placeholder-icon\'>🟢</div><div class=\'placeholder-text\'>aurasecurity scan result<br><small>Green node = 0 issues</small></div></div>'">
|
|
925
|
+
</div>
|
|
926
|
+
<div class="screenshot-caption">
|
|
927
|
+
<strong>aurasecurity</strong> - Clean green node with no severity indicators. This is your target state.
|
|
928
|
+
</div>
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
|
|
932
|
+
<div class="info-box">
|
|
933
|
+
<h5>🎨 Reading the 3D View</h5>
|
|
934
|
+
<p><strong>Node Color:</strong> Red = critical issues, Orange = high, Yellow = medium, Green = clean<br>
|
|
935
|
+
<strong>Orbiting Shapes:</strong> Each shape around a node represents a finding category. More shapes = more issues.<br>
|
|
936
|
+
<strong>Click to Drill Down:</strong> Click any node to see severity breakdown, click severity to see individual findings.</p>
|
|
937
|
+
</div>
|
|
938
|
+
|
|
939
|
+
<!-- Stats Comparison Cards -->
|
|
940
|
+
<h3>Scan Statistics</h3>
|
|
800
941
|
<div class="comparison">
|
|
801
942
|
<div class="comparison-card bad">
|
|
802
943
|
<div class="comparison-header">
|
|
@@ -815,7 +956,7 @@
|
|
|
815
956
|
</div>
|
|
816
957
|
<div class="comparison-stat">
|
|
817
958
|
<span class="stat-label">High Findings</span>
|
|
818
|
-
<span class="stat-value high">
|
|
959
|
+
<span class="stat-value high">8 (API Keys)</span>
|
|
819
960
|
</div>
|
|
820
961
|
<div class="comparison-stat">
|
|
821
962
|
<span class="stat-label">Medium Findings</span>
|
|
@@ -827,7 +968,7 @@
|
|
|
827
968
|
</div>
|
|
828
969
|
<div class="comparison-stat">
|
|
829
970
|
<span class="stat-label">Total Issues</span>
|
|
830
|
-
<span class="stat-value critical">
|
|
971
|
+
<span class="stat-value critical">24</span>
|
|
831
972
|
</div>
|
|
832
973
|
</div>
|
|
833
974
|
</div>
|
|
@@ -835,13 +976,13 @@
|
|
|
835
976
|
<div class="comparison-card good">
|
|
836
977
|
<div class="comparison-header">
|
|
837
978
|
<span>✅</span>
|
|
838
|
-
<span>
|
|
979
|
+
<span>aurasecurity (Clean)</span>
|
|
839
980
|
</div>
|
|
840
981
|
<div class="comparison-body">
|
|
841
|
-
<p style="font-size: 0.875rem; margin-bottom: 1rem;">
|
|
982
|
+
<p style="font-size: 0.875rem; margin-bottom: 1rem;">Our own repository - we practice what we preach.</p>
|
|
842
983
|
<div class="comparison-stat">
|
|
843
984
|
<span class="stat-label">Scan Time</span>
|
|
844
|
-
<span class="stat-value">
|
|
985
|
+
<span class="stat-value">4.10s</span>
|
|
845
986
|
</div>
|
|
846
987
|
<div class="comparison-stat">
|
|
847
988
|
<span class="stat-label">Critical Findings</span>
|
|
@@ -436,13 +436,6 @@
|
|
|
436
436
|
overflow-y: auto;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
/* Mobile: hide details panel */
|
|
440
|
-
@media (max-width: 768px) {
|
|
441
|
-
#details-panel {
|
|
442
|
-
display: none;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
439
|
.selected-repo-header {
|
|
447
440
|
display: none;
|
|
448
441
|
padding: 12px;
|
|
@@ -584,6 +577,152 @@
|
|
|
584
577
|
color: var(--text-primary);
|
|
585
578
|
}
|
|
586
579
|
|
|
580
|
+
/* Filter toggles */
|
|
581
|
+
.filter-section {
|
|
582
|
+
margin-bottom: 16px;
|
|
583
|
+
padding-bottom: 12px;
|
|
584
|
+
border-bottom: 1px solid var(--border);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.filter-label {
|
|
588
|
+
font-size: 11px;
|
|
589
|
+
color: var(--text-muted);
|
|
590
|
+
margin-bottom: 8px;
|
|
591
|
+
text-transform: uppercase;
|
|
592
|
+
letter-spacing: 0.5px;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.filter-toggles {
|
|
596
|
+
display: flex;
|
|
597
|
+
flex-wrap: wrap;
|
|
598
|
+
gap: 6px;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.filter-toggle {
|
|
602
|
+
display: flex;
|
|
603
|
+
align-items: center;
|
|
604
|
+
gap: 4px;
|
|
605
|
+
padding: 4px 10px;
|
|
606
|
+
background: var(--bg-primary);
|
|
607
|
+
border: 1px solid var(--border);
|
|
608
|
+
border-radius: 4px;
|
|
609
|
+
font-size: 11px;
|
|
610
|
+
font-family: inherit;
|
|
611
|
+
color: var(--text-secondary);
|
|
612
|
+
cursor: pointer;
|
|
613
|
+
transition: all 0.15s;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.filter-toggle:hover {
|
|
617
|
+
border-color: var(--border-light);
|
|
618
|
+
color: var(--text-primary);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.filter-toggle.active {
|
|
622
|
+
background: var(--accent-subtle);
|
|
623
|
+
border-color: var(--accent);
|
|
624
|
+
color: var(--accent);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.filter-toggle.active.critical {
|
|
628
|
+
background: var(--critical-subtle);
|
|
629
|
+
border-color: var(--critical);
|
|
630
|
+
color: var(--critical);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.filter-toggle.active.high {
|
|
634
|
+
background: var(--high-subtle);
|
|
635
|
+
border-color: var(--high);
|
|
636
|
+
color: var(--high);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.filter-toggle.active.medium {
|
|
640
|
+
background: var(--warning-subtle);
|
|
641
|
+
border-color: var(--warning);
|
|
642
|
+
color: var(--warning);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.filter-toggle.active.low {
|
|
646
|
+
background: var(--success-subtle);
|
|
647
|
+
border-color: var(--success);
|
|
648
|
+
color: var(--success);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.filter-toggle .toggle-dot {
|
|
652
|
+
width: 6px;
|
|
653
|
+
height: 6px;
|
|
654
|
+
border-radius: 50%;
|
|
655
|
+
background: currentColor;
|
|
656
|
+
opacity: 0.6;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.filter-toggle.active .toggle-dot {
|
|
660
|
+
opacity: 1;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.filter-toggle .toggle-count {
|
|
664
|
+
font-weight: 600;
|
|
665
|
+
margin-left: 2px;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* Badge generator */
|
|
669
|
+
.badge-section {
|
|
670
|
+
margin-top: 16px;
|
|
671
|
+
padding-top: 12px;
|
|
672
|
+
border-top: 1px solid var(--border);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.badge-preview {
|
|
676
|
+
display: flex;
|
|
677
|
+
align-items: center;
|
|
678
|
+
gap: 8px;
|
|
679
|
+
margin-bottom: 12px;
|
|
680
|
+
padding: 12px;
|
|
681
|
+
background: var(--bg-primary);
|
|
682
|
+
border-radius: 6px;
|
|
683
|
+
justify-content: center;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.badge-preview img {
|
|
687
|
+
height: 20px;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.badge-code {
|
|
691
|
+
background: var(--bg-primary);
|
|
692
|
+
border: 1px solid var(--border);
|
|
693
|
+
border-radius: 6px;
|
|
694
|
+
padding: 10px;
|
|
695
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
696
|
+
font-size: 11px;
|
|
697
|
+
color: var(--text-secondary);
|
|
698
|
+
word-break: break-all;
|
|
699
|
+
margin-bottom: 8px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.badge-actions {
|
|
703
|
+
display: flex;
|
|
704
|
+
gap: 6px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.badge-actions .btn {
|
|
708
|
+
width: auto;
|
|
709
|
+
padding: 6px 12px;
|
|
710
|
+
font-size: 11px;
|
|
711
|
+
margin-top: 0;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.copy-success {
|
|
715
|
+
color: var(--success);
|
|
716
|
+
font-size: 11px;
|
|
717
|
+
margin-left: 8px;
|
|
718
|
+
opacity: 0;
|
|
719
|
+
transition: opacity 0.2s;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.copy-success.visible {
|
|
723
|
+
opacity: 1;
|
|
724
|
+
}
|
|
725
|
+
|
|
587
726
|
/* Empty state */
|
|
588
727
|
.empty-state {
|
|
589
728
|
text-align: center;
|
|
@@ -729,12 +868,63 @@
|
|
|
729
868
|
<div class="selected-repo-path" id="selectedRepoPath">-</div>
|
|
730
869
|
</div>
|
|
731
870
|
|
|
871
|
+
<!-- Filter Toggles -->
|
|
872
|
+
<div class="filter-section" id="filterSection" style="display: none;">
|
|
873
|
+
<div class="filter-label">Filter by Severity</div>
|
|
874
|
+
<div class="filter-toggles" id="severityFilters">
|
|
875
|
+
<button class="filter-toggle active critical" data-filter="critical" onclick="toggleFilter('severity', 'critical')">
|
|
876
|
+
<span class="toggle-dot"></span>Critical <span class="toggle-count" id="countCritical">0</span>
|
|
877
|
+
</button>
|
|
878
|
+
<button class="filter-toggle active high" data-filter="high" onclick="toggleFilter('severity', 'high')">
|
|
879
|
+
<span class="toggle-dot"></span>High <span class="toggle-count" id="countHigh">0</span>
|
|
880
|
+
</button>
|
|
881
|
+
<button class="filter-toggle active medium" data-filter="medium" onclick="toggleFilter('severity', 'medium')">
|
|
882
|
+
<span class="toggle-dot"></span>Medium <span class="toggle-count" id="countMedium">0</span>
|
|
883
|
+
</button>
|
|
884
|
+
<button class="filter-toggle active low" data-filter="low" onclick="toggleFilter('severity', 'low')">
|
|
885
|
+
<span class="toggle-dot"></span>Low <span class="toggle-count" id="countLow">0</span>
|
|
886
|
+
</button>
|
|
887
|
+
</div>
|
|
888
|
+
<div class="filter-label" style="margin-top: 12px;">Filter by Type</div>
|
|
889
|
+
<div class="filter-toggles" id="typeFilters">
|
|
890
|
+
<button class="filter-toggle active" data-filter="SECRET" onclick="toggleFilter('type', 'SECRET')">
|
|
891
|
+
<span class="toggle-dot"></span>Secrets
|
|
892
|
+
</button>
|
|
893
|
+
<button class="filter-toggle active" data-filter="VULN" onclick="toggleFilter('type', 'VULN')">
|
|
894
|
+
<span class="toggle-dot"></span>Vulns
|
|
895
|
+
</button>
|
|
896
|
+
<button class="filter-toggle active" data-filter="CODE" onclick="toggleFilter('type', 'CODE')">
|
|
897
|
+
<span class="toggle-dot"></span>Code
|
|
898
|
+
</button>
|
|
899
|
+
<button class="filter-toggle active" data-filter="IAC" onclick="toggleFilter('type', 'IAC')">
|
|
900
|
+
<span class="toggle-dot"></span>IaC
|
|
901
|
+
</button>
|
|
902
|
+
<button class="filter-toggle active" data-filter="DOCKER" onclick="toggleFilter('type', 'DOCKER')">
|
|
903
|
+
<span class="toggle-dot"></span>Docker
|
|
904
|
+
</button>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
907
|
+
|
|
732
908
|
<div class="finding-list" id="findingList">
|
|
733
909
|
<div class="empty-state">
|
|
734
910
|
<div class="empty-state-icon">🔍</div>
|
|
735
911
|
<p>Select a target to view findings</p>
|
|
736
912
|
</div>
|
|
737
913
|
</div>
|
|
914
|
+
|
|
915
|
+
<!-- Badge Generator -->
|
|
916
|
+
<div class="badge-section" id="badgeSection" style="display: none;">
|
|
917
|
+
<div class="filter-label">Security Badge</div>
|
|
918
|
+
<div class="badge-preview" id="badgePreview">
|
|
919
|
+
<img id="badgeImg" src="" alt="Security Badge">
|
|
920
|
+
</div>
|
|
921
|
+
<div class="badge-code" id="badgeCode">Select a repo to generate badge</div>
|
|
922
|
+
<div class="badge-actions">
|
|
923
|
+
<button class="btn btn-secondary" onclick="copyBadge('markdown')">Copy Markdown</button>
|
|
924
|
+
<button class="btn btn-secondary" onclick="copyBadge('html')">Copy HTML</button>
|
|
925
|
+
<span class="copy-success" id="copySuccess">Copied!</span>
|
|
926
|
+
</div>
|
|
927
|
+
</div>
|
|
738
928
|
</div>
|
|
739
929
|
|
|
740
930
|
<!-- Reports Panel (Center, hidden by default) -->
|
|
@@ -1659,6 +1849,113 @@
|
|
|
1659
1849
|
}
|
|
1660
1850
|
window.selectRepo = selectRepo;
|
|
1661
1851
|
|
|
1852
|
+
// ========== FILTER TOGGLES ==========
|
|
1853
|
+
const filters = {
|
|
1854
|
+
severity: { critical: true, high: true, medium: true, low: true },
|
|
1855
|
+
type: { SECRET: true, VULN: true, CODE: true, IAC: true, DOCKER: true }
|
|
1856
|
+
};
|
|
1857
|
+
|
|
1858
|
+
function toggleFilter(category, value) {
|
|
1859
|
+
filters[category][value] = !filters[category][value];
|
|
1860
|
+
|
|
1861
|
+
// Update button state
|
|
1862
|
+
const btn = document.querySelector(`.filter-toggle[data-filter="${value}"]`);
|
|
1863
|
+
if (btn) {
|
|
1864
|
+
btn.classList.toggle('active', filters[category][value]);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// Re-render findings list with filters
|
|
1868
|
+
if (state.selectedRepo) {
|
|
1869
|
+
const repo = state.repos.find(r => r.id === state.selectedRepo);
|
|
1870
|
+
if (repo) {
|
|
1871
|
+
updateFindingsList(repo);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
window.toggleFilter = toggleFilter;
|
|
1876
|
+
|
|
1877
|
+
function updateFilterCounts(repo) {
|
|
1878
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1879
|
+
if (repo && repo.findings) {
|
|
1880
|
+
repo.findings.forEach(f => {
|
|
1881
|
+
const sev = f.severity?.toLowerCase() || 'medium';
|
|
1882
|
+
if (counts[sev] !== undefined) counts[sev]++;
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
document.getElementById('countCritical').textContent = counts.critical;
|
|
1887
|
+
document.getElementById('countHigh').textContent = counts.high;
|
|
1888
|
+
document.getElementById('countMedium').textContent = counts.medium;
|
|
1889
|
+
document.getElementById('countLow').textContent = counts.low;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// ========== BADGE GENERATOR ==========
|
|
1893
|
+
function generateBadge(repo) {
|
|
1894
|
+
if (!repo) return;
|
|
1895
|
+
|
|
1896
|
+
const secrets = repo.secrets || 0;
|
|
1897
|
+
const vulns = repo.vulns || 0;
|
|
1898
|
+
|
|
1899
|
+
// Determine status and color
|
|
1900
|
+
let status, color;
|
|
1901
|
+
if (secrets > 0) {
|
|
1902
|
+
status = `${secrets} secrets`;
|
|
1903
|
+
color = 'critical';
|
|
1904
|
+
} else if (vulns > 10) {
|
|
1905
|
+
status = `${vulns} vulns`;
|
|
1906
|
+
color = 'important';
|
|
1907
|
+
} else if (vulns > 0) {
|
|
1908
|
+
status = `${vulns} vulns`;
|
|
1909
|
+
color = 'yellow';
|
|
1910
|
+
} else {
|
|
1911
|
+
status = 'passing';
|
|
1912
|
+
color = 'success';
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// Generate shields.io URL
|
|
1916
|
+
const label = 'security';
|
|
1917
|
+
const encodedStatus = encodeURIComponent(status);
|
|
1918
|
+
const badgeUrl = `https://img.shields.io/badge/${label}-${encodedStatus}-${color}?style=flat-square&logo=shield&logoColor=white`;
|
|
1919
|
+
|
|
1920
|
+
// Update preview
|
|
1921
|
+
const badgeImg = document.getElementById('badgeImg');
|
|
1922
|
+
badgeImg.src = badgeUrl;
|
|
1923
|
+
badgeImg.alt = `Security: ${status}`;
|
|
1924
|
+
|
|
1925
|
+
// Store for copy functions
|
|
1926
|
+
window.currentBadge = {
|
|
1927
|
+
url: badgeUrl,
|
|
1928
|
+
label: label,
|
|
1929
|
+
status: status,
|
|
1930
|
+
repoName: repo.name
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
// Update code preview with markdown
|
|
1934
|
+
const markdown = `[](https://aurasecurity.io)`;
|
|
1935
|
+
document.getElementById('badgeCode').textContent = markdown;
|
|
1936
|
+
|
|
1937
|
+
// Show badge section
|
|
1938
|
+
document.getElementById('badgeSection').style.display = 'block';
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
function copyBadge(format) {
|
|
1942
|
+
if (!window.currentBadge) return;
|
|
1943
|
+
|
|
1944
|
+
let text;
|
|
1945
|
+
if (format === 'markdown') {
|
|
1946
|
+
text = `[](https://aurasecurity.io)`;
|
|
1947
|
+
} else {
|
|
1948
|
+
text = `<a href="https://aurasecurity.io"><img src="${window.currentBadge.url}" alt="Security"></a>`;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
1952
|
+
const successEl = document.getElementById('copySuccess');
|
|
1953
|
+
successEl.classList.add('visible');
|
|
1954
|
+
setTimeout(() => successEl.classList.remove('visible'), 2000);
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
window.copyBadge = copyBadge;
|
|
1958
|
+
|
|
1662
1959
|
function updateUI() {
|
|
1663
1960
|
let totalCrit = 0, totalWarn = 0;
|
|
1664
1961
|
state.repos.forEach(r => {
|
|
@@ -1702,7 +1999,17 @@
|
|
|
1702
1999
|
function updateFindingsList(repo) {
|
|
1703
2000
|
const container = document.getElementById('findingList');
|
|
1704
2001
|
|
|
2002
|
+
// Show/hide filter section
|
|
2003
|
+
document.getElementById('filterSection').style.display = repo && repo.findings?.length > 0 ? 'block' : 'none';
|
|
2004
|
+
|
|
2005
|
+
// Update filter counts
|
|
2006
|
+
updateFilterCounts(repo);
|
|
2007
|
+
|
|
2008
|
+
// Generate badge
|
|
2009
|
+
generateBadge(repo);
|
|
2010
|
+
|
|
1705
2011
|
if (!repo || repo.findings.length === 0) {
|
|
2012
|
+
document.getElementById('badgeSection').style.display = 'none';
|
|
1706
2013
|
container.innerHTML = `
|
|
1707
2014
|
<div class="empty-state">
|
|
1708
2015
|
<div class="empty-state-icon">✓</div>
|
|
@@ -1712,7 +2019,24 @@
|
|
|
1712
2019
|
return;
|
|
1713
2020
|
}
|
|
1714
2021
|
|
|
1715
|
-
|
|
2022
|
+
// Apply filters
|
|
2023
|
+
const filtered = repo.findings.filter(f => {
|
|
2024
|
+
const sev = f.severity?.toLowerCase() || 'medium';
|
|
2025
|
+
const type = f.type || 'CODE';
|
|
2026
|
+
return filters.severity[sev] && filters.type[type];
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
if (filtered.length === 0) {
|
|
2030
|
+
container.innerHTML = `
|
|
2031
|
+
<div class="empty-state">
|
|
2032
|
+
<div class="empty-state-icon">🔍</div>
|
|
2033
|
+
<p>No findings match current filters</p>
|
|
2034
|
+
</div>
|
|
2035
|
+
`;
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
container.innerHTML = filtered.map(f => `
|
|
1716
2040
|
<div class="finding-item ${f.severity}">
|
|
1717
2041
|
<div class="finding-header">
|
|
1718
2042
|
<span class="finding-severity">${f.severity}</span>
|
package/visualizer/index.html
CHANGED
|
@@ -421,49 +421,6 @@
|
|
|
421
421
|
box-shadow: 0 0 30px #00ff88;
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
/* Filter Toggles */
|
|
425
|
-
.filter-toggle {
|
|
426
|
-
display: flex;
|
|
427
|
-
align-items: center;
|
|
428
|
-
gap: 4px;
|
|
429
|
-
padding: 4px 8px;
|
|
430
|
-
background: rgba(0, 20, 10, 0.6);
|
|
431
|
-
border: 1px solid #00ff8844;
|
|
432
|
-
border-radius: 4px;
|
|
433
|
-
font-size: 10px;
|
|
434
|
-
cursor: pointer;
|
|
435
|
-
transition: all 0.2s;
|
|
436
|
-
user-select: none;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.filter-toggle input {
|
|
440
|
-
display: none;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.filter-toggle .filter-icon {
|
|
444
|
-
width: 14px;
|
|
445
|
-
height: 14px;
|
|
446
|
-
border-radius: 3px;
|
|
447
|
-
display: flex;
|
|
448
|
-
align-items: center;
|
|
449
|
-
justify-content: center;
|
|
450
|
-
font-size: 8px;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
.filter-toggle.active {
|
|
454
|
-
border-color: #00ff88;
|
|
455
|
-
background: rgba(0, 255, 136, 0.1);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.filter-toggle:not(.active) {
|
|
459
|
-
opacity: 0.5;
|
|
460
|
-
border-color: #333;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
.filter-toggle:hover {
|
|
464
|
-
border-color: #00ff88;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
424
|
/* Scrollbar */
|
|
468
425
|
::-webkit-scrollbar { width: 6px; }
|
|
469
426
|
::-webkit-scrollbar-track { background: #0a0a0f; }
|
|
@@ -658,32 +615,6 @@
|
|
|
658
615
|
|
|
659
616
|
<div class="panel">
|
|
660
617
|
<div class="panel-title">Security Findings</div>
|
|
661
|
-
<!-- Filter Toggles -->
|
|
662
|
-
<div id="findingsFilter" style="margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #00ff8833;">
|
|
663
|
-
<div style="font-size: 9px; color: #666; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 1px;">Filter by Type:</div>
|
|
664
|
-
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
|
|
665
|
-
<label class="filter-toggle active" data-filter="secrets">
|
|
666
|
-
<input type="checkbox" checked onchange="toggleFilter('secrets', this.checked)">
|
|
667
|
-
<span class="filter-icon" style="background: #ff0088;">🔑</span>
|
|
668
|
-
<span>Secrets</span>
|
|
669
|
-
</label>
|
|
670
|
-
<label class="filter-toggle active" data-filter="cves">
|
|
671
|
-
<input type="checkbox" checked onchange="toggleFilter('cves', this.checked)">
|
|
672
|
-
<span class="filter-icon" style="background: #ff4400;">📦</span>
|
|
673
|
-
<span>CVEs</span>
|
|
674
|
-
</label>
|
|
675
|
-
<label class="filter-toggle active" data-filter="dockerfile">
|
|
676
|
-
<input type="checkbox" checked onchange="toggleFilter('dockerfile', this.checked)">
|
|
677
|
-
<span class="filter-icon" style="background: #00aaff;">🐳</span>
|
|
678
|
-
<span>Docker</span>
|
|
679
|
-
</label>
|
|
680
|
-
<label class="filter-toggle active" data-filter="sast">
|
|
681
|
-
<input type="checkbox" checked onchange="toggleFilter('sast', this.checked)">
|
|
682
|
-
<span class="filter-icon" style="background: #ffaa00;">🔬</span>
|
|
683
|
-
<span>SAST</span>
|
|
684
|
-
</label>
|
|
685
|
-
</div>
|
|
686
|
-
</div>
|
|
687
618
|
<div id="findingsList">
|
|
688
619
|
<p style="color: #666; font-size: 11px;">No findings yet. Run an audit to detect issues.</p>
|
|
689
620
|
</div>
|
|
@@ -694,34 +625,6 @@
|
|
|
694
625
|
<div class="panel-title">Settings <span id="settingsStatus" style="color: #666; font-size: 10px;"></span></div>
|
|
695
626
|
<button class="btn secondary" style="width: 100%;" onclick="openSettingsModal()">Configure Integrations</button>
|
|
696
627
|
</div>
|
|
697
|
-
|
|
698
|
-
<!-- Badge Generator Panel -->
|
|
699
|
-
<div class="panel">
|
|
700
|
-
<div class="panel-title">Badge Generator</div>
|
|
701
|
-
<p style="font-size: 10px; color: #666; margin-bottom: 8px;">Generate shields for your README</p>
|
|
702
|
-
<div id="badgePreview" style="margin-bottom: 10px; text-align: center; padding: 10px; background: rgba(0,0,0,0.3); border-radius: 4px;">
|
|
703
|
-
<img id="badgeImg" src="https://img.shields.io/badge/security-scanning-00ff88?style=flat-square&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAxTDMgNXY2YzAgNS41NSAzLjg0IDEwLjc0IDkgMTIgNS4xNi0xLjI2IDktNi40NSA5LTEyVjVsLTktNHoiLz48L3N2Zz4=" alt="security badge" style="height: 20px;">
|
|
704
|
-
</div>
|
|
705
|
-
<div style="margin-bottom: 8px;">
|
|
706
|
-
<select id="badgeStyle" style="width: 100%; padding: 6px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88; font-size: 11px;" onchange="updateBadgePreview()">
|
|
707
|
-
<option value="flat-square">Flat Square</option>
|
|
708
|
-
<option value="flat">Flat</option>
|
|
709
|
-
<option value="plastic">Plastic</option>
|
|
710
|
-
<option value="for-the-badge">For The Badge</option>
|
|
711
|
-
</select>
|
|
712
|
-
</div>
|
|
713
|
-
<div style="margin-bottom: 8px;">
|
|
714
|
-
<select id="badgeType" style="width: 100%; padding: 6px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88; font-size: 11px;" onchange="updateBadgePreview()">
|
|
715
|
-
<option value="status">Scan Status</option>
|
|
716
|
-
<option value="critical">Critical Count</option>
|
|
717
|
-
<option value="total">Total Findings</option>
|
|
718
|
-
</select>
|
|
719
|
-
</div>
|
|
720
|
-
<div id="badgeMarkdown" style="background: rgba(0,0,0,0.4); padding: 8px; border-radius: 4px; font-family: monospace; font-size: 9px; color: #00ff88; word-break: break-all; margin-bottom: 8px; max-height: 60px; overflow-y: auto;">
|
|
721
|
-

|
|
722
|
-
</div>
|
|
723
|
-
<button class="btn secondary" style="width: 100%; font-size: 10px;" onclick="copyBadgeMarkdown()">📋 Copy Markdown</button>
|
|
724
|
-
</div>
|
|
725
628
|
</div>
|
|
726
629
|
|
|
727
630
|
<!-- Settings Modal -->
|
|
@@ -1048,71 +951,6 @@
|
|
|
1048
951
|
blocked: 0xff0044
|
|
1049
952
|
};
|
|
1050
953
|
|
|
1051
|
-
// ========== FINDINGS FILTER STATE ==========
|
|
1052
|
-
const findingsFilter = {
|
|
1053
|
-
secrets: true,
|
|
1054
|
-
cves: true,
|
|
1055
|
-
dockerfile: true,
|
|
1056
|
-
sast: true
|
|
1057
|
-
};
|
|
1058
|
-
|
|
1059
|
-
// Toggle filter and re-render findings
|
|
1060
|
-
window.toggleFilter = function(filterType, enabled) {
|
|
1061
|
-
findingsFilter[filterType] = enabled;
|
|
1062
|
-
const toggle = document.querySelector(`.filter-toggle[data-filter="${filterType}"]`);
|
|
1063
|
-
if (toggle) {
|
|
1064
|
-
toggle.classList.toggle('active', enabled);
|
|
1065
|
-
}
|
|
1066
|
-
// Re-render with current findings
|
|
1067
|
-
if (typeof renderFindingsList === 'function') {
|
|
1068
|
-
renderFindingsList(currentFindings);
|
|
1069
|
-
}
|
|
1070
|
-
// Also re-render scan findings if we have them
|
|
1071
|
-
if (typeof renderScanFindings === 'function' && currentScanFindings) {
|
|
1072
|
-
renderScanFindings(currentScanFindings);
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1075
|
-
|
|
1076
|
-
// Determine finding type from finding data
|
|
1077
|
-
function getFindingType(finding) {
|
|
1078
|
-
// Check payload for audit events
|
|
1079
|
-
if (finding.payload) {
|
|
1080
|
-
const claim = (finding.payload.claim || '').toLowerCase();
|
|
1081
|
-
const assets = (finding.payload.affected_assets || []).join(' ').toLowerCase();
|
|
1082
|
-
|
|
1083
|
-
if (claim.includes('secret') || claim.includes('credential') || claim.includes('key') || claim.includes('password') || assets.includes('secrets')) {
|
|
1084
|
-
return 'secrets';
|
|
1085
|
-
}
|
|
1086
|
-
if (claim.includes('vulnerability') || claim.includes('cve') || claim.includes('package') || claim.includes('dependency')) {
|
|
1087
|
-
return 'cves';
|
|
1088
|
-
}
|
|
1089
|
-
if (claim.includes('docker') || claim.includes('container') || claim.includes('dockerfile')) {
|
|
1090
|
-
return 'dockerfile';
|
|
1091
|
-
}
|
|
1092
|
-
if (claim.includes('injection') || claim.includes('xss') || claim.includes('sast') || claim.includes('code')) {
|
|
1093
|
-
return 'sast';
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
// Check direct finding type
|
|
1097
|
-
if (finding.type) {
|
|
1098
|
-
const type = finding.type.toLowerCase();
|
|
1099
|
-
if (type.includes('secret') || type.includes('key') || type.includes('credential')) return 'secrets';
|
|
1100
|
-
if (type.includes('vuln') || type.includes('cve') || type.includes('package')) return 'cves';
|
|
1101
|
-
if (type.includes('docker') || type.includes('hadolint')) return 'dockerfile';
|
|
1102
|
-
if (type.includes('sast') || type.includes('semgrep')) return 'sast';
|
|
1103
|
-
}
|
|
1104
|
-
return 'sast'; // Default to SAST for unknown types
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Check if finding passes current filters
|
|
1108
|
-
function findingPassesFilter(finding) {
|
|
1109
|
-
const type = getFindingType(finding);
|
|
1110
|
-
return findingsFilter[type] === true;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// Store current scan findings for filtering
|
|
1114
|
-
let currentScanFindings = null;
|
|
1115
|
-
|
|
1116
954
|
// Clear the map - remove all modules and connections to start fresh
|
|
1117
955
|
function clearMap() {
|
|
1118
956
|
console.log('Clearing map for fresh scan...');
|
|
@@ -1537,24 +1375,11 @@
|
|
|
1537
1375
|
return;
|
|
1538
1376
|
}
|
|
1539
1377
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (filteredFindings.length === 0) {
|
|
1544
|
-
container.innerHTML = '<p style="color: #666; font-size: 11px;">No findings match current filters. Adjust filters above.</p>';
|
|
1545
|
-
return;
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
container.innerHTML = filteredFindings.map(f => {
|
|
1549
|
-
const findingType = getFindingType(f);
|
|
1550
|
-
const typeIcons = { secrets: '🔑', cves: '📦', dockerfile: '🐳', sast: '🔬' };
|
|
1551
|
-
const typeIcon = typeIcons[findingType] || '⚠️';
|
|
1552
|
-
|
|
1553
|
-
return `
|
|
1554
|
-
<div class="finding ${f.payload.severity}" data-type="${findingType}">
|
|
1378
|
+
container.innerHTML = findings.map(f => `
|
|
1379
|
+
<div class="finding ${f.payload.severity}">
|
|
1555
1380
|
<div class="finding-header">
|
|
1556
1381
|
<span class="severity-badge ${f.payload.severity}">${f.payload.severity.toUpperCase()}</span>
|
|
1557
|
-
<span class="finding-module">${
|
|
1382
|
+
<span class="finding-module">${f.payload.affected_assets[0] || 'system'}</span>
|
|
1558
1383
|
</div>
|
|
1559
1384
|
<div class="finding-claim">${f.payload.claim}</div>
|
|
1560
1385
|
<div class="finding-details">
|
|
@@ -1564,7 +1389,7 @@
|
|
|
1564
1389
|
<strong>Confidence:</strong> ${(f.payload.confidence * 100).toFixed(0)}%
|
|
1565
1390
|
</div>
|
|
1566
1391
|
</div>
|
|
1567
|
-
`
|
|
1392
|
+
`).join('');
|
|
1568
1393
|
}
|
|
1569
1394
|
|
|
1570
1395
|
function updateStats(findings) {
|
|
@@ -2136,83 +1961,6 @@
|
|
|
2136
1961
|
setTimeout(() => toast.remove(), 4000);
|
|
2137
1962
|
}
|
|
2138
1963
|
|
|
2139
|
-
// ========== BADGE GENERATOR ==========
|
|
2140
|
-
window.updateBadgePreview = function() {
|
|
2141
|
-
const style = document.getElementById('badgeStyle').value;
|
|
2142
|
-
const type = document.getElementById('badgeType').value;
|
|
2143
|
-
|
|
2144
|
-
// Get current stats
|
|
2145
|
-
const critical = parseInt(document.getElementById('statCritical').textContent) || 0;
|
|
2146
|
-
const high = parseInt(document.getElementById('statHigh').textContent) || 0;
|
|
2147
|
-
const medium = parseInt(document.getElementById('statMedium').textContent) || 0;
|
|
2148
|
-
const low = parseInt(document.getElementById('statLow').textContent) || 0;
|
|
2149
|
-
const total = critical + high + medium + low;
|
|
2150
|
-
|
|
2151
|
-
let label, message, color;
|
|
2152
|
-
|
|
2153
|
-
if (type === 'status') {
|
|
2154
|
-
label = 'security';
|
|
2155
|
-
if (critical > 0) {
|
|
2156
|
-
message = 'critical';
|
|
2157
|
-
color = 'ff0044';
|
|
2158
|
-
} else if (high > 0) {
|
|
2159
|
-
message = 'issues found';
|
|
2160
|
-
color = 'ff4400';
|
|
2161
|
-
} else if (total > 0) {
|
|
2162
|
-
message = 'warnings';
|
|
2163
|
-
color = 'ffaa00';
|
|
2164
|
-
} else {
|
|
2165
|
-
message = 'passing';
|
|
2166
|
-
color = '00ff88';
|
|
2167
|
-
}
|
|
2168
|
-
} else if (type === 'critical') {
|
|
2169
|
-
label = 'critical';
|
|
2170
|
-
message = critical.toString();
|
|
2171
|
-
color = critical > 0 ? 'ff0044' : '00ff88';
|
|
2172
|
-
} else {
|
|
2173
|
-
label = 'findings';
|
|
2174
|
-
message = total.toString();
|
|
2175
|
-
color = total > 5 ? 'ff4400' : total > 0 ? 'ffaa00' : '00ff88';
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
// Shield logo as base64
|
|
2179
|
-
const logo = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAxTDMgNXY2YzAgNS41NSAzLjg0IDEwLjc0IDkgMTIgNS4xNi0xLjI2IDktNi40NSA5LTEyVjVsLTktNHoiLz48L3N2Zz4=';
|
|
2180
|
-
|
|
2181
|
-
const badgeUrl = `https://img.shields.io/badge/${encodeURIComponent(label)}-${encodeURIComponent(message)}-${color}?style=${style}&logo=${encodeURIComponent(logo)}`;
|
|
2182
|
-
const markdown = `[](https://aurasecurity.io)`;
|
|
2183
|
-
|
|
2184
|
-
document.getElementById('badgeImg').src = badgeUrl;
|
|
2185
|
-
document.getElementById('badgeMarkdown').textContent = markdown;
|
|
2186
|
-
};
|
|
2187
|
-
|
|
2188
|
-
window.copyBadgeMarkdown = function() {
|
|
2189
|
-
const markdown = document.getElementById('badgeMarkdown').textContent;
|
|
2190
|
-
navigator.clipboard.writeText(markdown).then(() => {
|
|
2191
|
-
showToast('Badge markdown copied!');
|
|
2192
|
-
}).catch(() => {
|
|
2193
|
-
showToast('Failed to copy', true);
|
|
2194
|
-
});
|
|
2195
|
-
};
|
|
2196
|
-
|
|
2197
|
-
// Update badge when stats change
|
|
2198
|
-
const originalUpdateStats = function() {};
|
|
2199
|
-
const statsObserver = new MutationObserver(() => {
|
|
2200
|
-
if (typeof updateBadgePreview === 'function') {
|
|
2201
|
-
updateBadgePreview();
|
|
2202
|
-
}
|
|
2203
|
-
});
|
|
2204
|
-
|
|
2205
|
-
// Start observing stats after DOM is ready
|
|
2206
|
-
setTimeout(() => {
|
|
2207
|
-
const statElements = ['statCritical', 'statHigh', 'statMedium', 'statLow'];
|
|
2208
|
-
statElements.forEach(id => {
|
|
2209
|
-
const el = document.getElementById(id);
|
|
2210
|
-
if (el) statsObserver.observe(el, { childList: true, characterData: true, subtree: true });
|
|
2211
|
-
});
|
|
2212
|
-
// Initial badge update
|
|
2213
|
-
if (typeof updateBadgePreview === 'function') updateBadgePreview();
|
|
2214
|
-
}, 1000);
|
|
2215
|
-
|
|
2216
1964
|
function onWindowResize() {
|
|
2217
1965
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
2218
1966
|
camera.updateProjectionMatrix();
|