aura-security 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -66,7 +66,18 @@ const FALSE_POSITIVE_DIRS = [
|
|
|
66
66
|
'/thirdparty/',
|
|
67
67
|
'/external/',
|
|
68
68
|
'/dummy/',
|
|
69
|
-
'/fake/'
|
|
69
|
+
'/fake/',
|
|
70
|
+
'/data/',
|
|
71
|
+
'/scripts/data/',
|
|
72
|
+
'/seeds/',
|
|
73
|
+
'/seed/',
|
|
74
|
+
'/migrations/',
|
|
75
|
+
'/migration/',
|
|
76
|
+
'/index_constituents/',
|
|
77
|
+
'/token-list/',
|
|
78
|
+
'/tokenlist/',
|
|
79
|
+
'/verified-tokens/',
|
|
80
|
+
'/validated-tokens/'
|
|
70
81
|
];
|
|
71
82
|
// File name patterns that indicate test/example data (not real secrets)
|
|
72
83
|
const TEST_FILE_PATTERNS = [
|
|
@@ -108,6 +119,8 @@ const SKIP_RULES_IN_FILE_TYPES = {
|
|
|
108
119
|
'.md': ['generic-api-key', 'curl-auth-header'],
|
|
109
120
|
'.rst': ['generic-api-key', 'curl-auth-header'],
|
|
110
121
|
'.txt': ['generic-api-key'],
|
|
122
|
+
// JSON files often contain token lists, address registries, config data - not real secrets
|
|
123
|
+
'.json': ['generic-api-key', 'aws-access-token', 'private-key'],
|
|
111
124
|
};
|
|
112
125
|
// Run gitleaks for secrets detection
|
|
113
126
|
function runGitleaks(targetPath) {
|
|
@@ -153,8 +166,17 @@ function runGitleaks(targetPath) {
|
|
|
153
166
|
const fileExt = extname(fileName).toLowerCase();
|
|
154
167
|
const skipRulesForExt = SKIP_RULES_IN_FILE_TYPES[fileExt] || [];
|
|
155
168
|
const isRuleSkippedForFileType = skipRulesForExt.includes(ruleId);
|
|
169
|
+
// Filter out JSON data files that contain token lists / address registries
|
|
170
|
+
// (Solana base58 addresses get flagged as API keys by gitleaks)
|
|
171
|
+
const isJsonDataFile = fileExt === '.json' && (/token[-_]?list/i.test(filePath) ||
|
|
172
|
+
/address(es)?[-_]?(list|registry)/i.test(filePath) ||
|
|
173
|
+
/constituents/i.test(filePath) ||
|
|
174
|
+
/verified[-_]?tokens/i.test(filePath) ||
|
|
175
|
+
/validated[-_]?tokens/i.test(filePath) ||
|
|
176
|
+
/mint(s)?[-_]?(list|registry)/i.test(filePath) ||
|
|
177
|
+
/registry\.json$/i.test(filePath));
|
|
156
178
|
// Skip if any false positive indicator matches
|
|
157
|
-
if (isExcludedFile || isInFalsePositiveDir || isTestFile || isFalsePositiveRule || isRuleSkippedForFileType) {
|
|
179
|
+
if (isExcludedFile || isInFalsePositiveDir || isTestFile || isFalsePositiveRule || isRuleSkippedForFileType || isJsonDataFile) {
|
|
158
180
|
filteredCount++;
|
|
159
181
|
continue;
|
|
160
182
|
}
|
|
@@ -166,13 +188,34 @@ function runGitleaks(targetPath) {
|
|
|
166
188
|
severity: finding.Entropy > 4.5 ? 'critical' : 'high'
|
|
167
189
|
});
|
|
168
190
|
}
|
|
191
|
+
// Post-processing: if a single JSON file has 10+ findings, it's almost certainly
|
|
192
|
+
// a data/registry file (token lists, address mappings) not real leaked secrets
|
|
193
|
+
const fileCounts = new Map();
|
|
194
|
+
for (const f of findings) {
|
|
195
|
+
if (f.file.toLowerCase().endsWith('.json')) {
|
|
196
|
+
fileCounts.set(f.file, (fileCounts.get(f.file) || 0) + 1);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const bulkJsonFiles = new Set();
|
|
200
|
+
for (const [file, count] of fileCounts) {
|
|
201
|
+
if (count >= 10) {
|
|
202
|
+
bulkJsonFiles.add(file);
|
|
203
|
+
filteredCount += count;
|
|
204
|
+
console.log(`[SCANNER] Filtered ${count} findings from ${basename(file)} (bulk data file)`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (bulkJsonFiles.size > 0) {
|
|
208
|
+
const beforeCount = findings.length;
|
|
209
|
+
findings.splice(0, findings.length, ...findings.filter(f => !bulkJsonFiles.has(f.file)));
|
|
210
|
+
console.log(`[SCANNER] Removed ${beforeCount - findings.length} bulk JSON false positives`);
|
|
211
|
+
}
|
|
169
212
|
// Cleanup
|
|
170
213
|
try {
|
|
171
214
|
rmSync(reportPath);
|
|
172
215
|
}
|
|
173
216
|
catch { }
|
|
174
217
|
if (filteredCount > 0) {
|
|
175
|
-
console.log(`[SCANNER] Filtered ${filteredCount} false positives (test files, examples, lock files)`);
|
|
218
|
+
console.log(`[SCANNER] Filtered ${filteredCount} false positives (test files, examples, lock files, data files)`);
|
|
176
219
|
}
|
|
177
220
|
}
|
|
178
221
|
console.log(`[SCANNER] gitleaks found ${findings.length} secrets`);
|
|
@@ -777,10 +820,20 @@ const SECRET_PATTERNS = [
|
|
|
777
820
|
// OpenAI API Key
|
|
778
821
|
{ name: 'OpenAI Key', regex: /sk-[A-Za-z0-9]{32,}/g, severity: 'high' },
|
|
779
822
|
];
|
|
780
|
-
// Files/patterns to SKIP for secret scanning (test files, examples,
|
|
823
|
+
// Files/patterns to SKIP for secret scanning (test files, examples, lock files, data files)
|
|
781
824
|
const SKIP_SECRET_SCAN_PATTERNS = [
|
|
825
|
+
// Test/example files
|
|
782
826
|
/example/i, /sample/i, /test/i, /mock/i, /fixture/i, /\.test\./i, /\.spec\./i,
|
|
783
|
-
|
|
827
|
+
// Build artifacts
|
|
828
|
+
/\.d\.ts$/, /\.map$/, /\.min\./,
|
|
829
|
+
// Lock files (contain hashes that trigger false positives, never contain real secrets)
|
|
830
|
+
/package-lock\.json$/i, /pnpm-lock\.yaml$/i, /yarn\.lock$/i, /Cargo\.lock$/i, /composer\.lock$/i, /Gemfile\.lock$/i,
|
|
831
|
+
// Template/example env files (placeholders, not real secrets)
|
|
832
|
+
/\.example$/i, /\.template$/i, /\.sample$/i,
|
|
833
|
+
// Data directories (contain public addresses, not secrets)
|
|
834
|
+
/\/data\//i, /\/fixtures?\//i, /\/seeds?\//i, /\/migrations?\//i,
|
|
835
|
+
// Index/constituent data files (stock symbols, token lists etc.)
|
|
836
|
+
/index_constituents/i, /token-?list/i, /verified-tokens/i, /validated-tokens/i,
|
|
784
837
|
];
|
|
785
838
|
// Files to scan for secrets
|
|
786
839
|
const FILES_TO_SCAN = [
|
|
@@ -1408,9 +1461,26 @@ export class LocalScanner {
|
|
|
1408
1461
|
if (stats.size > 1024 * 1024)
|
|
1409
1462
|
return findings;
|
|
1410
1463
|
const content = readFileSync(filePath, 'utf-8');
|
|
1464
|
+
// Skip JSON files that are clearly data (token lists, address registries, etc.)
|
|
1465
|
+
// These contain public blockchain addresses that regex misidentifies as secrets
|
|
1466
|
+
if (fileName.endsWith('.json')) {
|
|
1467
|
+
// If a JSON file has "tokens", "addresses", "mints", "symbols" keys, it's data
|
|
1468
|
+
if (/["'](tokens|addresses|mints|symbols|constituents|verified|validated)["']\s*:/i.test(content)) {
|
|
1469
|
+
return findings;
|
|
1470
|
+
}
|
|
1471
|
+
// If JSON file has more than 100 similar-looking base58 values, it's a token/address list
|
|
1472
|
+
const base58Matches = content.match(/["'][1-9A-HJ-NP-Za-km-z]{32,44}["']/g);
|
|
1473
|
+
if (base58Matches && base58Matches.length > 50) {
|
|
1474
|
+
return findings;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1411
1477
|
const lines = content.split('\n');
|
|
1412
1478
|
for (let i = 0; i < lines.length; i++) {
|
|
1413
1479
|
const line = lines[i];
|
|
1480
|
+
// Skip lines that are clearly placeholder/example values
|
|
1481
|
+
if (/your[_-]?(?:api[_-]?key|token|secret)|REPLACE[_-]?ME|TODO|CHANGEME|xxxx|placeholder/i.test(line)) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1414
1484
|
for (const pattern of SECRET_PATTERNS) {
|
|
1415
1485
|
pattern.regex.lastIndex = 0;
|
|
1416
1486
|
const match = pattern.regex.exec(line);
|
|
@@ -545,8 +545,21 @@ function calculateScore(checks) {
|
|
|
545
545
|
const isAbandoned = checks.some(c => c.id === 'activity' && c.status === 'bad');
|
|
546
546
|
// Apply caps based on critical issues
|
|
547
547
|
if (hasSecrets) {
|
|
548
|
-
|
|
549
|
-
|
|
548
|
+
const secretsCheck = checks.find(c => c.id === 'secrets');
|
|
549
|
+
const secretCount = secretsCheck ? Math.abs(secretsCheck.points) / 10 : 1;
|
|
550
|
+
if (secretCount >= 5) {
|
|
551
|
+
// Many secrets = definitely compromised, hard cap RISKY
|
|
552
|
+
score = Math.min(score, 40);
|
|
553
|
+
}
|
|
554
|
+
else if (secretCount >= 3) {
|
|
555
|
+
// Several secrets = serious concern
|
|
556
|
+
score = Math.min(score, 50);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
// 1-2 secrets = concerning but could be marginal detection
|
|
560
|
+
// Cap at DYOR, let other signals weigh in
|
|
561
|
+
score = Math.min(score, 65);
|
|
562
|
+
}
|
|
550
563
|
}
|
|
551
564
|
if (hasNoCode) {
|
|
552
565
|
// No code = max score 40 (RISKY)
|
|
@@ -632,16 +645,24 @@ function generateSummary(checks, score, verdict) {
|
|
|
632
645
|
*/
|
|
633
646
|
function scanForSecrets(content) {
|
|
634
647
|
const secretPatterns = [
|
|
635
|
-
/AKIA[0-9A-Z]{16}/, // AWS Access Key
|
|
636
|
-
/[
|
|
637
|
-
/
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
/
|
|
648
|
+
/AKIA[0-9A-Z]{16}/, // AWS Access Key (always starts with AKIA)
|
|
649
|
+
/gh[pousr]_[A-Za-z0-9_]{36,}/, // GitHub tokens (prefixed)
|
|
650
|
+
/sk_live_[A-Za-z0-9]{24,}/, // Stripe live keys
|
|
651
|
+
/-----BEGIN\s*(?:RSA|EC|DSA|OPENSSH)?\s*PRIVATE KEY-----/, // Private keys (PEM format)
|
|
652
|
+
/password\s*[:=]\s*["'][^"']{8,}["']/i, // Hardcoded passwords with real values
|
|
653
|
+
/xox[bprs]-[0-9]{10,}-[A-Za-z0-9-]+/, // Slack tokens
|
|
654
|
+
/(?:api[_-]?key|secret[_-]?key|auth[_-]?token)\s*[:=]\s*["'][A-Za-z0-9+\/=]{20,}["']/i, // Secret assignments
|
|
641
655
|
];
|
|
656
|
+
// Skip data files (token lists, address registries)
|
|
657
|
+
if (/["'](tokens|addresses|mints|constituents|verified)["']\s*:/i.test(content)) {
|
|
658
|
+
return 0;
|
|
659
|
+
}
|
|
660
|
+
// Placeholder values are not real secrets
|
|
661
|
+
const placeholderPattern = /your[_-]?api[_-]?key|REPLACE[_-]?ME|TODO|CHANGEME|xxxx|placeholder|example/i;
|
|
642
662
|
let count = 0;
|
|
643
663
|
for (const pattern of secretPatterns) {
|
|
644
|
-
|
|
664
|
+
const match = content.match(pattern);
|
|
665
|
+
if (match && !placeholderPattern.test(match[0])) {
|
|
645
666
|
count++;
|
|
646
667
|
}
|
|
647
668
|
}
|
|
@@ -884,11 +905,28 @@ export async function performTrustScan(gitUrl) {
|
|
|
884
905
|
}
|
|
885
906
|
}
|
|
886
907
|
codeRedFlags = scanCodeForRedFlags(scannedFiles);
|
|
887
|
-
// Quick secret scan on
|
|
888
|
-
const sensitiveFiles = files.filter((f) =>
|
|
889
|
-
f.path.
|
|
890
|
-
|
|
891
|
-
|
|
908
|
+
// Quick secret scan on files that could plausibly contain secrets
|
|
909
|
+
const sensitiveFiles = files.filter((f) => {
|
|
910
|
+
const p = f.path.toLowerCase();
|
|
911
|
+
// Only scan files that could contain secrets
|
|
912
|
+
const isSensitive = p.includes('.env') ||
|
|
913
|
+
p.includes('config') ||
|
|
914
|
+
p.endsWith('.yml') ||
|
|
915
|
+
p.endsWith('.yaml');
|
|
916
|
+
// Exclude known safe files that produce false positives
|
|
917
|
+
const isSafe = p === 'package.json' ||
|
|
918
|
+
p === 'package-lock.json' ||
|
|
919
|
+
p.includes('tsconfig') ||
|
|
920
|
+
p.includes('eslint') ||
|
|
921
|
+
p.includes('prettier') ||
|
|
922
|
+
p.endsWith('.lock') ||
|
|
923
|
+
p.includes('token-list') ||
|
|
924
|
+
p.includes('tokenlist') ||
|
|
925
|
+
p.includes('/test') ||
|
|
926
|
+
p.includes('/example') ||
|
|
927
|
+
p.includes('/fixture');
|
|
928
|
+
return isSensitive && !isSafe;
|
|
929
|
+
}).slice(0, 5);
|
|
892
930
|
for (const file of sensitiveFiles) {
|
|
893
931
|
try {
|
|
894
932
|
const fileRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${file.path}`, { headers });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-security",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "AI-powered security scanner with 9-agent swarm. Detect secrets, vulnerabilities, attack paths. CLI, API, or cloud dashboard at app.aurasecurity.io",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|