@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.
Files changed (36) hide show
  1. package/bin/registry.js +106 -116
  2. package/bin/runners/context/generators/mcp.js +18 -0
  3. package/bin/runners/context/index.js +72 -4
  4. package/bin/runners/context/proof-context.js +293 -1
  5. package/bin/runners/context/security-scanner.js +311 -73
  6. package/bin/runners/lib/analyzers.js +607 -20
  7. package/bin/runners/lib/detectors-v2.js +172 -15
  8. package/bin/runners/lib/entitlements-v2.js +48 -1
  9. package/bin/runners/lib/evidence-pack.js +678 -0
  10. package/bin/runners/lib/html-proof-report.js +913 -0
  11. package/bin/runners/lib/missions/plan.js +231 -41
  12. package/bin/runners/lib/missions/templates.js +125 -0
  13. package/bin/runners/lib/scan-output.js +492 -253
  14. package/bin/runners/lib/ship-output.js +901 -641
  15. package/bin/runners/runCheckpoint.js +44 -3
  16. package/bin/runners/runContext.d.ts +4 -0
  17. package/bin/runners/runDoctor.js +10 -2
  18. package/bin/runners/runFix.js +51 -341
  19. package/bin/runners/runInit.js +11 -0
  20. package/bin/runners/runPolish.d.ts +4 -0
  21. package/bin/runners/runPolish.js +608 -29
  22. package/bin/runners/runProve.js +210 -25
  23. package/bin/runners/runReality.js +846 -101
  24. package/bin/runners/runScan.js +238 -4
  25. package/bin/runners/runShip.js +19 -3
  26. package/bin/runners/runWatch.js +14 -1
  27. package/bin/vibecheck.js +32 -2
  28. package/mcp-server/consolidated-tools.js +408 -42
  29. package/mcp-server/index.js +152 -15
  30. package/mcp-server/proof-tools.js +571 -0
  31. package/mcp-server/tier-auth.js +22 -19
  32. package/mcp-server/tools-v3.js +744 -0
  33. package/mcp-server/truth-firewall-tools.js +190 -4
  34. package/package.json +3 -1
  35. package/bin/runners/runInstall.js +0 -281
  36. 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: /sk_live_[0-9a-zA-Z]{24}/, type: "Stripe Live Key" },
20
- { pattern: /pk_live_[0-9a-zA-Z]{24}/, type: "Stripe Publishable Key" },
21
-
22
- // Generic patterns
23
- { pattern: /['"]?API[_-]?KEY['"]?\s*[:=]\s*['"][^'"]{8,}['"]/, type: "API Key" },
24
- { pattern: /['"]?SECRET[_-]?KEY['"]?\s*[:=]\s*['"][^'"]{8,}['"]/, type: "Secret Key" },
25
- { pattern: /['"]?PASSWORD['"]?\s*[:=]\s*['"][^'"]{6,}['"]/, type: "Password" },
26
- { pattern: /['"]?TOKEN['"]?\s*[:=]\s*['"][^'"]{8,}['"]/, type: "Token" },
27
- { pattern: /['"]?PRIVATE[_-]?KEY['"]?\s*[:=]\s*['"][^'"]{16,}['"]/, type: "Private Key" },
28
-
29
- // Database URLs
30
- { pattern: /mongodb:\/\/[^:]+:[^@]+@/, type: "MongoDB URL" },
31
- { pattern: /postgres:\/\/[^:]+:[^@]+@/, type: "PostgreSQL URL" },
32
- { pattern: /mysql:\/\/[^:]+:[^@]+@/, type: "MySQL URL" },
33
-
34
- // JWT tokens
35
- { pattern: /eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/, type: "JWT Token" },
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
- { pattern: /query\s*\(\s*['"]\s*\+.*\+\s*['"]/, type: "SQL Injection", severity: "high" },
44
- { pattern: /execute\s*\(\s*['"]\s*\+/, type: "SQL Injection", severity: "high" },
45
-
46
- // XSS
47
- { pattern: /dangerouslySetInnerHTML/, type: "XSS Risk", severity: "high" },
48
- { pattern: /innerHTML\s*=/, type: "XSS Risk", severity: "medium" },
49
- { pattern: /document\.write\s*\(/, type: "XSS Risk", severity: "high" },
50
-
51
- // Path Traversal
52
- { pattern: /\.\.\/\.\./, type: "Path Traversal", severity: "medium" },
53
- { pattern: /readFile\s*\(\s*.*\+/, type: "Path Traversal", severity: "high" },
54
-
55
- // Insecure Crypto
56
- { pattern: /md5\s*\(/, type: "Weak Hash", severity: "medium" },
57
- { pattern: /sha1\s*\(/, type: "Weak Hash", severity: "medium" },
58
-
59
- // Hardcoded credentials
60
- { pattern: /admin\s*:\s*['"]admin['"]/, type: "Hardcoded Credentials", severity: "high" },
61
- { pattern: /root\s*:\s*['"][^'"]{4,}['"]/, type: "Hardcoded Credentials", severity: "high" },
62
-
63
- // Debug code
64
- { pattern: /console\.log\s*\(\s*password/, type: "Password in Log", severity: "high" },
65
- { pattern: /console\.log\s*\(\s*token/, type: "Token in Log", severity: "high" },
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
- for (const pattern of SECRET_PATTERNS) {
97
- const matches = content.matchAll(new RegExp(pattern.pattern.source, 'g'));
98
- for (const match of matches) {
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: pattern.type,
104
- file: path.relative(process.cwd(), filePath).replace(/\\/g, "/"),
267
+ type: patternDef.type,
268
+ file: relativePath,
105
269
  line: lineNum,
106
- content: line.trim(),
107
- severity: "critical",
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
- * Scan file for vulnerabilities
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
- for (const pattern of VULNERABILITY_PATTERNS) {
123
- const matches = content.matchAll(new RegExp(pattern.pattern.source, 'g'));
124
- for (const match of matches) {
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: pattern.type,
130
- file: path.relative(process.cwd(), filePath).replace(/\\/g, "/"),
366
+ type: patternDef.type,
367
+ file: relativePath,
131
368
  line: lineNum,
132
- content: line.trim(),
133
- severity: pattern.severity || "medium",
134
- recommendation: getRecommendation(pattern.type),
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 and use textContent instead of innerHTML",
149
- "Path Traversal": "Validate and sanitize file paths, use path.join()",
150
- "Weak Hash": "Use stronger hashing algorithms like bcrypt or Argon2",
151
- "Hardcoded Credentials": "Use environment variables for credentials",
152
- "Password in Log": "Remove sensitive data from logs",
153
- "Token in Log": "Remove sensitive data from logs",
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";