@vibecheckai/cli 3.2.2 → 3.2.4

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/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  6. package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
  7. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
  8. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
  9. package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
  10. package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
  11. package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
  12. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
  14. package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
  15. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  16. package/bin/runners/lib/analyzers.js +606 -325
  17. package/bin/runners/lib/auth-truth.js +193 -193
  18. package/bin/runners/lib/backup.js +62 -62
  19. package/bin/runners/lib/billing.js +107 -107
  20. package/bin/runners/lib/claims.js +118 -118
  21. package/bin/runners/lib/cli-ui.js +540 -540
  22. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  23. package/bin/runners/lib/contracts/env-contract.js +181 -181
  24. package/bin/runners/lib/contracts/external-contract.js +206 -206
  25. package/bin/runners/lib/contracts/guard.js +168 -168
  26. package/bin/runners/lib/contracts/index.js +89 -89
  27. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  28. package/bin/runners/lib/contracts/route-contract.js +199 -199
  29. package/bin/runners/lib/contracts.js +804 -804
  30. package/bin/runners/lib/detect.js +89 -89
  31. package/bin/runners/lib/doctor/autofix.js +254 -254
  32. package/bin/runners/lib/doctor/index.js +37 -37
  33. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  34. package/bin/runners/lib/doctor/modules/index.js +46 -46
  35. package/bin/runners/lib/doctor/modules/network.js +250 -250
  36. package/bin/runners/lib/doctor/modules/project.js +312 -312
  37. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  38. package/bin/runners/lib/doctor/modules/security.js +348 -348
  39. package/bin/runners/lib/doctor/modules/system.js +213 -213
  40. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  41. package/bin/runners/lib/doctor/reporter.js +262 -262
  42. package/bin/runners/lib/doctor/service.js +262 -262
  43. package/bin/runners/lib/doctor/types.js +113 -113
  44. package/bin/runners/lib/doctor/ui.js +263 -263
  45. package/bin/runners/lib/doctor-v2.js +608 -608
  46. package/bin/runners/lib/drift.js +425 -425
  47. package/bin/runners/lib/enforcement.js +72 -72
  48. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  49. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  50. package/bin/runners/lib/engines/ast-cache.js +99 -0
  51. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  52. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  53. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  54. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  55. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  56. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  57. package/bin/runners/lib/engines/file-filter.js +131 -0
  58. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  59. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  60. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  61. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  62. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  63. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  64. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  65. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  66. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  67. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  68. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  69. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  70. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  71. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  72. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  73. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  74. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  75. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  76. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  77. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  78. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  79. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  80. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  81. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  82. package/bin/runners/lib/enterprise-detect.js +603 -603
  83. package/bin/runners/lib/enterprise-init.js +942 -942
  84. package/bin/runners/lib/env-resolver.js +417 -417
  85. package/bin/runners/lib/env-template.js +66 -66
  86. package/bin/runners/lib/env.js +189 -189
  87. package/bin/runners/lib/extractors/client-calls.js +990 -990
  88. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  89. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  90. package/bin/runners/lib/extractors/index.js +363 -363
  91. package/bin/runners/lib/extractors/next-routes.js +524 -524
  92. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  93. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  94. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  95. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  96. package/bin/runners/lib/findings-schema.js +281 -281
  97. package/bin/runners/lib/firewall-prompt.js +50 -50
  98. package/bin/runners/lib/global-flags.js +213 -213
  99. package/bin/runners/lib/graph/graph-builder.js +265 -265
  100. package/bin/runners/lib/graph/html-renderer.js +413 -413
  101. package/bin/runners/lib/graph/index.js +32 -32
  102. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  103. package/bin/runners/lib/graph/static-extractor.js +518 -518
  104. package/bin/runners/lib/html-report.js +650 -650
  105. package/bin/runners/lib/interactive-menu.js +1496 -1496
  106. package/bin/runners/lib/llm.js +75 -75
  107. package/bin/runners/lib/meter.js +61 -61
  108. package/bin/runners/lib/missions/evidence.js +126 -126
  109. package/bin/runners/lib/patch.js +40 -40
  110. package/bin/runners/lib/permissions/auth-model.js +213 -213
  111. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  112. package/bin/runners/lib/permissions/index.js +45 -45
  113. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  114. package/bin/runners/lib/pkgjson.js +28 -28
  115. package/bin/runners/lib/policy.js +295 -295
  116. package/bin/runners/lib/preflight.js +142 -142
  117. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  118. package/bin/runners/lib/reality/index.js +318 -318
  119. package/bin/runners/lib/reality/request-hashing.js +416 -416
  120. package/bin/runners/lib/reality/request-mapper.js +453 -453
  121. package/bin/runners/lib/reality/safety-rails.js +463 -463
  122. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  123. package/bin/runners/lib/reality/toast-detector.js +393 -393
  124. package/bin/runners/lib/reality-findings.js +84 -84
  125. package/bin/runners/lib/receipts.js +179 -179
  126. package/bin/runners/lib/redact.js +29 -29
  127. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  128. package/bin/runners/lib/replay/index.js +263 -263
  129. package/bin/runners/lib/replay/player.js +348 -348
  130. package/bin/runners/lib/replay/recorder.js +331 -331
  131. package/bin/runners/lib/report-output.js +187 -187
  132. package/bin/runners/lib/report.js +135 -135
  133. package/bin/runners/lib/route-detection.js +1140 -1140
  134. package/bin/runners/lib/sandbox/index.js +59 -59
  135. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  136. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  137. package/bin/runners/lib/sandbox/worktree.js +174 -174
  138. package/bin/runners/lib/scan-output.js +525 -190
  139. package/bin/runners/lib/schema-validator.js +350 -350
  140. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  141. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  142. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  143. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  144. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  145. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  146. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  147. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  148. package/bin/runners/lib/schemas/validator.js +438 -438
  149. package/bin/runners/lib/score-history.js +282 -282
  150. package/bin/runners/lib/share-pack.js +239 -239
  151. package/bin/runners/lib/snippets.js +67 -67
  152. package/bin/runners/lib/status-output.js +253 -253
  153. package/bin/runners/lib/terminal-ui.js +351 -271
  154. package/bin/runners/lib/upsell.js +510 -510
  155. package/bin/runners/lib/usage.js +153 -153
  156. package/bin/runners/lib/validate-patch.js +156 -156
  157. package/bin/runners/lib/verdict-engine.js +628 -628
  158. package/bin/runners/reality/engine.js +917 -917
  159. package/bin/runners/reality/flows.js +122 -122
  160. package/bin/runners/reality/report.js +378 -378
  161. package/bin/runners/reality/session.js +193 -193
  162. package/bin/runners/runGuard.js +168 -168
  163. package/bin/runners/runProof.zip +0 -0
  164. package/bin/runners/runProve.js +8 -0
  165. package/bin/runners/runReality.js +14 -0
  166. package/bin/runners/runScan.js +17 -1
  167. package/bin/runners/runTruth.js +15 -3
  168. package/mcp-server/tier-auth.js +4 -4
  169. package/mcp-server/tools/index.js +72 -72
  170. package/package.json +1 -1
@@ -0,0 +1,474 @@
1
+ /**
2
+ * AI-Powered False Positive Analyzer
3
+ *
4
+ * Uses AI to analyze code context and determine if a violation is a false positive.
5
+ * Helps reduce false positives by understanding code intent and patterns.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+
13
+ // Cache for AI responses to avoid repeated calls
14
+ const aiCache = new Map();
15
+ const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
16
+
17
+ /**
18
+ * Analyze if a violation is likely a false positive using AI
19
+ * @param {object} params
20
+ * @param {object} params.violation - Violation object
21
+ * @param {object} params.claim - Original claim
22
+ * @param {string} params.filePath - File path where violation occurred
23
+ * @param {string} params.projectRoot - Project root directory
24
+ * @param {object} params.policy - Policy configuration
25
+ * @returns {Promise<object>} Analysis result with isFalsePositive boolean and confidence
26
+ */
27
+ async function analyzeFalsePositive({ violation, claim, filePath, projectRoot, policy }) {
28
+ const ruleConfig = policy.rules?.ai_false_positive_detection;
29
+
30
+ // Check if AI analysis is enabled
31
+ if (!ruleConfig || !ruleConfig.enabled) {
32
+ return { isFalsePositive: false, confidence: 0, reason: "AI analysis disabled" };
33
+ }
34
+
35
+ // Check cache first
36
+ const cacheKey = `${violation.rule}|${claim.value}|${filePath}`;
37
+ const cached = aiCache.get(cacheKey);
38
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
39
+ return cached.result;
40
+ }
41
+
42
+ try {
43
+ // Read the file to get context
44
+ const fullPath = path.join(projectRoot, filePath);
45
+ if (!fs.existsSync(fullPath)) {
46
+ return { isFalsePositive: false, confidence: 0, reason: "File not found" };
47
+ }
48
+
49
+ const fileContent = fs.readFileSync(fullPath, "utf8");
50
+ const lines = fileContent.split("\n");
51
+
52
+ // Extract context around the violation (10 lines before and after)
53
+ const pointer = claim.pointer || violation.claim?.pointer;
54
+ let startLine = 1;
55
+ let endLine = lines.length;
56
+
57
+ if (pointer) {
58
+ const match = pointer.match(/:(\d+)-(\d+)/);
59
+ if (match) {
60
+ const lineNum = parseInt(match[1], 10);
61
+ startLine = Math.max(1, lineNum - 10);
62
+ endLine = Math.min(lines.length, lineNum + 10);
63
+ }
64
+ }
65
+
66
+ const context = lines.slice(startLine - 1, endLine).join("\n");
67
+ const lineNumbers = Array.from({ length: endLine - startLine + 1 }, (_, i) => startLine + i);
68
+ const numberedContext = lines.slice(startLine - 1, endLine)
69
+ .map((line, i) => `${lineNumbers[i]}: ${line}`)
70
+ .join("\n");
71
+
72
+ // Build AI prompt
73
+ const prompt = buildAnalysisPrompt({
74
+ violation,
75
+ claim,
76
+ filePath,
77
+ context: numberedContext,
78
+ ruleType: violation.rule
79
+ });
80
+
81
+ // Try LLM first if enabled, otherwise use heuristics
82
+ let analysis = null;
83
+ if (ruleConfig.useLLM) {
84
+ analysis = await analyzeWithLLM(prompt, ruleConfig);
85
+ }
86
+
87
+ // Fall back to heuristics if LLM not available or failed
88
+ if (!analysis) {
89
+ analysis = await analyzeWithHeuristics({
90
+ violation,
91
+ claim,
92
+ filePath,
93
+ context,
94
+ numberedContext
95
+ });
96
+ }
97
+
98
+ // Cache result
99
+ aiCache.set(cacheKey, {
100
+ timestamp: Date.now(),
101
+ result: analysis
102
+ });
103
+
104
+ return analysis;
105
+ } catch (error) {
106
+ // If AI analysis fails, default to not a false positive
107
+ return {
108
+ isFalsePositive: false,
109
+ confidence: 0,
110
+ reason: `AI analysis failed: ${error.message}`
111
+ };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Build prompt for AI analysis
117
+ */
118
+ function buildAnalysisPrompt({ violation, claim, filePath, context, ruleType }) {
119
+ return `Analyze this code violation to determine if it's a false positive.
120
+
121
+ Rule: ${ruleType}
122
+ Claim Type: ${claim.type}
123
+ Claim Value: ${claim.value}
124
+ File: ${filePath}
125
+
126
+ Code Context:
127
+ \`\`\`
128
+ ${context}
129
+ \`\`\`
130
+
131
+ Question: Is this a false positive? Consider:
132
+ 1. Is this an import path being mistaken for a route? (e.g., "from './api/content'")
133
+ 2. Is this an external API call that shouldn't be validated? (e.g., "/content/blog" going to backend)
134
+ 3. Is this test/fixture code that should be ignored?
135
+ 4. Is this a legitimate pattern that the rule doesn't understand?
136
+
137
+ Respond with JSON:
138
+ {
139
+ "isFalsePositive": boolean,
140
+ "confidence": 0.0-1.0,
141
+ "reason": "explanation",
142
+ "suggestedFix": "what to do if false positive"
143
+ }`;
144
+ }
145
+
146
+ /**
147
+ * Analyze using heuristics (can be replaced with actual LLM call)
148
+ * This provides immediate value while LLM integration can be added later
149
+ */
150
+ async function analyzeWithHeuristics({ violation, claim, filePath, context, numberedContext }) {
151
+ const claimValue = String(claim.value || "").trim();
152
+ const filePathLower = filePath.toLowerCase();
153
+ const contextLower = context.toLowerCase();
154
+
155
+ // Heuristic 1: Import paths
156
+ if (claimValue.includes("./") || claimValue.includes("../")) {
157
+ // Check if it's in an import/require statement
158
+ const importPatterns = [
159
+ /from\s+['"]\./,
160
+ /require\(['"]\./,
161
+ /import\s+.*from\s+['"]\./
162
+ ];
163
+
164
+ for (const pattern of importPatterns) {
165
+ if (pattern.test(context)) {
166
+ return {
167
+ isFalsePositive: true,
168
+ confidence: 0.95,
169
+ reason: "This appears to be an import path, not a route",
170
+ suggestedFix: "Update route detection to exclude import statements"
171
+ };
172
+ }
173
+ }
174
+ }
175
+
176
+ // Heuristic 2: External API calls (not starting with /api/)
177
+ if (violation.rule === "ghost_route" && claimValue.startsWith("/")) {
178
+ if (!claimValue.startsWith("/api/")) {
179
+ return {
180
+ isFalsePositive: true,
181
+ confidence: 0.9,
182
+ reason: "This is an external API call to the backend, not a Next.js route",
183
+ suggestedFix: "Only validate Next.js API routes (starting with /api/)"
184
+ };
185
+ }
186
+ }
187
+
188
+ // Heuristic 3: Test files
189
+ if (filePathLower.includes("test") ||
190
+ filePathLower.includes("spec") ||
191
+ filePathLower.includes("fixture") ||
192
+ filePathLower.includes("mock")) {
193
+ return {
194
+ isFalsePositive: true,
195
+ confidence: 0.85,
196
+ reason: "This is in a test/fixture file",
197
+ suggestedFix: "Add test files to .vibecheckignore"
198
+ };
199
+ }
200
+
201
+ // Heuristic 4: Comments or strings
202
+ if (claimValue.includes("//") ||
203
+ claimValue.includes("/*") ||
204
+ contextLower.includes(`"${claimValue}"`) ||
205
+ contextLower.includes(`'${claimValue}'`)) {
206
+ // Check if it's in a comment
207
+ const lines = numberedContext.split("\n");
208
+ for (const line of lines) {
209
+ if (line.includes(claimValue)) {
210
+ const beforeValue = line.substring(0, line.indexOf(claimValue));
211
+ if (beforeValue.includes("//") || beforeValue.includes("/*")) {
212
+ return {
213
+ isFalsePositive: true,
214
+ confidence: 0.8,
215
+ reason: "This appears to be in a comment",
216
+ suggestedFix: "Skip route detection in comments"
217
+ };
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ // Heuristic 5: Environment variable names (for ghost_env)
224
+ if (violation.rule === "ghost_env") {
225
+ // Check if it's a standard/system env var that doesn't need declaration
226
+ const systemEnvVars = [
227
+ "NODE_ENV", "PATH", "HOME", "USER", "SHELL", "TMPDIR",
228
+ "CI", "GITHUB_ACTIONS", "GITLAB_CI", "CIRCLECI", "BUILDKITE",
229
+ "COLORTERM", "TERM", "LANG", "LC_ALL"
230
+ ];
231
+
232
+ if (systemEnvVars.includes(claimValue)) {
233
+ return {
234
+ isFalsePositive: true,
235
+ confidence: 0.9,
236
+ reason: "This is a standard system environment variable",
237
+ suggestedFix: "Add system env vars to allowlist"
238
+ };
239
+ }
240
+ }
241
+
242
+ // Heuristic 6: Success messages in UI components (for fake_success_ui)
243
+ if (violation.rule === "fake_success_ui") {
244
+ // Check if it's just displaying a version number or static text
245
+ if (claimValue.match(/v?\d+\.\d+\.\d+/) || // Version numbers
246
+ claimValue.length < 10) { // Very short messages
247
+ return {
248
+ isFalsePositive: true,
249
+ confidence: 0.7,
250
+ reason: "This appears to be a version number or static text, not a success message",
251
+ suggestedFix: "Improve success message detection to exclude version numbers"
252
+ };
253
+ }
254
+ }
255
+
256
+ // Default: not a false positive
257
+ return {
258
+ isFalsePositive: false,
259
+ confidence: 0.5,
260
+ reason: "No clear indicators of false positive",
261
+ suggestedFix: null
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Call actual LLM for analysis (when AI is available)
267
+ * Uses OpenAI or Anthropic API directly
268
+ */
269
+ async function analyzeWithLLM(prompt, config = {}) {
270
+ const { useLLM = false, provider = "openai" } = config;
271
+
272
+ if (!useLLM) {
273
+ return null;
274
+ }
275
+
276
+ const hasOpenAI = !!process.env.OPENAI_API_KEY;
277
+ const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
278
+
279
+ if (!hasOpenAI && !hasAnthropic) {
280
+ return null;
281
+ }
282
+
283
+ try {
284
+ // Try OpenAI first (default)
285
+ if ((provider === "openai" || !hasAnthropic) && hasOpenAI) {
286
+ return await callOpenAI(prompt);
287
+ }
288
+
289
+ // Try Anthropic
290
+ if ((provider === "anthropic" || !hasOpenAI) && hasAnthropic) {
291
+ return await callAnthropic(prompt);
292
+ }
293
+ } catch (error) {
294
+ // If LLM call fails, fall back to heuristics
295
+ console.warn(`[AI] LLM analysis failed: ${error.message}`);
296
+ return null;
297
+ }
298
+
299
+ return null;
300
+ }
301
+
302
+ /**
303
+ * Call OpenAI API
304
+ */
305
+ async function callOpenAI(prompt) {
306
+ const https = require("https");
307
+ const http = require("http");
308
+
309
+ const apiKey = process.env.OPENAI_API_KEY;
310
+ if (!apiKey) return null;
311
+
312
+ const requestBody = JSON.stringify({
313
+ model: "gpt-4o-mini", // Fast and cheap model
314
+ messages: [
315
+ {
316
+ role: "system",
317
+ content: "You are a code analysis assistant. Analyze if a code violation is a false positive. Respond with JSON only."
318
+ },
319
+ {
320
+ role: "user",
321
+ content: prompt
322
+ }
323
+ ],
324
+ response_format: { type: "json_object" },
325
+ temperature: 0.3,
326
+ max_tokens: 500
327
+ });
328
+
329
+ return new Promise((resolve, reject) => {
330
+ const options = {
331
+ hostname: "api.openai.com",
332
+ path: "/v1/chat/completions",
333
+ method: "POST",
334
+ headers: {
335
+ "Content-Type": "application/json",
336
+ "Authorization": `Bearer ${apiKey}`,
337
+ "Content-Length": Buffer.byteLength(requestBody)
338
+ },
339
+ timeout: 5000
340
+ };
341
+
342
+ const req = https.request(options, (res) => {
343
+ let data = "";
344
+
345
+ res.on("data", (chunk) => {
346
+ data += chunk;
347
+ });
348
+
349
+ res.on("end", () => {
350
+ try {
351
+ const response = JSON.parse(data);
352
+ if (response.error) {
353
+ reject(new Error(response.error.message));
354
+ return;
355
+ }
356
+
357
+ const content = response.choices?.[0]?.message?.content;
358
+ if (!content) {
359
+ resolve(null);
360
+ return;
361
+ }
362
+
363
+ const result = JSON.parse(content);
364
+ resolve(result);
365
+ } catch (error) {
366
+ reject(error);
367
+ }
368
+ });
369
+ });
370
+
371
+ req.on("error", reject);
372
+ req.on("timeout", () => {
373
+ req.destroy();
374
+ reject(new Error("Request timeout"));
375
+ });
376
+
377
+ req.write(requestBody);
378
+ req.end();
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Call Anthropic API
384
+ */
385
+ async function callAnthropic(prompt) {
386
+ const https = require("https");
387
+
388
+ const apiKey = process.env.ANTHROPIC_API_KEY;
389
+ if (!apiKey) return null;
390
+
391
+ const requestBody = JSON.stringify({
392
+ model: "claude-3-haiku-20240307", // Fast and cheap model
393
+ max_tokens: 500,
394
+ messages: [
395
+ {
396
+ role: "user",
397
+ content: prompt
398
+ }
399
+ ],
400
+ system: "You are a code analysis assistant. Analyze if a code violation is a false positive. Respond with JSON only."
401
+ });
402
+
403
+ return new Promise((resolve, reject) => {
404
+ const options = {
405
+ hostname: "api.anthropic.com",
406
+ path: "/v1/messages",
407
+ method: "POST",
408
+ headers: {
409
+ "Content-Type": "application/json",
410
+ "x-api-key": apiKey,
411
+ "anthropic-version": "2023-06-01",
412
+ "Content-Length": Buffer.byteLength(requestBody)
413
+ },
414
+ timeout: 5000
415
+ };
416
+
417
+ const req = https.request(options, (res) => {
418
+ let data = "";
419
+
420
+ res.on("data", (chunk) => {
421
+ data += chunk;
422
+ });
423
+
424
+ res.on("end", () => {
425
+ try {
426
+ const response = JSON.parse(data);
427
+ if (response.error) {
428
+ reject(new Error(response.error.message));
429
+ return;
430
+ }
431
+
432
+ const content = response.content?.[0]?.text;
433
+ if (!content) {
434
+ resolve(null);
435
+ return;
436
+ }
437
+
438
+ // Extract JSON from response (might be wrapped in markdown)
439
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
440
+ if (!jsonMatch) {
441
+ resolve(null);
442
+ return;
443
+ }
444
+
445
+ const result = JSON.parse(jsonMatch[0]);
446
+ resolve(result);
447
+ } catch (error) {
448
+ reject(error);
449
+ }
450
+ });
451
+ });
452
+
453
+ req.on("error", reject);
454
+ req.on("timeout", () => {
455
+ req.destroy();
456
+ reject(new Error("Request timeout"));
457
+ });
458
+
459
+ req.write(requestBody);
460
+ req.end();
461
+ });
462
+ }
463
+
464
+ /**
465
+ * Clear AI cache (useful for testing)
466
+ */
467
+ function clearCache() {
468
+ aiCache.clear();
469
+ }
470
+
471
+ module.exports = {
472
+ analyzeFalsePositive,
473
+ clearCache
474
+ };
@@ -12,6 +12,7 @@ const traverse = require("@babel/traverse").default;
12
12
  const t = require("@babel/types");
13
13
  const { CLAIM_TYPES, CRITICALITY } = require("./claim-types");
14
14
  const { ROUTE_LIKE, AUTH_HINTS, SUCCESS_UI } = require("./patterns");
15
+ const { shouldIgnore } = require("../utils/ignore-checker");
15
16
 
16
17
  function parse(code) {
17
18
  return parser.parse(code, {
@@ -36,11 +37,36 @@ function pushClaim(out, claim) {
36
37
 
37
38
  function extractStringLiterals(code) {
38
39
  // Cheap scan for route-ish strings even if AST misses template parts.
40
+ // Exclude import paths (from './api/...' or from '../api/...')
39
41
  const hits = [];
40
- for (const rx of ROUTE_LIKE) {
41
- const m = code.match(rx);
42
- if (m) hits.push(...m);
42
+ const lines = code.split('\n');
43
+
44
+ for (let i = 0; i < lines.length; i++) {
45
+ const line = lines[i];
46
+
47
+ // Skip import/require statements to avoid false positives
48
+ if (/^\s*(import|export|require)\s+.*from\s+['"]/.test(line) ||
49
+ /^\s*const\s+\w+\s*=\s*require\(/.test(line)) {
50
+ continue;
51
+ }
52
+
53
+ // Check for route-like patterns in this line
54
+ for (const rx of ROUTE_LIKE) {
55
+ const matches = line.match(rx);
56
+ if (matches) {
57
+ // Filter out matches that are part of import paths
58
+ for (const match of matches) {
59
+ // Exclude if it's part of an import path (has './' or '../' before it)
60
+ const matchIndex = line.indexOf(match);
61
+ const beforeMatch = line.substring(Math.max(0, matchIndex - 10), matchIndex);
62
+ if (!/['"]\.\.?\/.*$/.test(beforeMatch)) {
63
+ hits.push(match);
64
+ }
65
+ }
66
+ }
67
+ }
43
68
  }
69
+
44
70
  return [...new Set(hits)];
45
71
  }
46
72
 
@@ -56,21 +82,19 @@ function classifyFileDomain(fileRel) {
56
82
 
57
83
  function extractClaimsFromFile({ repoRoot, fileAbs }) {
58
84
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
85
+
86
+ // Skip ignored files (test files, fixtures, etc.)
87
+ if (shouldIgnore(repoRoot, fileRel)) {
88
+ return { fileRel, domain: "ignored", claims: [], _dedupe: new Set() };
89
+ }
90
+
59
91
  const code = fs.readFileSync(fileAbs, "utf8");
60
92
  const ast = parse(code);
61
93
 
62
94
  const out = { fileRel, domain: classifyFileDomain(fileRel), claims: [], _dedupe: new Set() };
63
95
 
64
- // 1) quick string route-ish scan
65
- for (const r of extractStringLiterals(code)) {
66
- pushClaim(out, {
67
- type: CLAIM_TYPES.ROUTE,
68
- value: r,
69
- criticality: CRITICALITY.HARD,
70
- pointer: `${fileRel}:1-1`,
71
- reason: "route-like string literal"
72
- });
73
- }
96
+ // 1) quick string route-ish scan (skip - AST-based detection is more accurate)
97
+ // Removed to avoid false positives from import paths
74
98
 
75
99
  // 2) AST-based env extraction
76
100
  traverse(ast, {
@@ -116,17 +140,58 @@ function extractClaimsFromFile({ repoRoot, fileAbs }) {
116
140
  CallExpression(p) {
117
141
  const n = p.node;
118
142
 
119
- // fetch("/api/...")
143
+ // fetch("/api/..." or fetch(`${url}/api/...`))
120
144
  if (t.isIdentifier(n.callee, { name: "fetch" }) && n.arguments[0]) {
121
145
  const a0 = n.arguments[0];
122
146
  if (t.isStringLiteral(a0)) {
123
- pushClaim(out, {
124
- type: CLAIM_TYPES.HTTP_CALL,
125
- value: a0.value,
126
- criticality: CRITICALITY.HARD,
127
- pointer: locPtr(fileRel, n),
128
- reason: "fetch call"
129
- });
147
+ const routeValue = a0.value;
148
+ // Skip if it's an external URL (starts with http:// or https://)
149
+ // Only track Next.js API routes (starting with /api/)
150
+ // External API calls (like /content/blog) are handled by backend, not validated here
151
+ if (!routeValue.startsWith('http://') && !routeValue.startsWith('https://') && routeValue.startsWith('/api/')) {
152
+ pushClaim(out, {
153
+ type: CLAIM_TYPES.HTTP_CALL,
154
+ value: routeValue,
155
+ criticality: CRITICALITY.HARD,
156
+ pointer: locPtr(fileRel, n),
157
+ reason: "fetch call"
158
+ });
159
+ }
160
+ } else if (t.isTemplateLiteral(a0)) {
161
+ // Template literal like fetch(`${apiUrl}/api/...`)
162
+ // Check if it contains /api/ pattern - this indicates an HTTP call
163
+ const templateParts = a0.quasis.map(q => q.value.cooked || q.value.raw).join('');
164
+ // Check if it uses apiUrl or similar variable (backend API call)
165
+ const hasApiUrlVar = a0.expressions.some(expr =>
166
+ t.isIdentifier(expr) && (
167
+ expr.name.includes('apiUrl') ||
168
+ expr.name.includes('API_URL') ||
169
+ expr.name.includes('api')
170
+ )
171
+ );
172
+
173
+ // Extract the actual route path if possible (e.g., /api/v1/dashboard/summary)
174
+ const apiMatch = templateParts.match(/\/api\/[a-zA-Z0-9_\/\-]+/);
175
+ if (apiMatch) {
176
+ pushClaim(out, {
177
+ type: CLAIM_TYPES.HTTP_CALL,
178
+ value: apiMatch[0], // Use the actual matched route
179
+ criticality: CRITICALITY.HARD,
180
+ pointer: locPtr(fileRel, n),
181
+ reason: hasApiUrlVar ? "fetch call (backend API - template literal)" : "fetch call (template literal)",
182
+ isBackendApi: hasApiUrlVar // Flag for evidence resolver
183
+ });
184
+ } else if (templateParts.includes('/api/')) {
185
+ // Fallback: if we can't extract exact route, still mark as HTTP call
186
+ pushClaim(out, {
187
+ type: CLAIM_TYPES.HTTP_CALL,
188
+ value: '/api/*', // Pattern for template literals
189
+ criticality: CRITICALITY.HARD,
190
+ pointer: locPtr(fileRel, n),
191
+ reason: hasApiUrlVar ? "fetch call (backend API - pattern)" : "fetch call (template literal - pattern)",
192
+ isBackendApi: hasApiUrlVar
193
+ });
194
+ }
130
195
  }
131
196
  }
132
197
 
@@ -135,13 +200,37 @@ function extractClaimsFromFile({ repoRoot, fileAbs }) {
135
200
  const method = t.isIdentifier(n.callee.property) ? n.callee.property.name : "call";
136
201
  const a0 = n.arguments[0];
137
202
  if (a0 && t.isStringLiteral(a0)) {
138
- pushClaim(out, {
139
- type: CLAIM_TYPES.HTTP_CALL,
140
- value: `${method.toUpperCase()} ${a0.value}`,
141
- criticality: CRITICALITY.HARD,
142
- pointer: locPtr(fileRel, n),
143
- reason: "axios call"
144
- });
203
+ const routeValue = a0.value;
204
+ // Only track Next.js API routes (starting with /api/)
205
+ // External API calls (like /content/blog) are handled by backend, not validated here
206
+ if (routeValue.startsWith('/api/')) {
207
+ pushClaim(out, {
208
+ type: CLAIM_TYPES.HTTP_CALL,
209
+ value: `${method.toUpperCase()} ${routeValue}`,
210
+ criticality: CRITICALITY.HARD,
211
+ pointer: locPtr(fileRel, n),
212
+ reason: "axios call"
213
+ });
214
+ }
215
+ }
216
+ }
217
+
218
+ // apiRequest("/api/...") - check if it's the apiRequest helper
219
+ if (t.isIdentifier(n.callee, { name: "apiRequest" }) && n.arguments[0]) {
220
+ const a0 = n.arguments[0];
221
+ if (t.isStringLiteral(a0)) {
222
+ const routeValue = a0.value;
223
+ // Only track Next.js API routes (starting with /api/)
224
+ // External API calls (like /content/blog) go to backend API, not Next.js routes
225
+ if (routeValue.startsWith('/api/')) {
226
+ pushClaim(out, {
227
+ type: CLAIM_TYPES.HTTP_CALL,
228
+ value: routeValue,
229
+ criticality: CRITICALITY.HARD,
230
+ pointer: locPtr(fileRel, n),
231
+ reason: "apiRequest call"
232
+ });
233
+ }
145
234
  }
146
235
  }
147
236