aura-security 0.4.0 → 0.4.2
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/LICENSE +1 -1
- package/dist/cli.js +22 -7
- package/dist/integrations/local-scanner.js +121 -11
- package/package.json +1 -1
- package/visualizer/index.html +32 -4
- package/visualizer/landing-backup-20260119-162124.html +1826 -0
- package/visualizer/landing.html +895 -67
package/LICENSE
CHANGED
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.4.1';
|
|
28
28
|
// ANSI colors for terminal output
|
|
29
29
|
const colors = {
|
|
30
30
|
reset: '\x1b[0m',
|
|
@@ -134,6 +134,15 @@ const SECURITY_TOOLS = [
|
|
|
134
134
|
install: { gem: 'gem install bundler-audit' },
|
|
135
135
|
license: 'GPLv3 (Free)',
|
|
136
136
|
required: false
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'flawfinder',
|
|
140
|
+
command: 'flawfinder',
|
|
141
|
+
description: 'C/C++ source code security scanner',
|
|
142
|
+
category: 'sast',
|
|
143
|
+
install: { pip: 'pip install flawfinder', apt: 'apt install flawfinder' },
|
|
144
|
+
license: 'GPL-2.0 (Free)',
|
|
145
|
+
required: false
|
|
137
146
|
}
|
|
138
147
|
];
|
|
139
148
|
function isToolInstalled(command) {
|
|
@@ -640,22 +649,28 @@ function displayScanResults(result) {
|
|
|
640
649
|
const dockerfileCount = result.dockerfileFindings.length;
|
|
641
650
|
const serviceCount = result.discoveredServices.length;
|
|
642
651
|
const moduleCount = result.discoveredModules.length;
|
|
652
|
+
// Helper to normalize severity (SAST findings use uppercase)
|
|
653
|
+
const normSev = (sev) => (sev || '').toLowerCase();
|
|
643
654
|
const criticalCount = result.secrets.filter(s => s.severity === 'critical').length +
|
|
644
655
|
result.packages.filter(p => p.severity === 'critical').length +
|
|
645
656
|
result.iacFindings.filter(i => i.severity === 'critical').length +
|
|
646
|
-
result.dockerfileFindings.filter(d => d.severity === 'critical').length
|
|
657
|
+
result.dockerfileFindings.filter(d => d.severity === 'critical').length +
|
|
658
|
+
result.sastFindings.filter(s => normSev(s.severity) === 'critical').length;
|
|
647
659
|
const highCount = result.secrets.filter(s => s.severity === 'high').length +
|
|
648
660
|
result.packages.filter(p => p.severity === 'high').length +
|
|
649
661
|
result.iacFindings.filter(i => i.severity === 'high').length +
|
|
650
|
-
result.dockerfileFindings.filter(d => d.severity === 'high').length
|
|
662
|
+
result.dockerfileFindings.filter(d => d.severity === 'high').length +
|
|
663
|
+
result.sastFindings.filter(s => normSev(s.severity) === 'high').length;
|
|
651
664
|
const mediumCount = result.secrets.filter(s => s.severity === 'medium').length +
|
|
652
665
|
result.packages.filter(p => p.severity === 'medium').length +
|
|
653
666
|
result.iacFindings.filter(i => i.severity === 'medium').length +
|
|
654
|
-
result.dockerfileFindings.filter(d => d.severity === 'medium').length
|
|
667
|
+
result.dockerfileFindings.filter(d => d.severity === 'medium').length +
|
|
668
|
+
result.sastFindings.filter(s => normSev(s.severity) === 'medium').length;
|
|
655
669
|
const lowCount = result.secrets.filter(s => s.severity === 'low').length +
|
|
656
670
|
result.packages.filter(p => p.severity === 'low').length +
|
|
657
671
|
result.iacFindings.filter(i => i.severity === 'low').length +
|
|
658
|
-
result.dockerfileFindings.filter(d => d.severity === 'low').length
|
|
672
|
+
result.dockerfileFindings.filter(d => d.severity === 'low').length +
|
|
673
|
+
result.sastFindings.filter(s => normSev(s.severity) === 'low').length;
|
|
659
674
|
// Stats bar
|
|
660
675
|
console.log('┌─────────────────────────────────────────────────────────────────┐');
|
|
661
676
|
console.log(`│ ${c('bgRed', ` CRITICAL ${criticalCount} `)} ${c('red', `HIGH ${highCount}`)} ${c('yellow', `MEDIUM ${mediumCount}`)} ${c('dim', `LOW ${lowCount}`)} │`);
|
|
@@ -1343,8 +1358,8 @@ ${c('bold', 'SCAN OPTIONS:')}
|
|
|
1343
1358
|
-j, --json Output results as JSON
|
|
1344
1359
|
-f, --format Output format: console, json, sarif, junit, gitlab-sast, gitlab-deps, summary
|
|
1345
1360
|
-o, --output Save results to file
|
|
1346
|
-
-l, --languages Filter by languages: js,py,go,rust,ruby,php,java
|
|
1347
|
-
-S, --scanners Use specific scanners: grype,trivy,gitleaks,semgrep,checkov,hadolint,pip-audit,govulncheck,cargo-audit,bundle-audit,composer
|
|
1361
|
+
-l, --languages Filter by languages: js,py,go,rust,ruby,php,java,c,csharp
|
|
1362
|
+
-S, --scanners Use specific scanners: grype,trivy,gitleaks,semgrep,checkov,hadolint,pip-audit,govulncheck,cargo-audit,bundle-audit,composer,flawfinder
|
|
1348
1363
|
--fail-on Exit with error on severity: critical,high,medium (default: critical)
|
|
1349
1364
|
--no-fail Never exit with error code
|
|
1350
1365
|
|
|
@@ -36,17 +36,51 @@ const SECRET_SCAN_EXCLUDE_FILES = [
|
|
|
36
36
|
'.next/',
|
|
37
37
|
'coverage/'
|
|
38
38
|
];
|
|
39
|
-
// Directories that often contain false positives (test data,
|
|
39
|
+
// Directories that often contain false positives (test data, vendored deps, examples)
|
|
40
40
|
const FALSE_POSITIVE_DIRS = [
|
|
41
41
|
'node_modules',
|
|
42
42
|
'__tests__',
|
|
43
43
|
'__mocks__',
|
|
44
|
-
'test/',
|
|
45
|
-
'tests/',
|
|
46
|
-
'spec/',
|
|
47
|
-
'fixtures/',
|
|
48
|
-
'examples/',
|
|
49
|
-
'
|
|
44
|
+
'/test/',
|
|
45
|
+
'/tests/',
|
|
46
|
+
'/spec/',
|
|
47
|
+
'/fixtures/',
|
|
48
|
+
'/examples/',
|
|
49
|
+
'/example/',
|
|
50
|
+
'/sample/',
|
|
51
|
+
'/samples/',
|
|
52
|
+
'/demo/',
|
|
53
|
+
'/demos/',
|
|
54
|
+
'/mock/',
|
|
55
|
+
'/mocks/',
|
|
56
|
+
'/testdata/',
|
|
57
|
+
'/test-data/',
|
|
58
|
+
'/assets/test/',
|
|
59
|
+
'/testing/',
|
|
60
|
+
'/vendor/',
|
|
61
|
+
'/vendors/',
|
|
62
|
+
'/bundled/',
|
|
63
|
+
'/deps/',
|
|
64
|
+
'/third_party/',
|
|
65
|
+
'/third-party/',
|
|
66
|
+
'/thirdparty/',
|
|
67
|
+
'/external/',
|
|
68
|
+
'/dummy/',
|
|
69
|
+
'/fake/'
|
|
70
|
+
];
|
|
71
|
+
// File name patterns that indicate test/example data (not real secrets)
|
|
72
|
+
const TEST_FILE_PATTERNS = [
|
|
73
|
+
/\.example$/i,
|
|
74
|
+
/\.sample$/i,
|
|
75
|
+
/\.test\./i,
|
|
76
|
+
/\.spec\./i,
|
|
77
|
+
/example\d*\.(key|pem|jwt|crt|p12|pfx)$/i,
|
|
78
|
+
/test\d*\.(key|pem|jwt|crt|p12|pfx)$/i,
|
|
79
|
+
/sample\d*\.(key|pem|jwt|crt|p12|pfx)$/i,
|
|
80
|
+
/fake[-_]?(key|secret|token|credential)/i,
|
|
81
|
+
/dummy[-_]?(key|secret|token|credential)/i,
|
|
82
|
+
/mock[-_]?(key|secret|token|credential)/i,
|
|
83
|
+
/temporary[-_]?key/i,
|
|
50
84
|
];
|
|
51
85
|
// Rules to completely skip - too many false positives to be useful
|
|
52
86
|
// aws-secret-access-key matches any 40-char base64 string which catches SHA hashes,
|
|
@@ -58,6 +92,14 @@ const SKIP_RULES_ENTIRELY = [
|
|
|
58
92
|
const FALSE_POSITIVE_RULES = [
|
|
59
93
|
'generic-api-key'
|
|
60
94
|
];
|
|
95
|
+
// Rules to skip in specific file types (too many false positives)
|
|
96
|
+
const SKIP_RULES_IN_FILE_TYPES = {
|
|
97
|
+
// generic-api-key matches C macros and function signatures in headers
|
|
98
|
+
'.h': ['generic-api-key'],
|
|
99
|
+
'.hpp': ['generic-api-key'],
|
|
100
|
+
'.c': ['generic-api-key', 'sourcegraph-access-token'],
|
|
101
|
+
'.cpp': ['generic-api-key'],
|
|
102
|
+
};
|
|
61
103
|
// Run gitleaks for secrets detection
|
|
62
104
|
function runGitleaks(targetPath) {
|
|
63
105
|
const findings = [];
|
|
@@ -93,10 +135,17 @@ function runGitleaks(targetPath) {
|
|
|
93
135
|
const isExcludedFile = SECRET_SCAN_EXCLUDE_FILES.some(pattern => fileName === pattern || filePath.includes(pattern.toLowerCase()));
|
|
94
136
|
// Filter out findings from known false positive directories
|
|
95
137
|
const isInFalsePositiveDir = FALSE_POSITIVE_DIRS.some(dir => filePath.includes(dir.toLowerCase()));
|
|
138
|
+
// Filter out test/example file names
|
|
139
|
+
const isTestFile = TEST_FILE_PATTERNS.some(pattern => pattern.test(fileName) || pattern.test(filePath));
|
|
96
140
|
// Filter out known false positive rules in certain file types
|
|
97
141
|
const isFalsePositiveRule = FALSE_POSITIVE_RULES.includes(ruleId) &&
|
|
98
142
|
(isExcludedFile || isInFalsePositiveDir);
|
|
99
|
-
if
|
|
143
|
+
// Check if this rule should be skipped for this file type
|
|
144
|
+
const fileExt = extname(fileName).toLowerCase();
|
|
145
|
+
const skipRulesForExt = SKIP_RULES_IN_FILE_TYPES[fileExt] || [];
|
|
146
|
+
const isRuleSkippedForFileType = skipRulesForExt.includes(ruleId);
|
|
147
|
+
// Skip if any false positive indicator matches
|
|
148
|
+
if (isExcludedFile || isInFalsePositiveDir || isTestFile || isFalsePositiveRule || isRuleSkippedForFileType) {
|
|
100
149
|
filteredCount++;
|
|
101
150
|
continue;
|
|
102
151
|
}
|
|
@@ -114,7 +163,7 @@ function runGitleaks(targetPath) {
|
|
|
114
163
|
}
|
|
115
164
|
catch { }
|
|
116
165
|
if (filteredCount > 0) {
|
|
117
|
-
console.log(`[SCANNER] Filtered ${filteredCount} false positives
|
|
166
|
+
console.log(`[SCANNER] Filtered ${filteredCount} false positives (test files, examples, lock files)`);
|
|
118
167
|
}
|
|
119
168
|
}
|
|
120
169
|
console.log(`[SCANNER] gitleaks found ${findings.length} secrets`);
|
|
@@ -533,6 +582,52 @@ function runComposerAudit(targetPath) {
|
|
|
533
582
|
}
|
|
534
583
|
return findings;
|
|
535
584
|
}
|
|
585
|
+
function runFlawfinder(targetPath) {
|
|
586
|
+
const findings = [];
|
|
587
|
+
if (!isToolAvailable('flawfinder')) {
|
|
588
|
+
console.log('[SCANNER] flawfinder not available, skipping C/C++ SAST');
|
|
589
|
+
return findings;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
console.log('[SCANNER] Running flawfinder...');
|
|
593
|
+
const result = spawnSync('flawfinder', [
|
|
594
|
+
'--dataonly',
|
|
595
|
+
'--quiet',
|
|
596
|
+
'--csv',
|
|
597
|
+
targetPath
|
|
598
|
+
], { encoding: 'utf-8', timeout: 180000, maxBuffer: 50 * 1024 * 1024 });
|
|
599
|
+
if (result.stdout) {
|
|
600
|
+
// Parse CSV output: File,Line,Column,Level,Category,Name,Warning
|
|
601
|
+
const lines = result.stdout.split('\n').filter(l => l.trim() && !l.startsWith('File,'));
|
|
602
|
+
for (const line of lines) {
|
|
603
|
+
const parts = line.split(',');
|
|
604
|
+
if (parts.length >= 7) {
|
|
605
|
+
const level = parseInt(parts[3], 10);
|
|
606
|
+
// Map flawfinder levels (0-5) to severity
|
|
607
|
+
let severity = 'low';
|
|
608
|
+
if (level >= 5)
|
|
609
|
+
severity = 'critical';
|
|
610
|
+
else if (level >= 4)
|
|
611
|
+
severity = 'high';
|
|
612
|
+
else if (level >= 2)
|
|
613
|
+
severity = 'medium';
|
|
614
|
+
findings.push({
|
|
615
|
+
file: parts[0],
|
|
616
|
+
line: parseInt(parts[1], 10),
|
|
617
|
+
rule: `${parts[4]}:${parts[5]}`,
|
|
618
|
+
message: parts.slice(6).join(',').replace(/"/g, ''),
|
|
619
|
+
severity
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
console.log(`[SCANNER] flawfinder found ${findings.length} issues`);
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
console.error('[SCANNER] flawfinder error:', err);
|
|
628
|
+
}
|
|
629
|
+
return findings;
|
|
630
|
+
}
|
|
536
631
|
function runCheckov(targetPath) {
|
|
537
632
|
const findings = [];
|
|
538
633
|
if (!isToolAvailable('checkov')) {
|
|
@@ -892,6 +987,13 @@ export class LocalScanner {
|
|
|
892
987
|
else {
|
|
893
988
|
console.log('[SCANNER] semgrep not available, skipping SAST');
|
|
894
989
|
}
|
|
990
|
+
// C/C++ SAST with flawfinder
|
|
991
|
+
if (shouldScanLang('c') && shouldUsescanner('flawfinder') && isToolAvailable('flawfinder')) {
|
|
992
|
+
console.log('[SCANNER] Using flawfinder for C/C++ SAST analysis');
|
|
993
|
+
const flawfinderFindings = runFlawfinder(this.config.targetPath);
|
|
994
|
+
result.sastFindings.push(...flawfinderFindings);
|
|
995
|
+
toolsUsed.push('flawfinder');
|
|
996
|
+
}
|
|
895
997
|
// ============ IAC SCANNING ============
|
|
896
998
|
if (this.config.scanIaC !== false) {
|
|
897
999
|
if (shouldUsescanner('checkov') && isToolAvailable('checkov')) {
|
|
@@ -948,8 +1050,16 @@ export class LocalScanner {
|
|
|
948
1050
|
languages.add('php');
|
|
949
1051
|
if (existsSync(join(dir, 'pom.xml')) || existsSync(join(dir, 'build.gradle')))
|
|
950
1052
|
languages.add('java');
|
|
951
|
-
|
|
952
|
-
|
|
1053
|
+
// C# - need to scan directory for .csproj/.sln files (existsSync doesn't support globs)
|
|
1054
|
+
try {
|
|
1055
|
+
const entries = readdirSync(dir);
|
|
1056
|
+
if (entries.some(f => f.endsWith('.csproj') || f.endsWith('.sln')))
|
|
1057
|
+
languages.add('csharp');
|
|
1058
|
+
// C/C++ detection
|
|
1059
|
+
if (entries.some(f => f === 'CMakeLists.txt' || f === 'Makefile' || f.endsWith('.c') || f.endsWith('.cpp') || f.endsWith('.h') || f.endsWith('.hpp')))
|
|
1060
|
+
languages.add('c');
|
|
1061
|
+
}
|
|
1062
|
+
catch { /* ignore */ }
|
|
953
1063
|
return Array.from(languages);
|
|
954
1064
|
}
|
|
955
1065
|
// Merge package findings avoiding duplicates
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-security",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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/index.html
CHANGED
|
@@ -804,8 +804,21 @@
|
|
|
804
804
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
805
805
|
|
|
806
806
|
// ========== CONFIGURATION ==========
|
|
807
|
-
|
|
808
|
-
const
|
|
807
|
+
// Auto-detect environment based on hostname
|
|
808
|
+
const hostname = window.location.hostname;
|
|
809
|
+
const isLocalDev = hostname === '127.0.0.1' || hostname === 'localhost';
|
|
810
|
+
|
|
811
|
+
// API URL: local dev uses localhost:3000, production uses same origin (nginx proxies to API)
|
|
812
|
+
const AURA_URL = isLocalDev ? 'http://127.0.0.1:3000' : '';
|
|
813
|
+
|
|
814
|
+
// WebSocket URL: local dev uses localhost:3001, production uses same origin /ws path
|
|
815
|
+
const WS_URL = isLocalDev
|
|
816
|
+
? 'ws://127.0.0.1:3001'
|
|
817
|
+
: `wss://${hostname}/ws`;
|
|
818
|
+
|
|
819
|
+
console.log('[CONFIG] Environment:', isLocalDev ? 'local' : 'production');
|
|
820
|
+
console.log('[CONFIG] API URL:', AURA_URL || '(same origin)');
|
|
821
|
+
console.log('[CONFIG] WS URL:', WS_URL);
|
|
809
822
|
const POLL_INTERVAL = 5000; // Slower polling as WebSocket handles real-time
|
|
810
823
|
let wsConnected = false;
|
|
811
824
|
let ws = null;
|
|
@@ -2383,7 +2396,19 @@
|
|
|
2383
2396
|
})
|
|
2384
2397
|
});
|
|
2385
2398
|
|
|
2386
|
-
|
|
2399
|
+
// Check if response is OK before parsing JSON
|
|
2400
|
+
if (!res.ok) {
|
|
2401
|
+
const errorText = await res.text();
|
|
2402
|
+
throw new Error(`Server error (${res.status}): ${errorText.substring(0, 100)}`);
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// Safely parse JSON with error handling
|
|
2406
|
+
let data;
|
|
2407
|
+
try {
|
|
2408
|
+
data = await res.json();
|
|
2409
|
+
} catch (parseErr) {
|
|
2410
|
+
throw new Error('Invalid response from server (not JSON)');
|
|
2411
|
+
}
|
|
2387
2412
|
console.log('Git scan response:', data);
|
|
2388
2413
|
|
|
2389
2414
|
if (data.result && data.result.scan_details) {
|
|
@@ -2391,8 +2416,11 @@
|
|
|
2391
2416
|
} else if (data.error) {
|
|
2392
2417
|
statusEl.innerHTML = `<span style="color: #ff4444;">Error: ${data.error}</span>`;
|
|
2393
2418
|
showToast('Git scan failed', true);
|
|
2419
|
+
} else if (data.result && data.result.error) {
|
|
2420
|
+
statusEl.innerHTML = `<span style="color: #ff4444;">Error: ${data.result.error}</span>`;
|
|
2421
|
+
showToast('Git scan failed', true);
|
|
2394
2422
|
} else {
|
|
2395
|
-
statusEl.innerHTML = '<span style="color: #ff4444;">Scan failed</span>';
|
|
2423
|
+
statusEl.innerHTML = '<span style="color: #ff4444;">Scan failed - no results</span>';
|
|
2396
2424
|
}
|
|
2397
2425
|
} catch (err) {
|
|
2398
2426
|
console.error('Git scan error:', err);
|