@vibecheckai/cli 3.4.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.
- package/bin/registry.js +243 -152
- package/bin/runners/cli-utils.js +2 -33
- package/bin/runners/context/generators/cursor.js +49 -2
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
- package/bin/runners/lib/analyzers.js +544 -19
- package/bin/runners/lib/audit-logger.js +532 -0
- package/bin/runners/lib/authority/authorities/architecture.js +364 -0
- package/bin/runners/lib/authority/authorities/compliance.js +341 -0
- package/bin/runners/lib/authority/authorities/human.js +343 -0
- package/bin/runners/lib/authority/authorities/quality.js +420 -0
- package/bin/runners/lib/authority/authorities/security.js +228 -0
- package/bin/runners/lib/authority/index.js +293 -0
- package/bin/runners/lib/authority-badge.js +425 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
- package/bin/runners/lib/cli-charts.js +368 -0
- package/bin/runners/lib/cli-config-display.js +405 -0
- package/bin/runners/lib/cli-demo.js +275 -0
- package/bin/runners/lib/cli-errors.js +438 -0
- package/bin/runners/lib/cli-help-formatter.js +439 -0
- package/bin/runners/lib/cli-interactive-menu.js +509 -0
- package/bin/runners/lib/cli-prompts.js +441 -0
- package/bin/runners/lib/cli-scan-cards.js +362 -0
- package/bin/runners/lib/compliance-reporter.js +710 -0
- package/bin/runners/lib/conductor/index.js +671 -0
- package/bin/runners/lib/easy/README.md +123 -0
- package/bin/runners/lib/easy/index.js +140 -0
- package/bin/runners/lib/easy/interactive-wizard.js +788 -0
- package/bin/runners/lib/easy/one-click-firewall.js +564 -0
- package/bin/runners/lib/easy/zero-config-reality.js +714 -0
- package/bin/runners/lib/engines/accessibility-engine.js +218 -18
- package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
- package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
- package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
- package/bin/runners/lib/engines/confidence-scoring.js +276 -0
- package/bin/runners/lib/engines/context-detection.js +264 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
- package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
- package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
- package/bin/runners/lib/engines/env-variables-engine.js +458 -0
- package/bin/runners/lib/engines/error-handling-engine.js +437 -0
- package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
- package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
- package/bin/runners/lib/engines/framework-detection.js +508 -0
- package/bin/runners/lib/engines/import-order-engine.js +429 -0
- package/bin/runners/lib/engines/mock-data-engine.js +53 -10
- package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
- package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
- package/bin/runners/lib/engines/orchestrator.js +334 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
- package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
- package/bin/runners/lib/engines/type-aware-engine.js +263 -39
- package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
- package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
- package/bin/runners/lib/enhanced-features/index.js +305 -0
- package/bin/runners/lib/enhanced-output.js +631 -0
- package/bin/runners/lib/enterprise.js +300 -0
- package/bin/runners/lib/entitlements-v2.js +103 -11
- package/bin/runners/lib/firewall/command-validator.js +351 -0
- package/bin/runners/lib/firewall/config.js +341 -0
- package/bin/runners/lib/firewall/content-validator.js +519 -0
- package/bin/runners/lib/firewall/index.js +101 -0
- package/bin/runners/lib/firewall/path-validator.js +256 -0
- package/bin/runners/lib/html-proof-report.js +350 -700
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
- package/bin/runners/lib/mcp-utils.js +425 -0
- package/bin/runners/lib/missions/plan.js +46 -6
- package/bin/runners/lib/missions/templates.js +232 -0
- package/bin/runners/lib/output/index.js +1022 -0
- package/bin/runners/lib/policy-engine.js +652 -0
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
- package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
- package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
- package/bin/runners/lib/polish/autofix/index.js +200 -0
- package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
- package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
- package/bin/runners/lib/polish/backend-checks.js +148 -0
- package/bin/runners/lib/polish/documentation-checks.js +111 -0
- package/bin/runners/lib/polish/frontend-checks.js +168 -0
- package/bin/runners/lib/polish/index.js +71 -0
- package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
- package/bin/runners/lib/polish/library-detection.js +175 -0
- package/bin/runners/lib/polish/performance-checks.js +100 -0
- package/bin/runners/lib/polish/security-checks.js +148 -0
- package/bin/runners/lib/polish/utils.js +203 -0
- package/bin/runners/lib/prompt-builder.js +540 -0
- package/bin/runners/lib/proof-certificate.js +634 -0
- package/bin/runners/lib/reality/accessibility-audit.js +946 -0
- package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
- package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
- package/bin/runners/lib/reality/performance-tracker.js +1077 -0
- package/bin/runners/lib/reality/scenario-generator.js +1404 -0
- package/bin/runners/lib/reality/visual-regression.js +852 -0
- package/bin/runners/lib/reality-profiler.js +717 -0
- package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
- package/bin/runners/lib/review/ai-code-review.js +832 -0
- package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
- package/bin/runners/lib/sbom-generator.js +641 -0
- package/bin/runners/lib/scan-output-enhanced.js +512 -0
- package/bin/runners/lib/scan-output.js +47 -0
- package/bin/runners/lib/security/owasp-scanner.js +939 -0
- package/bin/runners/lib/terminal-ui.js +113 -1
- package/bin/runners/lib/unified-cli-output.js +603 -430
- package/bin/runners/lib/validators/contract-validator.js +283 -0
- package/bin/runners/lib/validators/dead-export-detector.js +279 -0
- package/bin/runners/lib/validators/dep-audit.js +245 -0
- package/bin/runners/lib/validators/env-validator.js +319 -0
- package/bin/runners/lib/validators/index.js +120 -0
- package/bin/runners/lib/validators/license-checker.js +252 -0
- package/bin/runners/lib/validators/route-validator.js +290 -0
- package/bin/runners/runAIAgent.js +5 -10
- package/bin/runners/runAgent.js +3 -0
- package/bin/runners/runApprove.js +1233 -1200
- package/bin/runners/runAuth.js +22 -1
- package/bin/runners/runAuthority.js +528 -0
- package/bin/runners/runCheckpoint.js +4 -24
- package/bin/runners/runClassify.js +862 -859
- package/bin/runners/runConductor.js +772 -0
- package/bin/runners/runContainer.js +366 -0
- package/bin/runners/runContext.js +3 -0
- package/bin/runners/runDoctor.js +28 -41
- package/bin/runners/runEasy.js +410 -0
- package/bin/runners/runFirewall.js +3 -0
- package/bin/runners/runFirewallHook.js +3 -0
- package/bin/runners/runFix.js +76 -66
- package/bin/runners/runGuard.js +411 -18
- package/bin/runners/runIaC.js +372 -0
- package/bin/runners/runInit.js +10 -60
- package/bin/runners/runMcp.js +11 -12
- package/bin/runners/runPolish.js +240 -64
- package/bin/runners/runPromptFirewall.js +5 -12
- package/bin/runners/runProve.js +20 -55
- package/bin/runners/runReality.js +68 -59
- package/bin/runners/runReport.js +31 -5
- package/bin/runners/runRuntime.js +5 -8
- package/bin/runners/runScan.js +194 -1286
- package/bin/runners/runShip.js +695 -47
- package/bin/runners/runTruth.js +3 -0
- package/bin/runners/runValidate.js +7 -11
- package/bin/runners/runVibe.js +791 -0
- package/bin/runners/runWatch.js +14 -23
- package/bin/vibecheck.js +175 -56
- package/mcp-server/index.js +190 -14
- package/mcp-server/package.json +1 -1
- package/mcp-server/tools-v3.js +397 -64
- package/mcp-server/tools.js +495 -0
- package/package.json +1 -1
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
- package/mcp-server/index-v1.js +0 -698
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Patterns Engine
|
|
3
|
+
* Detects:
|
|
4
|
+
* - N+1 query patterns
|
|
5
|
+
* - Missing transaction boundaries
|
|
6
|
+
* - Unbounded queries (no LIMIT)
|
|
7
|
+
* - Raw SQL injection risks (beyond basic detection)
|
|
8
|
+
* - Missing indexes (suggested)
|
|
9
|
+
* - Connection pool issues
|
|
10
|
+
* - ORM anti-patterns
|
|
11
|
+
* - Missing error handling on DB operations
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { getAST } = require("./ast-cache");
|
|
15
|
+
const traverse = require("@babel/traverse").default;
|
|
16
|
+
const t = require("@babel/types");
|
|
17
|
+
const { shouldExcludeFile, isTestContext, hasIgnoreDirective } = require("./file-filter");
|
|
18
|
+
|
|
19
|
+
function snippetForLine(lines, line) {
|
|
20
|
+
return lines[line - 1] ? lines[line - 1].trim() : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ORM detection patterns
|
|
25
|
+
*/
|
|
26
|
+
const ORM_PATTERNS = {
|
|
27
|
+
prisma: {
|
|
28
|
+
import: /from\s+['"]@prisma\/client['"]/,
|
|
29
|
+
findMany: /\.findMany\s*\(/,
|
|
30
|
+
findFirst: /\.findFirst\s*\(/,
|
|
31
|
+
findUnique: /\.findUnique\s*\(/,
|
|
32
|
+
create: /\.create\s*\(/,
|
|
33
|
+
update: /\.update\s*\(/,
|
|
34
|
+
delete: /\.delete\s*\(/,
|
|
35
|
+
transaction: /\$transaction/,
|
|
36
|
+
rawQuery: /\$queryRaw|\$executeRaw/,
|
|
37
|
+
},
|
|
38
|
+
drizzle: {
|
|
39
|
+
import: /from\s+['"]drizzle-orm['"]/,
|
|
40
|
+
select: /\.select\s*\(/,
|
|
41
|
+
insert: /\.insert\s*\(/,
|
|
42
|
+
update: /\.update\s*\(/,
|
|
43
|
+
delete: /\.delete\s*\(/,
|
|
44
|
+
rawQuery: /sql`|sql\(/,
|
|
45
|
+
},
|
|
46
|
+
typeorm: {
|
|
47
|
+
import: /from\s+['"]typeorm['"]/,
|
|
48
|
+
find: /\.find\s*\(/,
|
|
49
|
+
findOne: /\.findOne\s*\(/,
|
|
50
|
+
save: /\.save\s*\(/,
|
|
51
|
+
remove: /\.remove\s*\(/,
|
|
52
|
+
transaction: /\.transaction\s*\(/,
|
|
53
|
+
rawQuery: /\.query\s*\(/,
|
|
54
|
+
},
|
|
55
|
+
sequelize: {
|
|
56
|
+
import: /from\s+['"]sequelize['"]/,
|
|
57
|
+
findAll: /\.findAll\s*\(/,
|
|
58
|
+
findOne: /\.findOne\s*\(/,
|
|
59
|
+
create: /\.create\s*\(/,
|
|
60
|
+
update: /\.update\s*\(/,
|
|
61
|
+
destroy: /\.destroy\s*\(/,
|
|
62
|
+
transaction: /\.transaction\s*\(/,
|
|
63
|
+
rawQuery: /\.query\s*\(/,
|
|
64
|
+
},
|
|
65
|
+
mongoose: {
|
|
66
|
+
import: /from\s+['"]mongoose['"]/,
|
|
67
|
+
find: /\.find\s*\(/,
|
|
68
|
+
findOne: /\.findOne\s*\(/,
|
|
69
|
+
findById: /\.findById\s*\(/,
|
|
70
|
+
save: /\.save\s*\(/,
|
|
71
|
+
aggregate: /\.aggregate\s*\(/,
|
|
72
|
+
},
|
|
73
|
+
knex: {
|
|
74
|
+
import: /from\s+['"]knex['"]/,
|
|
75
|
+
select: /\.select\s*\(/,
|
|
76
|
+
insert: /\.insert\s*\(/,
|
|
77
|
+
update: /\.update\s*\(/,
|
|
78
|
+
delete: /\.del\s*\(|\.delete\s*\(/,
|
|
79
|
+
transaction: /\.transaction\s*\(/,
|
|
80
|
+
rawQuery: /\.raw\s*\(/,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Detect which ORM is being used
|
|
86
|
+
*/
|
|
87
|
+
function detectORM(code) {
|
|
88
|
+
for (const [name, patterns] of Object.entries(ORM_PATTERNS)) {
|
|
89
|
+
if (patterns.import.test(code)) {
|
|
90
|
+
return name;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Analyze database patterns
|
|
98
|
+
*/
|
|
99
|
+
function analyzeDatabasePatterns(code, filePath) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
|
|
102
|
+
if (shouldExcludeFile(filePath)) return findings;
|
|
103
|
+
if (isTestContext(code, filePath)) return findings;
|
|
104
|
+
if (hasIgnoreDirective(code, "database-patterns")) return findings;
|
|
105
|
+
|
|
106
|
+
const ast = getAST(code, filePath);
|
|
107
|
+
if (!ast) return findings;
|
|
108
|
+
|
|
109
|
+
const lines = code.split("\n");
|
|
110
|
+
const orm = detectORM(code);
|
|
111
|
+
|
|
112
|
+
// If no ORM detected, skip most checks
|
|
113
|
+
if (!orm) return findings;
|
|
114
|
+
|
|
115
|
+
// Track queries for N+1 detection
|
|
116
|
+
const queriesInLoops = [];
|
|
117
|
+
let inLoop = false;
|
|
118
|
+
let loopDepth = 0;
|
|
119
|
+
|
|
120
|
+
// Track if we're in a transaction
|
|
121
|
+
let inTransaction = false;
|
|
122
|
+
|
|
123
|
+
traverse(ast, {
|
|
124
|
+
// Track loop entry
|
|
125
|
+
ForStatement: {
|
|
126
|
+
enter() { inLoop = true; loopDepth++; },
|
|
127
|
+
exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
|
|
128
|
+
},
|
|
129
|
+
ForInStatement: {
|
|
130
|
+
enter() { inLoop = true; loopDepth++; },
|
|
131
|
+
exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
|
|
132
|
+
},
|
|
133
|
+
ForOfStatement: {
|
|
134
|
+
enter() { inLoop = true; loopDepth++; },
|
|
135
|
+
exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
|
|
136
|
+
},
|
|
137
|
+
WhileStatement: {
|
|
138
|
+
enter() { inLoop = true; loopDepth++; },
|
|
139
|
+
exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// Check for map/forEach with DB calls (potential N+1)
|
|
143
|
+
CallExpression(path) {
|
|
144
|
+
const node = path.node;
|
|
145
|
+
const callee = node.callee;
|
|
146
|
+
const loc = node.loc?.start;
|
|
147
|
+
if (!loc) return;
|
|
148
|
+
|
|
149
|
+
// Check for array.map/forEach that might contain DB queries
|
|
150
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
151
|
+
const methodName = callee.property.name;
|
|
152
|
+
|
|
153
|
+
if (["map", "forEach", "filter", "reduce"].includes(methodName)) {
|
|
154
|
+
const callback = node.arguments[0];
|
|
155
|
+
|
|
156
|
+
if (callback && (t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback))) {
|
|
157
|
+
// Check if callback contains DB operations
|
|
158
|
+
let hasDBCall = false;
|
|
159
|
+
let dbCallLoc = null;
|
|
160
|
+
|
|
161
|
+
path.traverse({
|
|
162
|
+
CallExpression(innerPath) {
|
|
163
|
+
const innerNode = innerPath.node;
|
|
164
|
+
const innerCallee = innerNode.callee;
|
|
165
|
+
|
|
166
|
+
// Check for Prisma patterns
|
|
167
|
+
if (orm === "prisma" && t.isMemberExpression(innerCallee)) {
|
|
168
|
+
const prop = innerCallee.property;
|
|
169
|
+
if (t.isIdentifier(prop) &&
|
|
170
|
+
["findUnique", "findFirst", "findMany", "create", "update", "delete"].includes(prop.name)) {
|
|
171
|
+
hasDBCall = true;
|
|
172
|
+
dbCallLoc = innerNode.loc?.start;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check for await inside the callback (common pattern)
|
|
177
|
+
if (t.isAwaitExpression(innerPath.parent)) {
|
|
178
|
+
// Check if awaiting a DB operation
|
|
179
|
+
const awaitedCode = code.substring(innerNode.start, innerNode.end);
|
|
180
|
+
const dbPatterns = [
|
|
181
|
+
/findUnique|findFirst|findMany|findOne|findById/,
|
|
182
|
+
/\.select\(|\.find\(|\.query\(/,
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
if (dbPatterns.some(p => p.test(awaitedCode))) {
|
|
186
|
+
hasDBCall = true;
|
|
187
|
+
dbCallLoc = innerNode.loc?.start;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (hasDBCall) {
|
|
194
|
+
findings.push({
|
|
195
|
+
type: "n_plus_1_query",
|
|
196
|
+
severity: "WARN",
|
|
197
|
+
category: "DatabasePatterns",
|
|
198
|
+
file: filePath,
|
|
199
|
+
line: dbCallLoc?.line || loc.line,
|
|
200
|
+
column: 0,
|
|
201
|
+
title: "Potential N+1 query pattern",
|
|
202
|
+
message: `Database query inside ${methodName}() callback. This executes a query for each item.`,
|
|
203
|
+
codeSnippet: snippetForLine(lines, dbCallLoc?.line || loc.line),
|
|
204
|
+
confidence: "high",
|
|
205
|
+
fixHint: orm === "prisma"
|
|
206
|
+
? "Use include/select to fetch related data in one query, or use Prisma's createMany/updateMany"
|
|
207
|
+
: "Batch queries or use eager loading to reduce database roundtrips",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Detect DB operations in loops
|
|
215
|
+
if (inLoop && t.isMemberExpression(callee)) {
|
|
216
|
+
const prop = callee.property;
|
|
217
|
+
const ormPatterns = ORM_PATTERNS[orm];
|
|
218
|
+
|
|
219
|
+
if (ormPatterns && t.isIdentifier(prop)) {
|
|
220
|
+
const isDbOp = Object.entries(ormPatterns)
|
|
221
|
+
.filter(([key]) => key !== "import" && key !== "transaction" && key !== "rawQuery")
|
|
222
|
+
.some(([_, pattern]) => pattern.test(`.${prop.name}(`));
|
|
223
|
+
|
|
224
|
+
if (isDbOp) {
|
|
225
|
+
findings.push({
|
|
226
|
+
type: "query_in_loop",
|
|
227
|
+
severity: "WARN",
|
|
228
|
+
category: "DatabasePatterns",
|
|
229
|
+
file: filePath,
|
|
230
|
+
line: loc.line,
|
|
231
|
+
column: loc.column,
|
|
232
|
+
title: "Database query inside loop",
|
|
233
|
+
message: `${prop.name}() called in a loop may cause performance issues.`,
|
|
234
|
+
codeSnippet: snippetForLine(lines, loc.line),
|
|
235
|
+
confidence: "high",
|
|
236
|
+
fixHint: "Batch operations or fetch all data before the loop",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check for findMany/findAll without take/limit
|
|
243
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
244
|
+
const methodName = callee.property.name;
|
|
245
|
+
|
|
246
|
+
if (["findMany", "findAll", "find"].includes(methodName)) {
|
|
247
|
+
const args = node.arguments;
|
|
248
|
+
let hasLimit = false;
|
|
249
|
+
|
|
250
|
+
if (args.length > 0 && t.isObjectExpression(args[0])) {
|
|
251
|
+
const props = args[0].properties;
|
|
252
|
+
hasLimit = props.some(p =>
|
|
253
|
+
t.isObjectProperty(p) &&
|
|
254
|
+
t.isIdentifier(p.key) &&
|
|
255
|
+
["take", "limit", "first", "last"].includes(p.key.name)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!hasLimit) {
|
|
260
|
+
findings.push({
|
|
261
|
+
type: "unbounded_query",
|
|
262
|
+
severity: "INFO",
|
|
263
|
+
category: "DatabasePatterns",
|
|
264
|
+
file: filePath,
|
|
265
|
+
line: loc.line,
|
|
266
|
+
column: loc.column,
|
|
267
|
+
title: "Query without limit",
|
|
268
|
+
message: `${methodName}() without limit may return excessive data.`,
|
|
269
|
+
codeSnippet: snippetForLine(lines, loc.line),
|
|
270
|
+
confidence: "low",
|
|
271
|
+
fixHint: orm === "prisma"
|
|
272
|
+
? "Add { take: 100 } or implement pagination"
|
|
273
|
+
: "Add a limit parameter or implement pagination",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check for missing transaction in multiple writes
|
|
280
|
+
// (This is a heuristic check)
|
|
281
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
282
|
+
const methodName = callee.property.name;
|
|
283
|
+
const writeMethods = ["create", "update", "delete", "upsert", "insert", "save", "remove", "destroy"];
|
|
284
|
+
|
|
285
|
+
if (writeMethods.includes(methodName) && !inTransaction) {
|
|
286
|
+
// Check if there are multiple write operations in the same function
|
|
287
|
+
const fnParent = path.getFunctionParent();
|
|
288
|
+
if (fnParent) {
|
|
289
|
+
let writeCount = 0;
|
|
290
|
+
|
|
291
|
+
fnParent.traverse({
|
|
292
|
+
CallExpression(innerPath) {
|
|
293
|
+
const innerCallee = innerPath.node.callee;
|
|
294
|
+
if (t.isMemberExpression(innerCallee) && t.isIdentifier(innerCallee.property)) {
|
|
295
|
+
if (writeMethods.includes(innerCallee.property.name)) {
|
|
296
|
+
writeCount++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (writeCount >= 2) {
|
|
303
|
+
// Check if function uses transaction
|
|
304
|
+
const fnCode = code.substring(fnParent.node.start, fnParent.node.end);
|
|
305
|
+
const hasTransaction = /\$transaction|\.transaction\s*\(|BEGIN|COMMIT/.test(fnCode);
|
|
306
|
+
|
|
307
|
+
if (!hasTransaction) {
|
|
308
|
+
findings.push({
|
|
309
|
+
type: "missing_transaction",
|
|
310
|
+
severity: "INFO",
|
|
311
|
+
category: "DatabasePatterns",
|
|
312
|
+
file: filePath,
|
|
313
|
+
line: loc.line,
|
|
314
|
+
column: loc.column,
|
|
315
|
+
title: "Multiple writes without transaction",
|
|
316
|
+
message: `Function has ${writeCount} write operations without explicit transaction.`,
|
|
317
|
+
codeSnippet: snippetForLine(lines, loc.line),
|
|
318
|
+
confidence: "low",
|
|
319
|
+
fixHint: orm === "prisma"
|
|
320
|
+
? "Wrap in prisma.$transaction([...])"
|
|
321
|
+
: "Wrap operations in a transaction for atomicity",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check for raw queries (SQL injection risk)
|
|
330
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
331
|
+
const methodName = callee.property.name;
|
|
332
|
+
const rawMethods = ["query", "raw", "$queryRaw", "$executeRaw", "queryRaw", "executeRaw"];
|
|
333
|
+
|
|
334
|
+
if (rawMethods.includes(methodName)) {
|
|
335
|
+
const args = node.arguments;
|
|
336
|
+
|
|
337
|
+
// Check if using template literal (safer) vs string concatenation
|
|
338
|
+
if (args.length > 0) {
|
|
339
|
+
const queryArg = args[0];
|
|
340
|
+
|
|
341
|
+
// String concatenation or interpolation is risky
|
|
342
|
+
if (t.isBinaryExpression(queryArg, { operator: "+" }) ||
|
|
343
|
+
(t.isTemplateLiteral(queryArg) && queryArg.expressions.length > 0)) {
|
|
344
|
+
|
|
345
|
+
// Check if expressions are parameterized
|
|
346
|
+
let hasUnsafeInterpolation = false;
|
|
347
|
+
|
|
348
|
+
if (t.isTemplateLiteral(queryArg)) {
|
|
349
|
+
// Prisma's Prisma.sql`` is safe, but direct string template isn't
|
|
350
|
+
const parentCallee = path.parentPath?.node?.callee;
|
|
351
|
+
const isPrismaSql = t.isMemberExpression(parentCallee) &&
|
|
352
|
+
t.isIdentifier(parentCallee.object, { name: "Prisma" }) &&
|
|
353
|
+
t.isIdentifier(parentCallee.property, { name: "sql" });
|
|
354
|
+
|
|
355
|
+
if (!isPrismaSql) {
|
|
356
|
+
hasUnsafeInterpolation = true;
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
hasUnsafeInterpolation = true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (hasUnsafeInterpolation) {
|
|
363
|
+
findings.push({
|
|
364
|
+
type: "raw_query_interpolation",
|
|
365
|
+
severity: "WARN",
|
|
366
|
+
category: "DatabasePatterns",
|
|
367
|
+
file: filePath,
|
|
368
|
+
line: loc.line,
|
|
369
|
+
column: loc.column,
|
|
370
|
+
title: "Dynamic values in raw query",
|
|
371
|
+
message: "Raw query with string interpolation may be vulnerable to SQL injection.",
|
|
372
|
+
codeSnippet: snippetForLine(lines, loc.line),
|
|
373
|
+
confidence: "med",
|
|
374
|
+
fixHint: orm === "prisma"
|
|
375
|
+
? "Use Prisma.sql`` tagged template or $queryRaw with Prisma.sql"
|
|
376
|
+
: "Use parameterized queries with placeholders ($1, ?, :name)",
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
// Check for missing error handling on DB operations
|
|
386
|
+
AwaitExpression(path) {
|
|
387
|
+
const node = path.node;
|
|
388
|
+
const argument = node.argument;
|
|
389
|
+
const loc = node.loc?.start;
|
|
390
|
+
if (!loc) return;
|
|
391
|
+
|
|
392
|
+
// Check if it's a DB operation
|
|
393
|
+
if (t.isCallExpression(argument) && t.isMemberExpression(argument.callee)) {
|
|
394
|
+
const prop = argument.callee.property;
|
|
395
|
+
const dbMethods = ["findMany", "findFirst", "findUnique", "findOne", "find", "findAll",
|
|
396
|
+
"create", "update", "delete", "save", "query", "execute"];
|
|
397
|
+
|
|
398
|
+
if (t.isIdentifier(prop) && dbMethods.includes(prop.name)) {
|
|
399
|
+
// Check if inside try-catch
|
|
400
|
+
const tryParent = path.findParent(p => p.isTryStatement());
|
|
401
|
+
|
|
402
|
+
if (!tryParent) {
|
|
403
|
+
findings.push({
|
|
404
|
+
type: "db_no_error_handling",
|
|
405
|
+
severity: "INFO",
|
|
406
|
+
category: "DatabasePatterns",
|
|
407
|
+
file: filePath,
|
|
408
|
+
line: loc.line,
|
|
409
|
+
column: loc.column,
|
|
410
|
+
title: "Database operation without error handling",
|
|
411
|
+
message: `${prop.name}() should be wrapped in try-catch for proper error handling.`,
|
|
412
|
+
codeSnippet: snippetForLine(lines, loc.line),
|
|
413
|
+
confidence: "low",
|
|
414
|
+
fixHint: "Wrap in try-catch to handle potential database errors",
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
return findings;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
module.exports = {
|
|
426
|
+
analyzeDatabasePatterns,
|
|
427
|
+
detectORM,
|
|
428
|
+
ORM_PATTERNS,
|
|
429
|
+
};
|