@vibecheckai/cli 3.3.0 → 3.5.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 (170) hide show
  1. package/bin/registry.js +389 -269
  2. package/bin/runners/cli-utils.js +2 -33
  3. package/bin/runners/context/generators/cursor.js +49 -2
  4. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
  5. package/bin/runners/lib/analyzers.js +599 -142
  6. package/bin/runners/lib/audit-logger.js +532 -0
  7. package/bin/runners/lib/authority/authorities/architecture.js +364 -0
  8. package/bin/runners/lib/authority/authorities/compliance.js +341 -0
  9. package/bin/runners/lib/authority/authorities/human.js +343 -0
  10. package/bin/runners/lib/authority/authorities/quality.js +420 -0
  11. package/bin/runners/lib/authority/authorities/security.js +228 -0
  12. package/bin/runners/lib/authority/index.js +293 -0
  13. package/bin/runners/lib/authority-badge.js +425 -425
  14. package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
  15. package/bin/runners/lib/cli-charts.js +368 -0
  16. package/bin/runners/lib/cli-config-display.js +405 -0
  17. package/bin/runners/lib/cli-demo.js +275 -0
  18. package/bin/runners/lib/cli-errors.js +438 -0
  19. package/bin/runners/lib/cli-help-formatter.js +439 -0
  20. package/bin/runners/lib/cli-interactive-menu.js +509 -0
  21. package/bin/runners/lib/cli-prompts.js +441 -0
  22. package/bin/runners/lib/cli-scan-cards.js +362 -0
  23. package/bin/runners/lib/compliance-reporter.js +710 -0
  24. package/bin/runners/lib/conductor/index.js +671 -0
  25. package/bin/runners/lib/easy/README.md +123 -0
  26. package/bin/runners/lib/easy/index.js +140 -0
  27. package/bin/runners/lib/easy/interactive-wizard.js +788 -0
  28. package/bin/runners/lib/easy/one-click-firewall.js +564 -0
  29. package/bin/runners/lib/easy/zero-config-reality.js +714 -0
  30. package/bin/runners/lib/engines/accessibility-engine.js +218 -18
  31. package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
  32. package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
  33. package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
  34. package/bin/runners/lib/engines/confidence-scoring.js +276 -0
  35. package/bin/runners/lib/engines/context-detection.js +264 -0
  36. package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
  37. package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
  38. package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
  39. package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
  40. package/bin/runners/lib/engines/env-variables-engine.js +458 -0
  41. package/bin/runners/lib/engines/error-handling-engine.js +437 -0
  42. package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
  43. package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
  44. package/bin/runners/lib/engines/framework-detection.js +508 -0
  45. package/bin/runners/lib/engines/import-order-engine.js +429 -0
  46. package/bin/runners/lib/engines/mock-data-engine.js +53 -10
  47. package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
  48. package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
  49. package/bin/runners/lib/engines/orchestrator.js +334 -0
  50. package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
  51. package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
  52. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
  53. package/bin/runners/lib/engines/type-aware-engine.js +263 -39
  54. package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
  55. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
  56. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
  57. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
  58. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
  59. package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
  60. package/bin/runners/lib/enhanced-features/index.js +305 -0
  61. package/bin/runners/lib/enhanced-output.js +631 -0
  62. package/bin/runners/lib/enterprise.js +300 -0
  63. package/bin/runners/lib/entitlements-v2.js +161 -478
  64. package/bin/runners/lib/firewall/command-validator.js +351 -0
  65. package/bin/runners/lib/firewall/config.js +341 -0
  66. package/bin/runners/lib/firewall/content-validator.js +519 -0
  67. package/bin/runners/lib/firewall/index.js +101 -0
  68. package/bin/runners/lib/firewall/path-validator.js +256 -0
  69. package/bin/runners/lib/html-proof-report.js +350 -700
  70. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
  71. package/bin/runners/lib/mcp-utils.js +425 -0
  72. package/bin/runners/lib/missions/plan.js +46 -6
  73. package/bin/runners/lib/missions/templates.js +232 -0
  74. package/bin/runners/lib/output/index.js +1022 -0
  75. package/bin/runners/lib/policy-engine.js +652 -0
  76. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
  77. package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
  78. package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
  79. package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
  80. package/bin/runners/lib/polish/autofix/index.js +200 -0
  81. package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
  82. package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
  83. package/bin/runners/lib/polish/backend-checks.js +148 -0
  84. package/bin/runners/lib/polish/documentation-checks.js +111 -0
  85. package/bin/runners/lib/polish/frontend-checks.js +168 -0
  86. package/bin/runners/lib/polish/index.js +71 -0
  87. package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
  88. package/bin/runners/lib/polish/library-detection.js +175 -0
  89. package/bin/runners/lib/polish/performance-checks.js +100 -0
  90. package/bin/runners/lib/polish/security-checks.js +148 -0
  91. package/bin/runners/lib/polish/utils.js +203 -0
  92. package/bin/runners/lib/prompt-builder.js +540 -0
  93. package/bin/runners/lib/proof-certificate.js +634 -0
  94. package/bin/runners/lib/reality/accessibility-audit.js +946 -0
  95. package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
  96. package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
  97. package/bin/runners/lib/reality/performance-tracker.js +1077 -0
  98. package/bin/runners/lib/reality/scenario-generator.js +1404 -0
  99. package/bin/runners/lib/reality/visual-regression.js +852 -0
  100. package/bin/runners/lib/reality-profiler.js +717 -0
  101. package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
  102. package/bin/runners/lib/review/ai-code-review.js +832 -0
  103. package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
  104. package/bin/runners/lib/sbom-generator.js +641 -0
  105. package/bin/runners/lib/scan-output-enhanced.js +512 -0
  106. package/bin/runners/lib/scan-output.js +65 -19
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/ship-output.js +18 -25
  109. package/bin/runners/lib/terminal-ui.js +113 -1
  110. package/bin/runners/lib/unified-cli-output.js +603 -430
  111. package/bin/runners/lib/upsell.js +90 -338
  112. package/bin/runners/lib/validators/contract-validator.js +283 -0
  113. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  114. package/bin/runners/lib/validators/dep-audit.js +245 -0
  115. package/bin/runners/lib/validators/env-validator.js +319 -0
  116. package/bin/runners/lib/validators/index.js +120 -0
  117. package/bin/runners/lib/validators/license-checker.js +252 -0
  118. package/bin/runners/lib/validators/route-validator.js +290 -0
  119. package/bin/runners/runAIAgent.js +5 -10
  120. package/bin/runners/runAgent.js +3 -0
  121. package/bin/runners/runApprove.js +1233 -1200
  122. package/bin/runners/runAuth.js +22 -1
  123. package/bin/runners/runAuthority.js +528 -0
  124. package/bin/runners/runCheckpoint.js +4 -24
  125. package/bin/runners/runClassify.js +862 -859
  126. package/bin/runners/runConductor.js +772 -0
  127. package/bin/runners/runContainer.js +366 -0
  128. package/bin/runners/runContext.js +3 -0
  129. package/bin/runners/runDoctor.js +28 -41
  130. package/bin/runners/runEasy.js +410 -0
  131. package/bin/runners/runFirewall.js +3 -0
  132. package/bin/runners/runFirewallHook.js +3 -0
  133. package/bin/runners/runFix.js +76 -66
  134. package/bin/runners/runGuard.js +411 -18
  135. package/bin/runners/runIaC.js +372 -0
  136. package/bin/runners/runInit.js +10 -60
  137. package/bin/runners/runMcp.js +11 -12
  138. package/bin/runners/runPolish.js +240 -64
  139. package/bin/runners/runPromptFirewall.js +5 -12
  140. package/bin/runners/runProve.js +20 -55
  141. package/bin/runners/runReality.js +68 -59
  142. package/bin/runners/runReport.js +31 -5
  143. package/bin/runners/runRuntime.js +5 -8
  144. package/bin/runners/runScan.js +194 -1273
  145. package/bin/runners/runShip.js +695 -47
  146. package/bin/runners/runTruth.js +3 -0
  147. package/bin/runners/runValidate.js +7 -11
  148. package/bin/runners/runVibe.js +791 -0
  149. package/bin/runners/runWatch.js +14 -23
  150. package/bin/vibecheck.js +179 -65
  151. package/mcp-server/index.js +202 -636
  152. package/mcp-server/lib/api-client.cjs +7 -299
  153. package/mcp-server/package.json +1 -1
  154. package/mcp-server/tier-auth.js +175 -574
  155. package/mcp-server/tools-v3.js +800 -505
  156. package/mcp-server/tools.js +495 -0
  157. package/package.json +1 -1
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  166. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  167. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  168. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  169. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  170. package/mcp-server/index-v1.js +0 -698
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Security Vulnerabilities Detection Engine
3
3
  * Detects SQL injection, XSS, command injection, path traversal, and other security issues
4
+ * Enhanced with smart detection to reduce false positives:
5
+ * - Detects parameterized queries (prepared statements)
6
+ * - Detects sanitization libraries (DOMPurify, xss, sanitize-html)
7
+ * - Detects path validation (path.resolve, path.normalize)
8
+ * - Framework-specific patterns (Prisma, Drizzle, Knex, TypeORM)
4
9
  */
5
10
 
6
11
  const { getAST } = require("./ast-cache");
@@ -8,6 +13,138 @@ const traverse = require("@babel/traverse").default;
8
13
  const t = require("@babel/types");
9
14
  const { shouldExcludeFile } = require("./file-filter");
10
15
 
16
+ /**
17
+ * Safe ORM/Query Builder patterns that use parameterized queries by default
18
+ */
19
+ const SAFE_ORM_PATTERNS = [
20
+ // Prisma
21
+ /prisma\.\w+\.(?:findUnique|findFirst|findMany|create|update|delete|upsert)/i,
22
+ /prisma\.\$queryRaw`/i, // Tagged template = safe
23
+ /prisma\.\$executeRaw`/i,
24
+ // Drizzle
25
+ /db\.select\(\)|db\.insert\(\)|db\.update\(\)|db\.delete\(\)/i,
26
+ // TypeORM
27
+ /\.createQueryBuilder\(\)/i,
28
+ /getRepository\(\)\.find/i,
29
+ // Knex
30
+ /knex\(\s*['"]?\w+['"]?\s*\)\.where/i,
31
+ /knex\.raw\(\s*['"][^'"]+['"]\s*,\s*\[/i, // knex.raw with params array
32
+ // Sequelize
33
+ /\.findAll\(|\.findOne\(|\.create\(|\.update\(/i,
34
+ // MongoDB/Mongoose
35
+ /\.find\(\{|\.findOne\(\{|\.aggregate\(\[/i,
36
+ ];
37
+
38
+ /**
39
+ * Check if a line uses a safe ORM pattern
40
+ */
41
+ function usesSafeORM(line, surroundingLines) {
42
+ const context = surroundingLines ? [...surroundingLines, line].join('\n') : line;
43
+ return SAFE_ORM_PATTERNS.some(pattern => pattern.test(context));
44
+ }
45
+
46
+ /**
47
+ * Sanitization library patterns
48
+ */
49
+ const SANITIZATION_PATTERNS = [
50
+ // DOMPurify
51
+ /DOMPurify\.sanitize\(/i,
52
+ /purify\.sanitize\(/i,
53
+ // xss library
54
+ /\bxss\s*\(/i,
55
+ /filterXSS\(/i,
56
+ // sanitize-html
57
+ /sanitizeHtml\(/i,
58
+ /sanitize\(/i,
59
+ // escape-html
60
+ /escapeHtml\(/i,
61
+ /escape\(/i,
62
+ // React's built-in escaping (JSX)
63
+ /dangerouslySetInnerHTML/i, // When this is NOT present, React escapes by default
64
+ // encode-html-entities
65
+ /htmlEncode\(/i,
66
+ /encodeHTML\(/i,
67
+ ];
68
+
69
+ /**
70
+ * Check if content is sanitized before use
71
+ */
72
+ function isSanitized(line, surroundingLines) {
73
+ const context = surroundingLines ? [...surroundingLines, line].join('\n') : line;
74
+ return SANITIZATION_PATTERNS.some(pattern => pattern.test(context));
75
+ }
76
+
77
+ /**
78
+ * Path validation patterns
79
+ */
80
+ const PATH_VALIDATION_PATTERNS = [
81
+ // path.resolve normalizes and makes absolute
82
+ /path\.resolve\(/i,
83
+ // path.normalize removes .. traversals
84
+ /path\.normalize\(/i,
85
+ // path.join with validation
86
+ /path\.join\(\s*__dirname/i,
87
+ /path\.join\(\s*process\.cwd\(\)/i,
88
+ // Common validation patterns
89
+ /\.startsWith\(\s*(?:baseDir|rootDir|allowedPath|safeDir)/i,
90
+ /\.includes\(\s*['"]\.\.["']\s*\)/i, // Manual check for ..
91
+ /!.*\.includes\(\s*['"]\.\.["']\s*\)/i, // Negated check
92
+ ];
93
+
94
+ /**
95
+ * Check if path is validated/sanitized
96
+ */
97
+ function isPathValidated(line, surroundingLines) {
98
+ const context = surroundingLines ? [...surroundingLines, line].join('\n') : line;
99
+ return PATH_VALIDATION_PATTERNS.some(pattern => pattern.test(context));
100
+ }
101
+
102
+ /**
103
+ * Check if SQL query uses parameterized placeholders
104
+ */
105
+ function usesParameterizedQuery(args, code) {
106
+ if (!args || args.length === 0) return false;
107
+
108
+ const firstArg = args[0];
109
+
110
+ // Check for placeholder patterns in the query string
111
+ if (t.isStringLiteral(firstArg)) {
112
+ const query = firstArg.value;
113
+ // PostgreSQL: $1, $2, etc.
114
+ if (/\$\d+/.test(query)) return true;
115
+ // MySQL: ?, ?? placeholders
116
+ if (/\?/.test(query)) return true;
117
+ // Named parameters: :name, @name
118
+ if (/[:@]\w+/.test(query)) return true;
119
+ }
120
+
121
+ // Check for tagged template literals (safe by default in most ORMs)
122
+ if (t.isTaggedTemplateExpression(args[0])) {
123
+ return true;
124
+ }
125
+
126
+ // Check if there's a second argument that's an array (params)
127
+ if (args.length >= 2 && t.isArrayExpression(args[1])) {
128
+ return true;
129
+ }
130
+
131
+ // Check if second argument is an object (named params)
132
+ if (args.length >= 2 && t.isObjectExpression(args[1])) {
133
+ return true;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Get surrounding lines for context analysis
141
+ */
142
+ function getSurroundingLines(lines, lineIndex, range = 3) {
143
+ const start = Math.max(0, lineIndex - range);
144
+ const end = Math.min(lines.length, lineIndex + range + 1);
145
+ return lines.slice(start, end);
146
+ }
147
+
11
148
  /**
12
149
  * Analyze a file for security vulnerabilities
13
150
  */
@@ -22,7 +159,7 @@ function analyzeSecurityVulnerabilities(code, filePath) {
22
159
 
23
160
  const lines = code.split("\n");
24
161
 
25
- // SQL Injection patterns
162
+ // SQL Injection patterns - Enhanced with ORM/parameterization detection
26
163
  traverse(ast, {
27
164
  CallExpression(path) {
28
165
  const node = path.node;
@@ -34,24 +171,44 @@ function analyzeSecurityVulnerabilities(code, filePath) {
34
171
 
35
172
  // Database query methods
36
173
  if (t.isIdentifier(prop) &&
37
- ["query", "execute", "exec", "run"].includes(prop.name)) {
174
+ ["query", "execute", "exec", "run", "raw"].includes(prop.name)) {
175
+
176
+ const lineNum = node.loc.start.line;
177
+ const lineContent = lines[lineNum - 1] || "";
178
+ const surroundingLines = getSurroundingLines(lines, lineNum - 1);
179
+
180
+ // Skip if using safe ORM patterns
181
+ if (usesSafeORM(lineContent, surroundingLines)) {
182
+ return;
183
+ }
184
+
185
+ // Skip if using parameterized queries
186
+ if (usesParameterizedQuery(node.arguments, code)) {
187
+ return;
188
+ }
38
189
 
39
190
  // Check if arguments contain template literals or string concatenation
40
191
  for (const arg of node.arguments) {
41
192
  if (t.isTemplateLiteral(arg) ||
42
193
  (t.isBinaryExpression(arg) && arg.operator === "+")) {
43
- const line = node.loc.start.line;
194
+
195
+ // Double-check it's not a tagged template (safe)
196
+ if (t.isTaggedTemplateExpression(arg)) {
197
+ continue;
198
+ }
199
+
44
200
  findings.push({
45
201
  type: "sql_injection",
46
202
  severity: "BLOCK",
47
203
  category: "Security",
48
204
  file: filePath,
49
- line,
205
+ line: lineNum,
50
206
  column: node.loc.start.column,
51
207
  title: "Potential SQL injection vulnerability",
52
- message: "SQL query constructed with user input without parameterization",
53
- codeSnippet: lines[line - 1]?.trim(),
208
+ message: "SQL query constructed with string interpolation. Use parameterized queries instead.",
209
+ codeSnippet: lineContent.trim(),
54
210
  confidence: "high",
211
+ fixHint: "Use parameterized queries: query('SELECT * FROM users WHERE id = $1', [userId])",
55
212
  });
56
213
  break;
57
214
  }
@@ -61,7 +218,7 @@ function analyzeSecurityVulnerabilities(code, filePath) {
61
218
  },
62
219
  });
63
220
 
64
- // XSS vulnerabilities
221
+ // XSS vulnerabilities - Enhanced with sanitization detection
65
222
  traverse(ast, {
66
223
  CallExpression(path) {
67
224
  const node = path.node;
@@ -74,21 +231,38 @@ function analyzeSecurityVulnerabilities(code, filePath) {
74
231
  if (t.isIdentifier(prop) &&
75
232
  ["innerHTML", "outerHTML", "insertAdjacentHTML"].includes(prop.name)) {
76
233
 
234
+ const lineNum = node.loc.start.line;
235
+ const lineContent = lines[lineNum - 1] || "";
236
+ const surroundingLines = getSurroundingLines(lines, lineNum - 1, 5);
237
+
238
+ // Skip if content is sanitized
239
+ if (isSanitized(lineContent, surroundingLines)) {
240
+ return;
241
+ }
242
+
77
243
  // Check if argument comes from user input or URL params
78
244
  for (const arg of node.arguments) {
79
245
  if (t.isIdentifier(arg) || t.isMemberExpression(arg)) {
80
- const line = node.loc.start.line;
246
+ // Check if the argument itself is a sanitize call
247
+ if (t.isCallExpression(arg)) {
248
+ const argCode = code.substring(arg.start, arg.end);
249
+ if (SANITIZATION_PATTERNS.some(p => p.test(argCode))) {
250
+ continue;
251
+ }
252
+ }
253
+
81
254
  findings.push({
82
255
  type: "xss",
83
256
  severity: "BLOCK",
84
257
  category: "Security",
85
258
  file: filePath,
86
- line,
259
+ line: lineNum,
87
260
  column: node.loc.start.column,
88
261
  title: "Potential XSS vulnerability",
89
- message: `Using ${prop.name} with potentially unsafe content`,
90
- codeSnippet: lines[line - 1]?.trim(),
262
+ message: `Using ${prop.name} with potentially unsafe content. Sanitize before rendering.`,
263
+ codeSnippet: lineContent.trim(),
91
264
  confidence: "med",
265
+ fixHint: "Use DOMPurify.sanitize(content) or sanitize-html before setting innerHTML",
92
266
  });
93
267
  break;
94
268
  }
@@ -97,75 +271,229 @@ function analyzeSecurityVulnerabilities(code, filePath) {
97
271
  }
98
272
  },
99
273
  });
274
+
275
+ // Also check for dangerouslySetInnerHTML in React
276
+ traverse(ast, {
277
+ JSXAttribute(path) {
278
+ const node = path.node;
279
+
280
+ if (t.isJSXIdentifier(node.name, { name: "dangerouslySetInnerHTML" })) {
281
+ const lineNum = node.loc.start.line;
282
+ const lineContent = lines[lineNum - 1] || "";
283
+ const surroundingLines = getSurroundingLines(lines, lineNum - 1, 5);
284
+
285
+ // Skip if content is sanitized
286
+ if (isSanitized(lineContent, surroundingLines)) {
287
+ return;
288
+ }
289
+
290
+ findings.push({
291
+ type: "xss",
292
+ severity: "WARN",
293
+ category: "Security",
294
+ file: filePath,
295
+ line: lineNum,
296
+ column: node.loc.start.column,
297
+ title: "Potential XSS via dangerouslySetInnerHTML",
298
+ message: "Using dangerouslySetInnerHTML without visible sanitization",
299
+ codeSnippet: lineContent.trim(),
300
+ confidence: "med",
301
+ fixHint: "Sanitize content with DOMPurify.sanitize() before rendering",
302
+ });
303
+ }
304
+ },
305
+ });
100
306
 
101
- // Command injection
307
+ // Command injection - Enhanced with shell escape detection
308
+ const SHELL_ESCAPE_PATTERNS = [
309
+ /shellEscape\(/i,
310
+ /escapeShell\(/i,
311
+ /shell-escape/i,
312
+ /shellescape/i,
313
+ /quote\(/i, // shell-quote
314
+ ];
315
+
316
+ function isShellEscaped(line, surroundingLines) {
317
+ const context = surroundingLines ? [...surroundingLines, line].join('\n') : line;
318
+ return SHELL_ESCAPE_PATTERNS.some(pattern => pattern.test(context));
319
+ }
320
+
102
321
  traverse(ast, {
103
322
  CallExpression(path) {
104
323
  const node = path.node;
105
324
 
106
325
  // Dangerous command execution methods
326
+ let methodName = null;
327
+
107
328
  if (t.isIdentifier(node.callee)) {
108
- const dangerousMethods = ["exec", "spawn", "execSync", "spawnSync"];
329
+ methodName = node.callee.name;
330
+ } else if (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property)) {
331
+ methodName = node.callee.property.name;
332
+ }
333
+
334
+ const dangerousMethods = ["exec", "spawn", "execSync", "spawnSync", "execFile", "execFileSync"];
335
+
336
+ if (methodName && dangerousMethods.includes(methodName)) {
337
+ const lineNum = node.loc.start.line;
338
+ const lineContent = lines[lineNum - 1] || "";
339
+ const surroundingLines = getSurroundingLines(lines, lineNum - 1, 5);
109
340
 
110
- if (dangerousMethods.includes(node.callee.name)) {
111
- // Check if arguments contain user input
112
- for (const arg of node.arguments) {
113
- if (t.isTemplateLiteral(arg) ||
114
- (t.isBinaryExpression(arg) && arg.operator === "+")) {
115
- const line = node.loc.start.line;
116
- findings.push({
117
- type: "command_injection",
118
- severity: "BLOCK",
119
- category: "Security",
120
- file: filePath,
121
- line,
122
- column: node.loc.start.column,
123
- title: "Potential command injection vulnerability",
124
- message: "Command execution with potentially unsafe user input",
125
- codeSnippet: lines[line - 1]?.trim(),
126
- confidence: "high",
127
- });
128
- break;
129
- }
341
+ // Skip if shell-escaped
342
+ if (isShellEscaped(lineContent, surroundingLines)) {
343
+ return;
344
+ }
345
+
346
+ // Check if arguments contain user input
347
+ for (const arg of node.arguments) {
348
+ if (t.isTemplateLiteral(arg) ||
349
+ (t.isBinaryExpression(arg) && arg.operator === "+")) {
350
+ findings.push({
351
+ type: "command_injection",
352
+ severity: "BLOCK",
353
+ category: "Security",
354
+ file: filePath,
355
+ line: lineNum,
356
+ column: node.loc.start.column,
357
+ title: "Potential command injection vulnerability",
358
+ message: "Command execution with string interpolation. Use array args or shell-escape.",
359
+ codeSnippet: lineContent.trim(),
360
+ confidence: "high",
361
+ fixHint: "Use spawn with array arguments: spawn('cmd', [arg1, arg2]) instead of shell strings",
362
+ });
363
+ break;
130
364
  }
131
365
  }
132
366
  }
133
367
  },
134
368
  });
135
369
 
136
- // Path traversal
370
+ // SSRF (Server-Side Request Forgery) detection
371
+ traverse(ast, {
372
+ CallExpression(path) {
373
+ const node = path.node;
374
+
375
+ // Check for fetch, axios, http.request with dynamic URLs
376
+ let isHttpCall = false;
377
+ let methodName = "";
378
+
379
+ if (t.isIdentifier(node.callee)) {
380
+ isHttpCall = ["fetch", "axios", "request", "got", "superagent"].includes(node.callee.name);
381
+ methodName = node.callee.name;
382
+ } else if (t.isMemberExpression(node.callee)) {
383
+ const prop = node.callee.property;
384
+ if (t.isIdentifier(prop)) {
385
+ isHttpCall = ["get", "post", "put", "delete", "request", "fetch"].includes(prop.name);
386
+ methodName = prop.name;
387
+ }
388
+ }
389
+
390
+ if (isHttpCall && node.arguments.length > 0) {
391
+ const urlArg = node.arguments[0];
392
+
393
+ // Check if URL contains user input
394
+ if (t.isTemplateLiteral(urlArg) && urlArg.expressions && urlArg.expressions.length > 0) {
395
+ const lineNum = node.loc.start.line;
396
+ const lineContent = lines[lineNum - 1] || "";
397
+ const surroundingLines = getSurroundingLines(lines, lineNum - 1, 5);
398
+
399
+ // Check for URL validation patterns
400
+ const urlValidationPatterns = [
401
+ /new URL\(/i,
402
+ /url\.parse\(/i,
403
+ /isValidUrl\(/i,
404
+ /validateUrl\(/i,
405
+ /allowedHosts/i,
406
+ /whitelist/i,
407
+ ];
408
+
409
+ const hasUrlValidation = urlValidationPatterns.some(p =>
410
+ p.test(lineContent) || p.test(surroundingLines.join('\n'))
411
+ );
412
+
413
+ if (!hasUrlValidation) {
414
+ findings.push({
415
+ type: "ssrf",
416
+ severity: "WARN",
417
+ category: "Security",
418
+ file: filePath,
419
+ line: lineNum,
420
+ column: node.loc.start.column,
421
+ title: "Potential SSRF vulnerability",
422
+ message: "HTTP request with user-controlled URL without visible validation",
423
+ codeSnippet: lineContent.trim(),
424
+ confidence: "med",
425
+ fixHint: "Validate URLs against an allowlist of hosts before making requests",
426
+ });
427
+ }
428
+ }
429
+ }
430
+ },
431
+ });
432
+
433
+ // Path traversal - Enhanced with path validation detection
137
434
  traverse(ast, {
138
435
  CallExpression(path) {
139
436
  const node = path.node;
140
437
 
141
438
  // File system operations
142
- if (t.isMemberExpression(node.callee) &&
143
- t.isIdentifier(node.callee.object, { name: "fs" })) {
439
+ if (t.isMemberExpression(node.callee)) {
440
+ const obj = node.callee.object;
144
441
  const prop = node.callee.property;
145
442
 
146
- if (t.isIdentifier(prop) &&
147
- ["readFile", "writeFile", "readFileSync", "writeFileSync", "unlink"].includes(prop.name)) {
443
+ // Check for fs.* or fs/promises methods
444
+ const isFsCall = t.isIdentifier(obj, { name: "fs" }) ||
445
+ t.isIdentifier(obj, { name: "fsp" }) ||
446
+ t.isIdentifier(obj, { name: "fsPromises" });
447
+
448
+ if (isFsCall && t.isIdentifier(prop) &&
449
+ ["readFile", "writeFile", "readFileSync", "writeFileSync", "unlink",
450
+ "unlinkSync", "access", "accessSync", "stat", "statSync",
451
+ "readdir", "readdirSync", "mkdir", "mkdirSync"].includes(prop.name)) {
452
+
453
+ const lineNum = node.loc.start.line;
454
+ const lineContent = lines[lineNum - 1] || "";
455
+ const surroundingLines = getSurroundingLines(lines, lineNum - 1, 5);
456
+
457
+ // Skip if path is validated/sanitized
458
+ if (isPathValidated(lineContent, surroundingLines)) {
459
+ return;
460
+ }
148
461
 
149
462
  // Check if path contains user input or "../"
150
463
  for (const arg of node.arguments) {
464
+ let hasTraversal = false;
465
+ let hasUserInput = false;
466
+
151
467
  if (t.isTemplateLiteral(arg)) {
152
468
  const template = code.substring(arg.start, arg.end);
153
- if (template.includes("../") || template.includes("..\\")) {
154
- const line = node.loc.start.line;
155
- findings.push({
156
- type: "path_traversal",
157
- severity: "BLOCK",
158
- category: "Security",
159
- file: filePath,
160
- line,
161
- column: node.loc.start.column,
162
- title: "Potential path traversal vulnerability",
163
- message: "File operation with path containing '..' - validate and sanitize paths",
164
- codeSnippet: lines[line - 1]?.trim(),
165
- confidence: "high",
166
- });
167
- break;
168
- }
469
+ hasTraversal = template.includes("../") || template.includes("..\\");
470
+ hasUserInput = arg.expressions && arg.expressions.length > 0;
471
+ } else if (t.isBinaryExpression(arg) && arg.operator === "+") {
472
+ // String concatenation with user input
473
+ hasUserInput = true;
474
+ } else if (t.isIdentifier(arg)) {
475
+ // Variable path - check if it looks like user input
476
+ const varName = arg.name.toLowerCase();
477
+ hasUserInput = /path|file|dir|folder|name|input|param|query|body|req/i.test(varName);
478
+ }
479
+
480
+ if (hasTraversal || hasUserInput) {
481
+ findings.push({
482
+ type: "path_traversal",
483
+ severity: hasTraversal ? "BLOCK" : "WARN",
484
+ category: "Security",
485
+ file: filePath,
486
+ line: lineNum,
487
+ column: node.loc.start.column,
488
+ title: "Potential path traversal vulnerability",
489
+ message: hasTraversal
490
+ ? "File operation with path containing '..' - validate and sanitize paths"
491
+ : "File operation with potentially user-controlled path - validate before use",
492
+ codeSnippet: lineContent.trim(),
493
+ confidence: hasTraversal ? "high" : "med",
494
+ fixHint: "Use path.resolve() with a base directory and validate the result starts with the expected prefix",
495
+ });
496
+ break;
169
497
  }
170
498
  }
171
499
  }