@vibecheckai/cli 3.1.8 → 3.2.0
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/bin/registry.js +106 -116
- package/bin/runners/context/generators/mcp.js +18 -0
- package/bin/runners/context/index.js +72 -4
- package/bin/runners/context/proof-context.js +293 -1
- package/bin/runners/context/security-scanner.js +311 -73
- package/bin/runners/lib/analyzers.js +607 -20
- package/bin/runners/lib/detectors-v2.js +172 -15
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/missions/plan.js +231 -41
- package/bin/runners/lib/missions/templates.js +125 -0
- package/bin/runners/lib/scan-output.js +492 -253
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/runCheckpoint.js +44 -3
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runDoctor.js +10 -2
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +11 -0
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +608 -29
- package/bin/runners/runProve.js +210 -25
- package/bin/runners/runReality.js +846 -101
- package/bin/runners/runScan.js +238 -4
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runWatch.js +14 -1
- package/bin/vibecheck.js +32 -2
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +152 -15
- package/mcp-server/proof-tools.js +571 -0
- package/mcp-server/tier-auth.js +22 -19
- package/mcp-server/tools-v3.js +744 -0
- package/mcp-server/truth-firewall-tools.js +190 -4
- package/package.json +3 -1
- package/bin/runners/runInstall.js +0 -281
- package/bin/runners/runLabs.js +0 -341
|
@@ -1,68 +1,192 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Security Scanner Module
|
|
3
3
|
* Scans context for secrets, vulnerabilities, and sensitive data
|
|
4
|
+
* Enhanced with entropy checking and better false positive prevention
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const fs = require("fs");
|
|
7
8
|
const path = require("path");
|
|
8
9
|
const crypto = require("crypto");
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Calculate Shannon entropy of a string
|
|
13
|
+
* Higher entropy = more random = more likely to be a real secret
|
|
14
|
+
*/
|
|
15
|
+
function calculateEntropy(str) {
|
|
16
|
+
if (!str || str.length === 0) return 0;
|
|
17
|
+
|
|
18
|
+
const freq = {};
|
|
19
|
+
for (const char of str) {
|
|
20
|
+
freq[char] = (freq[char] || 0) + 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let entropy = 0;
|
|
24
|
+
const len = str.length;
|
|
25
|
+
for (const count of Object.values(freq)) {
|
|
26
|
+
const p = count / len;
|
|
27
|
+
entropy -= p * Math.log2(p);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return entropy;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a value is a known false positive
|
|
35
|
+
*/
|
|
36
|
+
function isFalsePositive(value, line, filePath) {
|
|
37
|
+
const lowerValue = value.toLowerCase();
|
|
38
|
+
const lowerLine = line.toLowerCase();
|
|
39
|
+
const lowerPath = filePath.toLowerCase();
|
|
40
|
+
|
|
41
|
+
// Common placeholder/test values
|
|
42
|
+
const falsePositiveValues = [
|
|
43
|
+
'example', 'test', 'sample', 'demo', 'placeholder', 'mock', 'fake', 'dummy',
|
|
44
|
+
'your_key', 'your_secret', 'your_token', 'changeme', 'replace_me', 'xxx',
|
|
45
|
+
'password', 'password123', 'secret', 'admin', '12345', 'qwerty'
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const fp of falsePositiveValues) {
|
|
49
|
+
if (lowerValue.includes(fp)) return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Repeating characters (low entropy)
|
|
53
|
+
if (/^(.)\1{5,}$/.test(value)) return true;
|
|
54
|
+
|
|
55
|
+
// Sequential characters
|
|
56
|
+
if (/^(012|123|234|345|456|567|678|789|abc|bcd|cde)/i.test(value)) return true;
|
|
57
|
+
|
|
58
|
+
// Test/example context
|
|
59
|
+
if (lowerLine.includes('example') || lowerLine.includes('// test')) return true;
|
|
60
|
+
if (lowerPath.includes('.test.') || lowerPath.includes('.spec.')) return true;
|
|
61
|
+
if (lowerPath.includes('__tests__') || lowerPath.includes('__mocks__')) return true;
|
|
62
|
+
if (lowerPath.includes('/fixtures/') || lowerPath.includes('/examples/')) return true;
|
|
63
|
+
|
|
64
|
+
// Documentation context
|
|
65
|
+
if (lowerPath.endsWith('.md') || lowerPath.includes('/docs/')) return true;
|
|
66
|
+
|
|
67
|
+
// Env template files
|
|
68
|
+
if (lowerPath.includes('.env.example') || lowerPath.includes('.env.template')) return true;
|
|
69
|
+
if (lowerPath.includes('.env.sample')) return true;
|
|
70
|
+
|
|
71
|
+
// Type definitions
|
|
72
|
+
if (lowerLine.includes('type ') || lowerLine.includes('interface ')) return true;
|
|
73
|
+
|
|
74
|
+
// Import statements
|
|
75
|
+
if (lowerLine.startsWith('import ') || lowerLine.includes('require(')) return true;
|
|
76
|
+
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
10
80
|
/**
|
|
11
81
|
* Secret patterns to detect
|
|
82
|
+
* Each pattern now includes minEntropy for generic patterns
|
|
12
83
|
*/
|
|
13
84
|
const SECRET_PATTERNS = [
|
|
14
|
-
// API Keys
|
|
15
|
-
{ pattern: /AIza[0-9A-Za-z_-]{35}/, type: "Google API Key" },
|
|
16
|
-
{ pattern: /AKIA[0-9A-Z]{16}/, type: "AWS Access Key" },
|
|
17
|
-
{ pattern: /xoxb-[0-9]{10}-[0-9]{10}/, type: "Slack Bot Token" },
|
|
18
|
-
{ pattern: /ghp_[a-zA-Z0-9]{36}/, type: "GitHub Personal Token" },
|
|
19
|
-
{ pattern: /
|
|
20
|
-
{ pattern: /
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{ pattern: /['"]?
|
|
25
|
-
{ pattern: /['"]?
|
|
26
|
-
{ pattern: /['"]?
|
|
27
|
-
{ pattern: /['"]?
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{ pattern: /
|
|
32
|
-
{ pattern: /
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
85
|
+
// API Keys - specific formats (high confidence, no entropy check needed)
|
|
86
|
+
{ pattern: /AIza[0-9A-Za-z_-]{35}/, type: "Google API Key", minEntropy: 3.5 },
|
|
87
|
+
{ pattern: /AKIA[0-9A-Z]{16}/, type: "AWS Access Key", minEntropy: 3.5 },
|
|
88
|
+
{ pattern: /xoxb-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/, type: "Slack Bot Token", minEntropy: 0 },
|
|
89
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/, type: "GitHub Personal Token", minEntropy: 3.5 },
|
|
90
|
+
{ pattern: /gho_[a-zA-Z0-9]{36}/, type: "GitHub OAuth Token", minEntropy: 3.5 },
|
|
91
|
+
{ pattern: /sk_live_[0-9a-zA-Z]{24,}/, type: "Stripe Live Key", minEntropy: 3.5 },
|
|
92
|
+
{ pattern: /pk_live_[0-9a-zA-Z]{24,}/, type: "Stripe Publishable Key", minEntropy: 0 },
|
|
93
|
+
|
|
94
|
+
// Generic patterns - require higher entropy to avoid false positives
|
|
95
|
+
{ pattern: /['"]?api[_-]?key['"]?\s*[:=]\s*['"]([^'"]{16,})['"]/, type: "API Key", minEntropy: 4.0, valueGroup: 1 },
|
|
96
|
+
{ pattern: /['"]?secret[_-]?key['"]?\s*[:=]\s*['"]([^'"]{16,})['"]/, type: "Secret Key", minEntropy: 4.0, valueGroup: 1 },
|
|
97
|
+
{ pattern: /['"]?password['"]?\s*[:=]\s*['"]([^'"]{8,})['"]/, type: "Password", minEntropy: 3.0, valueGroup: 1 },
|
|
98
|
+
{ pattern: /['"]?auth[_-]?token['"]?\s*[:=]\s*['"]([^'"]{16,})['"]/, type: "Auth Token", minEntropy: 4.0, valueGroup: 1 },
|
|
99
|
+
{ pattern: /['"]?private[_-]?key['"]?\s*[:=]\s*['"]([^'"]{20,})['"]/, type: "Private Key", minEntropy: 4.0, valueGroup: 1 },
|
|
100
|
+
|
|
101
|
+
// Database URLs (credentials embedded)
|
|
102
|
+
{ pattern: /mongodb(?:\+srv)?:\/\/([^:]+):([^@]+)@/, type: "MongoDB URL", minEntropy: 0 },
|
|
103
|
+
{ pattern: /postgres(?:ql)?:\/\/([^:]+):([^@]+)@/, type: "PostgreSQL URL", minEntropy: 0 },
|
|
104
|
+
{ pattern: /mysql:\/\/([^:]+):([^@]+)@/, type: "MySQL URL", minEntropy: 0 },
|
|
105
|
+
{ pattern: /redis:\/\/([^:]+):([^@]+)@/, type: "Redis URL", minEntropy: 0 },
|
|
106
|
+
|
|
107
|
+
// JWT tokens - validate structure
|
|
108
|
+
{ pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/, type: "JWT Token", minEntropy: 4.0 },
|
|
36
109
|
];
|
|
37
110
|
|
|
38
111
|
/**
|
|
39
|
-
* Vulnerability patterns
|
|
112
|
+
* Vulnerability patterns - refined to reduce false positives
|
|
113
|
+
* Each pattern now includes contextual requirements
|
|
40
114
|
*/
|
|
41
115
|
const VULNERABILITY_PATTERNS = [
|
|
42
|
-
// SQL Injection
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
116
|
+
// SQL Injection - require concatenation with user input indicators
|
|
117
|
+
{
|
|
118
|
+
pattern: /(?:query|execute|raw)\s*\(\s*['"`](?:SELECT|INSERT|UPDATE|DELETE|DROP)\s*[^'"]*\+\s*(?:req\.|user|input|param)/i,
|
|
119
|
+
type: "SQL Injection",
|
|
120
|
+
severity: "critical",
|
|
121
|
+
contextExclusions: ['parameterized', 'prepared', 'placeholder']
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// XSS - dangerouslySetInnerHTML only when value comes from user/external
|
|
125
|
+
{
|
|
126
|
+
pattern: /dangerouslySetInnerHTML\s*=\s*{{\s*__html:\s*(?:props|data|user|input|param)/,
|
|
127
|
+
type: "XSS Risk",
|
|
128
|
+
severity: "high",
|
|
129
|
+
contextExclusions: ['sanitize', 'DOMPurify', 'escape']
|
|
130
|
+
},
|
|
131
|
+
// innerHTML with dynamic content
|
|
132
|
+
{
|
|
133
|
+
pattern: /\.innerHTML\s*=\s*(?:[^'";\n]*\+|`[^`]*\$\{)/,
|
|
134
|
+
type: "XSS Risk",
|
|
135
|
+
severity: "high",
|
|
136
|
+
contextExclusions: ['sanitize', 'escape', 'encode']
|
|
137
|
+
},
|
|
138
|
+
// document.write with variables
|
|
139
|
+
{
|
|
140
|
+
pattern: /document\.write\s*\([^)]*(?:\+|\$\{)/,
|
|
141
|
+
type: "XSS Risk",
|
|
142
|
+
severity: "high"
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Path Traversal - only flag when path comes from user input
|
|
146
|
+
{
|
|
147
|
+
pattern: /(?:readFile|readdir|open|access)\s*\([^)]*(?:req\.|user|input|param)[^)]*\)/,
|
|
148
|
+
type: "Path Traversal",
|
|
149
|
+
severity: "high",
|
|
150
|
+
contextExclusions: ['path.join', 'path.resolve', 'normalize', 'sanitize']
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Insecure Crypto - only for password/credential hashing, not checksums
|
|
154
|
+
{
|
|
155
|
+
pattern: /(?:createHash|crypto)\s*\(\s*['"](?:md5|sha1)['"]\s*\)[^;]*(?:password|credential|secret)/i,
|
|
156
|
+
type: "Weak Password Hash",
|
|
157
|
+
severity: "high",
|
|
158
|
+
contextExclusions: ['checksum', 'fingerprint', 'etag', 'cache']
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// Hardcoded credentials - more specific patterns
|
|
162
|
+
{
|
|
163
|
+
pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]\s*(?:,|;|$)/,
|
|
164
|
+
type: "Hardcoded Credentials",
|
|
165
|
+
severity: "high",
|
|
166
|
+
contextExclusions: ['process.env', 'config.', 'example', 'test', 'placeholder']
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// Sensitive data in logs - only when logging actual variables
|
|
170
|
+
{
|
|
171
|
+
pattern: /console\.(?:log|info|debug)\s*\([^)]*(?:password|secret|token|apiKey|auth)[^)]*\)/,
|
|
172
|
+
type: "Sensitive Data in Log",
|
|
173
|
+
severity: "high",
|
|
174
|
+
contextExclusions: ['masked', 'redacted', '***', '[REDACTED]']
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// Eval with dynamic content
|
|
178
|
+
{
|
|
179
|
+
pattern: /\beval\s*\([^)]*(?:\+|`\$\{|concat)/,
|
|
180
|
+
type: "Code Injection",
|
|
181
|
+
severity: "critical"
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Insecure random for security purposes
|
|
185
|
+
{
|
|
186
|
+
pattern: /Math\.random\s*\(\s*\)[^;]*(?:token|secret|key|password|salt|nonce)/i,
|
|
187
|
+
type: "Insecure Random",
|
|
188
|
+
severity: "high"
|
|
189
|
+
},
|
|
66
190
|
];
|
|
67
191
|
|
|
68
192
|
/**
|
|
@@ -87,24 +211,65 @@ function findFiles(dir, extensions, maxDepth = 5, currentDepth = 0) {
|
|
|
87
211
|
}
|
|
88
212
|
|
|
89
213
|
/**
|
|
90
|
-
* Scan file for secrets
|
|
214
|
+
* Scan file for secrets with entropy checking and false positive filtering
|
|
91
215
|
*/
|
|
92
216
|
function scanForSecrets(content, filePath) {
|
|
93
217
|
const secrets = [];
|
|
94
218
|
const lines = content.split("\n");
|
|
219
|
+
const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
95
220
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
221
|
+
// Skip known safe file types
|
|
222
|
+
if (/\.(md|txt|svg|png|jpg|gif|ico|woff|woff2|ttf|eot|map)$/i.test(filePath)) {
|
|
223
|
+
return secrets;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (const patternDef of SECRET_PATTERNS) {
|
|
227
|
+
const regex = new RegExp(patternDef.pattern.source, 'gi');
|
|
228
|
+
let match;
|
|
229
|
+
|
|
230
|
+
while ((match = regex.exec(content)) !== null) {
|
|
99
231
|
const lineNum = content.substring(0, match.index).split("\n").length;
|
|
100
|
-
const line = lines[lineNum - 1];
|
|
232
|
+
const line = lines[lineNum - 1] || '';
|
|
233
|
+
|
|
234
|
+
// Extract the actual secret value (use valueGroup if specified)
|
|
235
|
+
const secretValue = patternDef.valueGroup !== undefined
|
|
236
|
+
? (match[patternDef.valueGroup] || match[0])
|
|
237
|
+
: match[0];
|
|
238
|
+
|
|
239
|
+
// Check for false positives first
|
|
240
|
+
if (isFalsePositive(secretValue, line, filePath)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check entropy if required
|
|
245
|
+
if (patternDef.minEntropy && patternDef.minEntropy > 0) {
|
|
246
|
+
const entropy = calculateEntropy(secretValue);
|
|
247
|
+
if (entropy < patternDef.minEntropy) {
|
|
248
|
+
continue; // Too low entropy, likely not a real secret
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Additional validation for specific types
|
|
253
|
+
if (patternDef.type === "JWT Token") {
|
|
254
|
+
// Validate JWT structure
|
|
255
|
+
const parts = secretValue.split('.');
|
|
256
|
+
if (parts.length !== 3) continue;
|
|
257
|
+
// Check if header looks valid (should be base64 JSON starting with eyJ)
|
|
258
|
+
try {
|
|
259
|
+
const header = Buffer.from(parts[0], 'base64url').toString();
|
|
260
|
+
if (!header.includes('{') || !header.includes('alg')) continue;
|
|
261
|
+
} catch {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
101
265
|
|
|
102
266
|
secrets.push({
|
|
103
|
-
type:
|
|
104
|
-
file:
|
|
267
|
+
type: patternDef.type,
|
|
268
|
+
file: relativePath,
|
|
105
269
|
line: lineNum,
|
|
106
|
-
content: line.trim(),
|
|
107
|
-
severity:
|
|
270
|
+
content: maskSensitiveLine(line.trim()),
|
|
271
|
+
severity: getSeverity(patternDef.type),
|
|
272
|
+
entropy: calculateEntropy(secretValue).toFixed(2),
|
|
108
273
|
});
|
|
109
274
|
}
|
|
110
275
|
}
|
|
@@ -113,25 +278,97 @@ function scanForSecrets(content, filePath) {
|
|
|
113
278
|
}
|
|
114
279
|
|
|
115
280
|
/**
|
|
116
|
-
*
|
|
281
|
+
* Mask sensitive values in the line for safe display
|
|
282
|
+
*/
|
|
283
|
+
function maskSensitiveLine(line) {
|
|
284
|
+
// Mask potential secret values (keep first 4 and last 4 chars if long enough)
|
|
285
|
+
return line.replace(/(['"])[A-Za-z0-9_\-/+=]{12,}(['"])/g, (match, q1, q2) => {
|
|
286
|
+
const inner = match.slice(1, -1);
|
|
287
|
+
if (inner.length > 12) {
|
|
288
|
+
return `${q1}${inner.slice(0, 4)}****${inner.slice(-4)}${q2}`;
|
|
289
|
+
}
|
|
290
|
+
return `${q1}****${q2}`;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get severity based on secret type
|
|
296
|
+
*/
|
|
297
|
+
function getSeverity(type) {
|
|
298
|
+
const criticalTypes = [
|
|
299
|
+
"AWS Access Key", "Stripe Live Key", "GitHub Personal Token",
|
|
300
|
+
"GitHub OAuth Token", "Private Key", "Secret Key"
|
|
301
|
+
];
|
|
302
|
+
const highTypes = [
|
|
303
|
+
"Google API Key", "Slack Bot Token", "MongoDB URL",
|
|
304
|
+
"PostgreSQL URL", "MySQL URL", "Redis URL", "Password", "Auth Token"
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
if (criticalTypes.includes(type)) return "critical";
|
|
308
|
+
if (highTypes.includes(type)) return "high";
|
|
309
|
+
return "medium";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Scan file for vulnerabilities with context-aware filtering
|
|
117
314
|
*/
|
|
118
315
|
function scanForVulnerabilities(content, filePath) {
|
|
119
316
|
const vulnerabilities = [];
|
|
120
317
|
const lines = content.split("\n");
|
|
318
|
+
const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
319
|
+
const lowerPath = relativePath.toLowerCase();
|
|
320
|
+
|
|
321
|
+
// Skip test files for vulnerability scanning (tests often contain intentional "bad" patterns)
|
|
322
|
+
if (lowerPath.includes('.test.') || lowerPath.includes('.spec.') ||
|
|
323
|
+
lowerPath.includes('__tests__') || lowerPath.includes('__mocks__')) {
|
|
324
|
+
return vulnerabilities;
|
|
325
|
+
}
|
|
121
326
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
327
|
+
// Skip documentation and examples
|
|
328
|
+
if (lowerPath.endsWith('.md') || lowerPath.includes('/docs/') ||
|
|
329
|
+
lowerPath.includes('/examples/')) {
|
|
330
|
+
return vulnerabilities;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (const patternDef of VULNERABILITY_PATTERNS) {
|
|
334
|
+
const regex = new RegExp(patternDef.pattern.source, 'gi');
|
|
335
|
+
let match;
|
|
336
|
+
|
|
337
|
+
while ((match = regex.exec(content)) !== null) {
|
|
125
338
|
const lineNum = content.substring(0, match.index).split("\n").length;
|
|
126
|
-
const line = lines[lineNum - 1];
|
|
339
|
+
const line = lines[lineNum - 1] || '';
|
|
340
|
+
const lowerLine = line.toLowerCase();
|
|
341
|
+
|
|
342
|
+
// Check context exclusions - skip if any exclusion pattern is present
|
|
343
|
+
if (patternDef.contextExclusions) {
|
|
344
|
+
const excluded = patternDef.contextExclusions.some(exclusion =>
|
|
345
|
+
lowerLine.includes(exclusion.toLowerCase())
|
|
346
|
+
);
|
|
347
|
+
if (excluded) continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Skip if the line is a comment
|
|
351
|
+
const trimmedLine = line.trim();
|
|
352
|
+
if (trimmedLine.startsWith('//') || trimmedLine.startsWith('/*') ||
|
|
353
|
+
trimmedLine.startsWith('*') || trimmedLine.startsWith('#')) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Skip if in a multi-line comment context
|
|
358
|
+
const beforeMatch = content.substring(0, match.index);
|
|
359
|
+
const lastCommentStart = beforeMatch.lastIndexOf('/*');
|
|
360
|
+
const lastCommentEnd = beforeMatch.lastIndexOf('*/');
|
|
361
|
+
if (lastCommentStart > lastCommentEnd) {
|
|
362
|
+
continue; // We're inside a multi-line comment
|
|
363
|
+
}
|
|
127
364
|
|
|
128
365
|
vulnerabilities.push({
|
|
129
|
-
type:
|
|
130
|
-
file:
|
|
366
|
+
type: patternDef.type,
|
|
367
|
+
file: relativePath,
|
|
131
368
|
line: lineNum,
|
|
132
|
-
content:
|
|
133
|
-
severity:
|
|
134
|
-
recommendation: getRecommendation(
|
|
369
|
+
content: trimmedLine.substring(0, 100), // Limit line length
|
|
370
|
+
severity: patternDef.severity || "medium",
|
|
371
|
+
recommendation: getRecommendation(patternDef.type),
|
|
135
372
|
});
|
|
136
373
|
}
|
|
137
374
|
}
|
|
@@ -144,13 +381,14 @@ function scanForVulnerabilities(content, filePath) {
|
|
|
144
381
|
*/
|
|
145
382
|
function getRecommendation(type) {
|
|
146
383
|
const recommendations = {
|
|
147
|
-
"SQL Injection": "Use parameterized queries or prepared statements",
|
|
148
|
-
"XSS Risk": "Sanitize user input
|
|
149
|
-
"Path Traversal": "Validate and sanitize file paths
|
|
150
|
-
"Weak Hash": "Use
|
|
151
|
-
"Hardcoded Credentials": "Use environment variables
|
|
152
|
-
"
|
|
153
|
-
"
|
|
384
|
+
"SQL Injection": "Use parameterized queries or prepared statements. Never concatenate user input into SQL.",
|
|
385
|
+
"XSS Risk": "Sanitize user input with DOMPurify or similar. Use textContent instead of innerHTML when possible.",
|
|
386
|
+
"Path Traversal": "Validate and sanitize file paths. Use path.join() and verify paths don't escape the base directory.",
|
|
387
|
+
"Weak Password Hash": "Use bcrypt, Argon2, or scrypt for password hashing. MD5/SHA1 are cryptographically broken.",
|
|
388
|
+
"Hardcoded Credentials": "Use environment variables or a secrets manager. Never commit credentials to version control.",
|
|
389
|
+
"Sensitive Data in Log": "Remove or mask sensitive data before logging. Use structured logging with redaction.",
|
|
390
|
+
"Code Injection": "Never use eval() with user input. Consider safer alternatives like JSON.parse() or a sandboxed environment.",
|
|
391
|
+
"Insecure Random": "Use crypto.randomBytes() or crypto.randomUUID() for security-sensitive random values.",
|
|
154
392
|
};
|
|
155
393
|
|
|
156
394
|
return recommendations[type] || "Review and fix the security issue";
|