@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.
- package/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- 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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|