@vibecheckai/cli 3.5.0 → 3.5.2
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 +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- 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 +73 -373
- 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/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context-Aware Detection Utilities
|
|
3
|
-
*
|
|
4
|
-
* Provides smarter detection logic to reduce false positives in vibecheck scans.
|
|
5
|
-
* Handles cases like:
|
|
6
|
-
* - Tailwind CSS modifiers (placeholder:text-white/40)
|
|
7
|
-
* - UI button labels (Mock/Live toggles)
|
|
8
|
-
* - Valid example data (Todo App in project lists)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Patterns that indicate Tailwind CSS class context
|
|
13
|
-
* These are styling directives, not placeholder content
|
|
14
|
-
*/
|
|
15
|
-
const TAILWIND_MODIFIER_PATTERNS = [
|
|
16
|
-
// Tailwind placeholder: modifier (styles placeholder text in inputs)
|
|
17
|
-
/\bplaceholder:/,
|
|
18
|
-
// Other Tailwind state modifiers that might contain keywords
|
|
19
|
-
/\b(?:hover|focus|active|disabled|checked|invalid|required|empty|default):/,
|
|
20
|
-
// Tailwind arbitrary value syntax
|
|
21
|
-
/\[placeholder[:\]]/,
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Patterns that indicate className or CSS context
|
|
26
|
-
*/
|
|
27
|
-
const CSS_CLASS_PATTERNS = [
|
|
28
|
-
// className="..." or className={...}
|
|
29
|
-
/className\s*[=:]\s*[{"`']/,
|
|
30
|
-
// class="..." (HTML)
|
|
31
|
-
/\bclass\s*=\s*["']/,
|
|
32
|
-
// Tailwind @apply directive
|
|
33
|
-
/@apply\s+/,
|
|
34
|
-
// CSS-in-JS style objects
|
|
35
|
-
/\bstyle\s*[=:]\s*\{/,
|
|
36
|
-
// clsx, classnames, cn utilities
|
|
37
|
-
/\b(?:clsx|classnames|cn)\s*\(/,
|
|
38
|
-
// twMerge, tailwind-merge
|
|
39
|
-
/\b(?:twMerge|tailwind-merge)\s*\(/,
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Patterns for UI labels/buttons that legitimately use "mock", "todo", etc.
|
|
44
|
-
*/
|
|
45
|
-
const LEGITIMATE_UI_LABEL_PATTERNS = [
|
|
46
|
-
// Button onClick handlers with mode labels
|
|
47
|
-
/onClick\s*=.*['"](?:mock|live|demo|test)["']/i,
|
|
48
|
-
// Mode change handlers
|
|
49
|
-
/onModeChange\s*\?\.\s*\(['"](?:mock|live|demo)["']\)/i,
|
|
50
|
-
// Radio button or toggle values
|
|
51
|
-
/value\s*[=:]\s*['"](?:mock|live|demo|test)["']/i,
|
|
52
|
-
// UI state labels
|
|
53
|
-
/mode\s*===?\s*['"](?:mock|live|demo|test)["']/i,
|
|
54
|
-
// Tab or button labels in arrays/maps
|
|
55
|
-
/\[\s*['"](?:mock|live)["']\s*,/i,
|
|
56
|
-
// Option/select values
|
|
57
|
-
/option\s*value\s*=\s*['"](?:mock|live|demo)["']/i,
|
|
58
|
-
// Button children with mode text
|
|
59
|
-
/>\s*(?:Mock|Live|Demo|Test)\s*</i,
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Patterns for legitimate example/template data
|
|
64
|
-
*/
|
|
65
|
-
const LEGITIMATE_EXAMPLE_DATA_PATTERNS = [
|
|
66
|
-
// Array of project/template names (common in UI)
|
|
67
|
-
/\[\s*['"](?:Todo\s*App|Landing\s*Page|Dashboard|Blog|Portfolio|E-?commerce)["']/i,
|
|
68
|
-
// Project name selectors or dropdowns
|
|
69
|
-
/projectName|templateName|appName|recentProjects/i,
|
|
70
|
-
// Example/sample data arrays in UI components
|
|
71
|
-
/examples?\s*[=:]\s*\[/i,
|
|
72
|
-
/templates?\s*[=:]\s*\[/i,
|
|
73
|
-
/presets?\s*[=:]\s*\[/i,
|
|
74
|
-
// Map over example items
|
|
75
|
-
/\.map\(\s*\(\s*(?:name|item|template)\s*\)/i,
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Check if a line contains Tailwind CSS placeholder modifier
|
|
80
|
-
*/
|
|
81
|
-
function isTailwindPlaceholderModifier(line) {
|
|
82
|
-
return TAILWIND_MODIFIER_PATTERNS.some(pattern => pattern.test(line));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Check if the context is a CSS class definition
|
|
87
|
-
*/
|
|
88
|
-
function isCSSClassContext(line, surroundingLines) {
|
|
89
|
-
// Check the current line
|
|
90
|
-
if (CSS_CLASS_PATTERNS.some(pattern => pattern.test(line))) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check surrounding lines for context
|
|
95
|
-
if (surroundingLines && surroundingLines.length > 0) {
|
|
96
|
-
const contextBlock = surroundingLines.join('\n');
|
|
97
|
-
return CSS_CLASS_PATTERNS.some(pattern => pattern.test(contextBlock));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Check if "mock" or similar words are used as legitimate UI labels
|
|
105
|
-
*/
|
|
106
|
-
function isLegitimateUILabel(line, surroundingLines) {
|
|
107
|
-
const fullContext = surroundingLines
|
|
108
|
-
? [...surroundingLines, line].join('\n')
|
|
109
|
-
: line;
|
|
110
|
-
|
|
111
|
-
return LEGITIMATE_UI_LABEL_PATTERNS.some(pattern => pattern.test(fullContext));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if the content is legitimate example/template data
|
|
116
|
-
*/
|
|
117
|
-
function isLegitimateExampleData(line, surroundingLines) {
|
|
118
|
-
const fullContext = surroundingLines
|
|
119
|
-
? [...surroundingLines, line].join('\n')
|
|
120
|
-
: line;
|
|
121
|
-
|
|
122
|
-
return LEGITIMATE_EXAMPLE_DATA_PATTERNS.some(pattern => pattern.test(fullContext));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Check if the match is in a JSX prop context (not user-visible content)
|
|
127
|
-
*/
|
|
128
|
-
function isJSXPropContext(line, matchedPattern) {
|
|
129
|
-
// Check if the pattern is part of a JSX prop name (not value)
|
|
130
|
-
const propNamePatterns = [
|
|
131
|
-
// Prop names that legitimately contain keywords
|
|
132
|
-
/\b(?:placeholder|mock|test)(?:Id|Key|Ref|Name|Type|Mode|Data)\s*[=:]/i,
|
|
133
|
-
// Data attributes
|
|
134
|
-
/data-(?:placeholder|mock|test)\s*=/i,
|
|
135
|
-
// Aria labels (visible content, but intentional)
|
|
136
|
-
/aria-(?:label|description)\s*=\s*["'][^"']*(?:placeholder|mock|test)/i,
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
return propNamePatterns.some(pattern => pattern.test(line));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Main context detection function
|
|
144
|
-
* Analyzes a line and its context to determine if a match is a false positive
|
|
145
|
-
*/
|
|
146
|
-
function detectFalsePositive(line, matchedPattern, options = {}) {
|
|
147
|
-
const { lineIndex, allLines, filePath, patternType } = options;
|
|
148
|
-
|
|
149
|
-
// Get surrounding lines for context (2 before, 2 after)
|
|
150
|
-
let surroundingLines = [];
|
|
151
|
-
if (allLines && lineIndex !== undefined) {
|
|
152
|
-
const start = Math.max(0, lineIndex - 2);
|
|
153
|
-
const end = Math.min(allLines.length, lineIndex + 3);
|
|
154
|
-
surroundingLines = allLines.slice(start, end);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Check for Tailwind CSS placeholder modifier
|
|
158
|
-
const lowerPattern = (matchedPattern || '').toLowerCase();
|
|
159
|
-
if (lowerPattern.includes('placeholder') || patternType === 'placeholder') {
|
|
160
|
-
if (isTailwindPlaceholderModifier(line)) {
|
|
161
|
-
return {
|
|
162
|
-
isFalsePositive: true,
|
|
163
|
-
reason: 'Tailwind CSS placeholder: modifier (styles input placeholder text)',
|
|
164
|
-
confidence: 0.95,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Check for CSS class context
|
|
170
|
-
if (isCSSClassContext(line, surroundingLines)) {
|
|
171
|
-
return {
|
|
172
|
-
isFalsePositive: true,
|
|
173
|
-
reason: 'CSS class/className context (styling, not content)',
|
|
174
|
-
confidence: 0.9,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Check for legitimate UI labels (Mock/Live toggles, etc.)
|
|
179
|
-
if (lowerPattern.includes('mock') || patternType === 'mock') {
|
|
180
|
-
if (isLegitimateUILabel(line, surroundingLines)) {
|
|
181
|
-
return {
|
|
182
|
-
isFalsePositive: true,
|
|
183
|
-
reason: 'Legitimate UI label (mode toggle, button text)',
|
|
184
|
-
confidence: 0.85,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Check for legitimate example data ("Todo App", etc.)
|
|
190
|
-
if (lowerPattern.includes('todo') || patternType === 'todo') {
|
|
191
|
-
if (isLegitimateExampleData(line, surroundingLines)) {
|
|
192
|
-
return {
|
|
193
|
-
isFalsePositive: true,
|
|
194
|
-
reason: 'Legitimate example/template data in UI',
|
|
195
|
-
confidence: 0.8,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Check for JSX props that are not content
|
|
201
|
-
if (isJSXPropContext(line, matchedPattern)) {
|
|
202
|
-
return {
|
|
203
|
-
isFalsePositive: true,
|
|
204
|
-
reason: 'JSX prop context (attribute, not visible content)',
|
|
205
|
-
confidence: 0.75,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Not a false positive
|
|
210
|
-
return {
|
|
211
|
-
isFalsePositive: false,
|
|
212
|
-
confidence: 0.5,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Quick check if a line is likely a false positive
|
|
218
|
-
* Use this for fast pre-filtering before detailed analysis
|
|
219
|
-
*/
|
|
220
|
-
function quickFalsePositiveCheck(line) {
|
|
221
|
-
// Fast check for common false positive patterns
|
|
222
|
-
const quickPatterns = [
|
|
223
|
-
/className.*placeholder:/,
|
|
224
|
-
/class=.*placeholder:/,
|
|
225
|
-
/placeholder:text-/,
|
|
226
|
-
/placeholder:opacity-/,
|
|
227
|
-
/placeholder:italic/,
|
|
228
|
-
/onModeChange.*['"]mock["']/i,
|
|
229
|
-
/onClick.*['"]mock["']/i,
|
|
230
|
-
/value=['"](?:mock|live)["']/i,
|
|
231
|
-
/\[['"]Todo App["']/i,
|
|
232
|
-
/\[['"]Mock["'],\s*['"]Live["']\]/i,
|
|
233
|
-
/>\s*Mock\s*</i,
|
|
234
|
-
/>\s*Live\s*</i,
|
|
235
|
-
];
|
|
236
|
-
|
|
237
|
-
return quickPatterns.some(pattern => pattern.test(line));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Filter findings to remove false positives
|
|
242
|
-
*/
|
|
243
|
-
function filterFalsePositives(findings, allLines, options = {}) {
|
|
244
|
-
return findings.filter(finding => {
|
|
245
|
-
const lineContent = finding.content || finding.codeSnippet || allLines[finding.line - 1] || '';
|
|
246
|
-
const result = detectFalsePositive(lineContent, lineContent, {
|
|
247
|
-
lineIndex: finding.line - 1,
|
|
248
|
-
allLines,
|
|
249
|
-
...options,
|
|
250
|
-
});
|
|
251
|
-
return !result.isFalsePositive;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
module.exports = {
|
|
256
|
-
isTailwindPlaceholderModifier,
|
|
257
|
-
isCSSClassContext,
|
|
258
|
-
isLegitimateUILabel,
|
|
259
|
-
isLegitimateExampleData,
|
|
260
|
-
isJSXPropContext,
|
|
261
|
-
detectFalsePositive,
|
|
262
|
-
quickFalsePositiveCheck,
|
|
263
|
-
filterFalsePositives,
|
|
264
|
-
};
|
|
@@ -1,429 +0,0 @@
|
|
|
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
|
-
};
|