@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
|
@@ -12,24 +12,6 @@ const t = require("@babel/types");
|
|
|
12
12
|
|
|
13
13
|
const { routeMatches } = require("./claims");
|
|
14
14
|
const { matcherCoversPath } = require("./auth-truth");
|
|
15
|
-
const {
|
|
16
|
-
getFrameworks,
|
|
17
|
-
hasFramework,
|
|
18
|
-
getFileContext,
|
|
19
|
-
isServerComponent,
|
|
20
|
-
isClientComponent,
|
|
21
|
-
isRouteHandler,
|
|
22
|
-
FRAMEWORKS,
|
|
23
|
-
} = require("./engines/framework-detection");
|
|
24
|
-
|
|
25
|
-
// V6: Bulletproof noise reduction and false positive prevention
|
|
26
|
-
let noiseReduction, falsePositivePrevention;
|
|
27
|
-
try {
|
|
28
|
-
noiseReduction = require("./engines/noise-reduction-engine");
|
|
29
|
-
falsePositivePrevention = require("./engines/false-positive-prevention");
|
|
30
|
-
} catch {
|
|
31
|
-
// Engines not available - continue without them
|
|
32
|
-
}
|
|
33
15
|
|
|
34
16
|
/* ============================================================================
|
|
35
17
|
* STANDARD IGNORE PATTERNS
|
|
@@ -141,7 +123,22 @@ function rxTest(rx, s) {
|
|
|
141
123
|
return rx.test(s);
|
|
142
124
|
}
|
|
143
125
|
|
|
126
|
+
// Try to use the engine's globalASTCache if available for better performance
|
|
127
|
+
let _globalASTCache = null;
|
|
128
|
+
try {
|
|
129
|
+
_globalASTCache = require("./engine/ast-cache").globalASTCache;
|
|
130
|
+
} catch {
|
|
131
|
+
// Engine not available, will use direct parsing
|
|
132
|
+
}
|
|
133
|
+
|
|
144
134
|
function parseFile(code, fileAbsForErrors = "") {
|
|
135
|
+
// Use globalASTCache if available (engine v2 integration)
|
|
136
|
+
if (_globalASTCache) {
|
|
137
|
+
const result = _globalASTCache.parse(code, fileAbsForErrors);
|
|
138
|
+
if (result.ast) return result.ast;
|
|
139
|
+
// Fall through to direct parse if cache failed
|
|
140
|
+
}
|
|
141
|
+
|
|
145
142
|
// Error recovery avoids hard-failing on mixed TS/JS/JSX edge cases.
|
|
146
143
|
return parser.parse(code, {
|
|
147
144
|
sourceType: "unambiguous",
|
|
@@ -889,8 +886,11 @@ function findEnvGaps(truthpack) {
|
|
|
889
886
|
confidence: isReallyRequired ? "high" : "low",
|
|
890
887
|
evidence: v.references || [],
|
|
891
888
|
fixHints: [
|
|
892
|
-
`Add
|
|
893
|
-
|
|
889
|
+
`Add to .env.example: ${v.name}=your_value_here`,
|
|
890
|
+
isReallyRequired
|
|
891
|
+
? `Add fallback: const value = process.env.${v.name} ?? 'default';`
|
|
892
|
+
: `Guard usage: if (process.env.${v.name}) { /* use it */ }`,
|
|
893
|
+
"Docs: https://12factor.net/config",
|
|
894
894
|
],
|
|
895
895
|
});
|
|
896
896
|
}
|
|
@@ -981,21 +981,6 @@ function findFakeSuccess(repoRoot) {
|
|
|
981
981
|
// relevant keywords (toast/push/navigate AND fetch/axios).
|
|
982
982
|
const hasSuccessUI = /\b(toast|\.push|navigate)\b/.test(code);
|
|
983
983
|
const hasNetworkCall = /\b(fetch|axios)\b/.test(code);
|
|
984
|
-
|
|
985
|
-
// Enhanced: Also check for TanStack Query / React Query mutations
|
|
986
|
-
// These have built-in error handling via onError callback
|
|
987
|
-
const hasTanstackMutation = /\buseMutation\b/.test(code);
|
|
988
|
-
const hasTRPCMutation = /\.useMutation\b/.test(code);
|
|
989
|
-
|
|
990
|
-
// Skip files that use TanStack Query mutations with onSuccess/onError
|
|
991
|
-
// These are properly handling async state
|
|
992
|
-
if (hasTanstackMutation || hasTRPCMutation) {
|
|
993
|
-
const hasProperCallbacks = /onSuccess\s*:|onError\s*:|onSettled\s*:/.test(code);
|
|
994
|
-
if (hasProperCallbacks) {
|
|
995
|
-
continue; // Properly handled
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
984
|
if (!hasSuccessUI || !hasNetworkCall) {
|
|
1000
985
|
continue;
|
|
1001
986
|
}
|
|
@@ -1031,7 +1016,16 @@ function findFakeSuccess(repoRoot) {
|
|
|
1031
1016
|
IfStatement(p) {
|
|
1032
1017
|
const test = p.node.test;
|
|
1033
1018
|
const txt = code.slice(test.start || 0, test.end || 0);
|
|
1019
|
+
// Check for response status validation (res.ok, status)
|
|
1034
1020
|
if (/\b(res|response)\b/i.test(txt) && /\b(ok|status)\b/i.test(txt)) okChecks.push({ pos: p.node.start ?? 0 });
|
|
1021
|
+
// Check for response body validation (data, error, success property checks)
|
|
1022
|
+
if (/\b(data|result|response)\b/i.test(txt) && /\b(error|success|\.data|\.result)\b/i.test(txt)) okChecks.push({ pos: p.node.start ?? 0 });
|
|
1023
|
+
},
|
|
1024
|
+
// Also check for try-catch around the network call as a form of validation
|
|
1025
|
+
TryStatement(tryPath) {
|
|
1026
|
+
if (tryPath.node.handler) {
|
|
1027
|
+
okChecks.push({ pos: tryPath.node.start ?? 0 });
|
|
1028
|
+
}
|
|
1035
1029
|
},
|
|
1036
1030
|
});
|
|
1037
1031
|
|
|
@@ -1063,8 +1057,9 @@ function findFakeSuccess(repoRoot) {
|
|
|
1063
1057
|
confidence: "med",
|
|
1064
1058
|
evidence: ev ? [ev] : [],
|
|
1065
1059
|
fixHints: [
|
|
1066
|
-
"
|
|
1067
|
-
"
|
|
1060
|
+
"const res = await fetch(...); if (!res.ok) throw new Error('Request failed');",
|
|
1061
|
+
"const data = await res.json(); if (data.success) toast.success('Done!'); else toast.error(data.error);",
|
|
1062
|
+
"Use try-catch: try { await api(); toast.success(); } catch (e) { toast.error(e.message); }",
|
|
1068
1063
|
],
|
|
1069
1064
|
});
|
|
1070
1065
|
}
|
|
@@ -1084,58 +1079,18 @@ function findFakeSuccess(repoRoot) {
|
|
|
1084
1079
|
* ========================================================================== */
|
|
1085
1080
|
|
|
1086
1081
|
function looksSensitive(pathStr) {
|
|
1087
|
-
const p = String(pathStr || "")
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
"/api/
|
|
1092
|
-
"/api/
|
|
1093
|
-
"/api/
|
|
1094
|
-
"/api/
|
|
1095
|
-
"/api/
|
|
1096
|
-
"/api/
|
|
1097
|
-
"/api/
|
|
1098
|
-
|
|
1099
|
-
"/api/user",
|
|
1100
|
-
"/api/profile",
|
|
1101
|
-
"/api/dashboard",
|
|
1102
|
-
"/api/private",
|
|
1103
|
-
"/api/internal",
|
|
1104
|
-
"/api/manage",
|
|
1105
|
-
"/api/payment",
|
|
1106
|
-
"/api/subscription",
|
|
1107
|
-
"/api/checkout",
|
|
1108
|
-
"/api/webhook", // Webhooks need verification
|
|
1109
|
-
"/api/upload",
|
|
1110
|
-
"/api/delete",
|
|
1111
|
-
"/api/export",
|
|
1112
|
-
"/api/import",
|
|
1113
|
-
];
|
|
1114
|
-
|
|
1115
|
-
// Sensitive path patterns (regex)
|
|
1116
|
-
const sensitivePathPatterns = [
|
|
1117
|
-
/\/api\/.*\/admin/,
|
|
1118
|
-
/\/api\/.*\/delete/,
|
|
1119
|
-
/\/api\/.*\/edit/,
|
|
1120
|
-
/\/api\/.*\/update/,
|
|
1121
|
-
/\/api\/.*\/create/,
|
|
1122
|
-
/\/api\/.*\/remove/,
|
|
1123
|
-
/\/api\/.*\/modify/,
|
|
1124
|
-
/\/api\/me\b/,
|
|
1125
|
-
/\/api\/\[.*id\]/, // Dynamic user/resource routes
|
|
1126
|
-
];
|
|
1127
|
-
|
|
1128
|
-
// Check prefixes
|
|
1129
|
-
if (sensitivePathPrefixes.some(prefix => p.startsWith(prefix))) {
|
|
1130
|
-
return true;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
// Check patterns
|
|
1134
|
-
if (sensitivePathPatterns.some(pattern => pattern.test(p))) {
|
|
1135
|
-
return true;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
return false;
|
|
1082
|
+
const p = String(pathStr || "");
|
|
1083
|
+
return (
|
|
1084
|
+
p.startsWith("/api/admin") ||
|
|
1085
|
+
p.startsWith("/api/billing") ||
|
|
1086
|
+
p.startsWith("/api/stripe") ||
|
|
1087
|
+
p.startsWith("/api/org") ||
|
|
1088
|
+
p.startsWith("/api/team") ||
|
|
1089
|
+
p.startsWith("/api/account") ||
|
|
1090
|
+
p.startsWith("/api/settings") ||
|
|
1091
|
+
p.startsWith("/api/users") ||
|
|
1092
|
+
p.startsWith("/api/user")
|
|
1093
|
+
);
|
|
1139
1094
|
}
|
|
1140
1095
|
|
|
1141
1096
|
function hasRouteLevelProtection(routeDef) {
|
|
@@ -1148,69 +1103,11 @@ function handlerHasAuthSignal(repoRoot, handlerRel) {
|
|
|
1148
1103
|
if (!fs.existsSync(abs)) return false;
|
|
1149
1104
|
const code = readFileCached(abs);
|
|
1150
1105
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
/\
|
|
1154
|
-
/\
|
|
1155
|
-
|
|
1156
|
-
/\buseSession\b/,
|
|
1157
|
-
/\bgetToken\b/,
|
|
1158
|
-
];
|
|
1159
|
-
|
|
1160
|
-
// Clerk patterns
|
|
1161
|
-
const clerkPatterns = [
|
|
1162
|
-
/\bclerk\b/i,
|
|
1163
|
-
/@clerk\/nextjs/,
|
|
1164
|
-
/\bauth\(\)\s*\.\s*userId/,
|
|
1165
|
-
/\bcurrentUser\b/,
|
|
1166
|
-
/\bgetAuth\b/,
|
|
1167
|
-
];
|
|
1168
|
-
|
|
1169
|
-
// Supabase patterns
|
|
1170
|
-
const supabasePatterns = [
|
|
1171
|
-
/\bcreateRouteHandlerClient\b/,
|
|
1172
|
-
/\bcreateServerComponentClient\b/,
|
|
1173
|
-
/\bsupabase\b.*\bauth\b/i,
|
|
1174
|
-
/@supabase/,
|
|
1175
|
-
/\bgetUser\(\)/,
|
|
1176
|
-
];
|
|
1177
|
-
|
|
1178
|
-
// General auth patterns
|
|
1179
|
-
const generalAuthPatterns = [
|
|
1180
|
-
/\b(jwtVerify|verifyToken|verifyJWT)\b/i,
|
|
1181
|
-
/\bauthorization\b/i,
|
|
1182
|
-
/\bbearer\s+token/i,
|
|
1183
|
-
/\b(isAdmin|adminOnly|permissions|rbac)\b/i,
|
|
1184
|
-
/\bauthenticated\b/i,
|
|
1185
|
-
/\brequireAuth\b/i,
|
|
1186
|
-
/\bprotectedProcedure\b/, // tRPC
|
|
1187
|
-
];
|
|
1188
|
-
|
|
1189
|
-
// Next.js App Router specific patterns
|
|
1190
|
-
const appRouterPatterns = [
|
|
1191
|
-
/\bcookies\(\)\s*\.\s*get\s*\(\s*['"](?:session|token|auth)/i,
|
|
1192
|
-
/\bheaders\(\)\s*\.\s*get\s*\(\s*['"]authorization/i,
|
|
1193
|
-
/\bNextResponse\.redirect\s*\(.*(?:login|signin|unauthorized)/i,
|
|
1194
|
-
];
|
|
1195
|
-
|
|
1196
|
-
// tRPC context patterns (context often has auth)
|
|
1197
|
-
const trpcPatterns = [
|
|
1198
|
-
/ctx\.session/,
|
|
1199
|
-
/ctx\.user/,
|
|
1200
|
-
/ctx\.auth/,
|
|
1201
|
-
/\.protectedProcedure/,
|
|
1202
|
-
];
|
|
1203
|
-
|
|
1204
|
-
const allPatterns = [
|
|
1205
|
-
...nextAuthPatterns,
|
|
1206
|
-
...clerkPatterns,
|
|
1207
|
-
...supabasePatterns,
|
|
1208
|
-
...generalAuthPatterns,
|
|
1209
|
-
...appRouterPatterns,
|
|
1210
|
-
...trpcPatterns,
|
|
1211
|
-
];
|
|
1212
|
-
|
|
1213
|
-
return allPatterns.some(pattern => pattern.test(code));
|
|
1106
|
+
return (
|
|
1107
|
+
/\bgetServerSession\b|\bauth\(\)\b|\bclerk\b|@clerk\/nextjs|\bcreateRouteHandlerClient\b|@supabase/i.test(code) ||
|
|
1108
|
+
/\b(jwtVerify|authorization|bearer|verifyToken|verifyJWT)\b/i.test(code) ||
|
|
1109
|
+
/\b(isAdmin|adminOnly|permissions|rbac)\b/i.test(code)
|
|
1110
|
+
);
|
|
1214
1111
|
}
|
|
1215
1112
|
|
|
1216
1113
|
function isProtectedByNextMiddleware(truthpack, routePath) {
|
|
@@ -1222,7 +1119,12 @@ function findGhostAuth(truthpack, repoRoot) {
|
|
|
1222
1119
|
const findings = [];
|
|
1223
1120
|
const server = truthpack?.routes?.server || [];
|
|
1224
1121
|
|
|
1122
|
+
// Track mutation routes without CSRF protection
|
|
1123
|
+
const mutationMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
1124
|
+
|
|
1225
1125
|
for (const r of server) {
|
|
1126
|
+
const isMutation = mutationMethods.has(String(r.method).toUpperCase());
|
|
1127
|
+
|
|
1226
1128
|
if (!looksSensitive(r.path)) continue;
|
|
1227
1129
|
|
|
1228
1130
|
const middlewareProtected = isProtectedByNextMiddleware(truthpack, r.path);
|
|
@@ -1241,12 +1143,41 @@ function findGhostAuth(truthpack, repoRoot) {
|
|
|
1241
1143
|
confidence: "med",
|
|
1242
1144
|
evidence: (r.evidence || []).slice(0, 2),
|
|
1243
1145
|
fixHints: [
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
"
|
|
1146
|
+
`Next.js: const session = await getServerSession(); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });`,
|
|
1147
|
+
`Fastify: fastify.addHook('preHandler', async (req, reply) => { if (!req.user) reply.code(401).send({ error: 'Unauthorized' }); });`,
|
|
1148
|
+
"Or add to middleware.ts matcher: export const config = { matcher: ['/api/admin/:path*'] };",
|
|
1149
|
+
"Docs: https://nextjs.org/docs/app/building-your-application/authentication",
|
|
1247
1150
|
],
|
|
1248
1151
|
});
|
|
1249
1152
|
}
|
|
1153
|
+
|
|
1154
|
+
// Check for CSRF protection on mutations
|
|
1155
|
+
if (isMutation && r.handler) {
|
|
1156
|
+
const abs = path.join(repoRoot, r.handler);
|
|
1157
|
+
if (fs.existsSync(abs)) {
|
|
1158
|
+
const code = readFileCached(abs);
|
|
1159
|
+
const hasCSRFCheck = /\b(csrf|csrfToken|_csrf|x-csrf-token|xsrf|anti-forgery)\b/i.test(code);
|
|
1160
|
+
const isAPIRoute = r.path.startsWith("/api/");
|
|
1161
|
+
|
|
1162
|
+
// Only warn for non-API routes (API routes typically use bearer tokens)
|
|
1163
|
+
if (!hasCSRFCheck && !isAPIRoute) {
|
|
1164
|
+
findings.push({
|
|
1165
|
+
id: stableId("F_GHOST_AUTH_NO_CSRF", `${r.method} ${r.path}`),
|
|
1166
|
+
severity: "WARN",
|
|
1167
|
+
category: "GhostAuth",
|
|
1168
|
+
title: `Mutation endpoint without CSRF protection: ${r.method} ${r.path}`,
|
|
1169
|
+
why: "State-changing endpoints should verify CSRF tokens to prevent cross-site request forgery attacks.",
|
|
1170
|
+
confidence: "low",
|
|
1171
|
+
evidence: (r.evidence || []).slice(0, 2),
|
|
1172
|
+
fixHints: [
|
|
1173
|
+
"Add CSRF token validation for form submissions.",
|
|
1174
|
+
"Or use SameSite cookies + Origin header validation.",
|
|
1175
|
+
"API routes using bearer tokens are generally exempt.",
|
|
1176
|
+
],
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1250
1181
|
}
|
|
1251
1182
|
|
|
1252
1183
|
const patterns = truthpack?.auth?.nextMatcherPatterns || [];
|
|
@@ -1494,7 +1425,7 @@ function findTodoFixme(repoRoot) {
|
|
|
1494
1425
|
evidence: [],
|
|
1495
1426
|
fixHints: [
|
|
1496
1427
|
"Review and address high-priority TODOs before shipping.",
|
|
1497
|
-
`Run:
|
|
1428
|
+
`Run: vibecheck scan --json | jq '.findings[] | select(.category == "TODO")'`,
|
|
1498
1429
|
],
|
|
1499
1430
|
});
|
|
1500
1431
|
} else {
|
|
@@ -2122,47 +2053,99 @@ function findAPIConsistencyIssues(repoRoot) {
|
|
|
2122
2053
|
}
|
|
2123
2054
|
|
|
2124
2055
|
/* ============================================================================
|
|
2125
|
-
*
|
|
2126
|
-
*
|
|
2056
|
+
* OPTIMISTIC NO ROLLBACK DETECTOR
|
|
2057
|
+
* Finds optimistic UI updates that don't rollback on failure
|
|
2127
2058
|
* ========================================================================== */
|
|
2128
2059
|
|
|
2129
|
-
function
|
|
2130
|
-
const { analyzeReactPatterns } = require("./engines/react-patterns-engine");
|
|
2060
|
+
function findOptimisticNoRollback(repoRoot) {
|
|
2131
2061
|
const findings = [];
|
|
2132
|
-
|
|
2133
|
-
// Only scan React files for performance
|
|
2134
|
-
const files = fg.sync(["**/*.{tsx,jsx}"], {
|
|
2062
|
+
const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
|
|
2135
2063
|
cwd: repoRoot,
|
|
2136
2064
|
absolute: true,
|
|
2137
2065
|
ignore: STANDARD_IGNORE_PATTERNS,
|
|
2138
2066
|
});
|
|
2139
2067
|
|
|
2140
2068
|
for (const fileAbs of files) {
|
|
2069
|
+
const code = readFileCached(fileAbs);
|
|
2070
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
2071
|
+
|
|
2072
|
+
// Fast path: skip files without optimistic patterns
|
|
2073
|
+
const hasOptimisticUpdate = /\b(setOptimistic|optimisticUpdate|setState.*\bfetch|useMutation.*onMutate)\b/i.test(code);
|
|
2074
|
+
const hasStateUpdate = /\b(setState|set[A-Z]\w*|dispatch|update[A-Z])\b/.test(code);
|
|
2075
|
+
const hasNetworkCall = /\b(fetch|axios|useMutation|mutate)\b/.test(code);
|
|
2076
|
+
|
|
2077
|
+
if (!hasStateUpdate || !hasNetworkCall) continue;
|
|
2078
|
+
|
|
2079
|
+
let ast;
|
|
2141
2080
|
try {
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2081
|
+
ast = parseFile(code, fileAbs);
|
|
2082
|
+
} catch {
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
try {
|
|
2087
|
+
traverse(ast, {
|
|
2088
|
+
CallExpression(pathNode) {
|
|
2089
|
+
const n = pathNode.node;
|
|
2090
|
+
|
|
2091
|
+
// Look for setState/dispatch calls followed by fetch without catch/finally rollback
|
|
2092
|
+
if (!t.isMemberExpression(n.callee)) return;
|
|
2093
|
+
|
|
2094
|
+
const methodName = n.callee.property?.name || "";
|
|
2095
|
+
const isStateUpdate = /^(setState|set[A-Z]|dispatch|update)/.test(methodName);
|
|
2096
|
+
|
|
2097
|
+
if (!isStateUpdate) return;
|
|
2098
|
+
|
|
2099
|
+
// Check if parent function has a try-catch with rollback
|
|
2100
|
+
let parentFn = pathNode.findParent(p => p.isFunction());
|
|
2101
|
+
if (!parentFn) return;
|
|
2102
|
+
|
|
2103
|
+
let hasRollback = false;
|
|
2104
|
+
let hasNetworkInSameBlock = false;
|
|
2105
|
+
|
|
2106
|
+
parentFn.traverse({
|
|
2107
|
+
CallExpression(inner) {
|
|
2108
|
+
const callee = inner.node.callee;
|
|
2109
|
+
if (isFetchCall(inner.node) || isAxiosCall(inner.node)) {
|
|
2110
|
+
hasNetworkInSameBlock = true;
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
CatchClause(catchPath) {
|
|
2114
|
+
// Check if catch has a state update (rollback)
|
|
2115
|
+
catchPath.traverse({
|
|
2116
|
+
CallExpression(rollbackCall) {
|
|
2117
|
+
if (t.isMemberExpression(rollbackCall.node.callee)) {
|
|
2118
|
+
const name = rollbackCall.node.callee.property?.name || "";
|
|
2119
|
+
if (/^(setState|set[A-Z]|dispatch|update)/.test(name)) {
|
|
2120
|
+
hasRollback = true;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2128
|
+
// If there's a state update, network call, but no rollback in catch
|
|
2129
|
+
if (hasNetworkInSameBlock && !hasRollback) {
|
|
2130
|
+
const loc = n.loc;
|
|
2131
|
+
findings.push({
|
|
2132
|
+
id: stableId("F_OPTIMISTIC_NO_ROLLBACK", `${fileRel}:${loc?.start?.line || 0}`),
|
|
2133
|
+
severity: "WARN",
|
|
2134
|
+
category: "OptimisticNoRollback",
|
|
2135
|
+
title: "Optimistic update without rollback on failure",
|
|
2136
|
+
why: "State is updated before network call completes, but there's no rollback if the request fails. Users see stale/incorrect data.",
|
|
2137
|
+
confidence: "med",
|
|
2138
|
+
evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Optimistic state update")].filter(Boolean),
|
|
2139
|
+
fixHints: [
|
|
2140
|
+
"Add a catch block that reverts the state to previous value on failure.",
|
|
2141
|
+
"Use react-query's onMutate/onError for automatic rollback.",
|
|
2142
|
+
"Store previous state before update and restore it on error.",
|
|
2143
|
+
],
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
} catch {
|
|
2166
2149
|
continue;
|
|
2167
2150
|
}
|
|
2168
2151
|
}
|
|
@@ -2171,12 +2154,11 @@ function findReactPatternIssues(repoRoot) {
|
|
|
2171
2154
|
}
|
|
2172
2155
|
|
|
2173
2156
|
/* ============================================================================
|
|
2174
|
-
*
|
|
2175
|
-
*
|
|
2157
|
+
* SILENT CATCH DETECTOR (Enhanced)
|
|
2158
|
+
* Finds catch blocks that swallow errors without logging or re-throwing
|
|
2176
2159
|
* ========================================================================== */
|
|
2177
2160
|
|
|
2178
|
-
function
|
|
2179
|
-
const { analyzeErrorHandling } = require("./engines/error-handling-engine");
|
|
2161
|
+
function findSilentCatch(repoRoot) {
|
|
2180
2162
|
const findings = [];
|
|
2181
2163
|
const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
|
|
2182
2164
|
cwd: repoRoot,
|
|
@@ -2185,31 +2167,91 @@ function findErrorHandlingIssues(repoRoot) {
|
|
|
2185
2167
|
});
|
|
2186
2168
|
|
|
2187
2169
|
for (const fileAbs of files) {
|
|
2170
|
+
const code = readFileCached(fileAbs);
|
|
2171
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
2172
|
+
|
|
2173
|
+
// Fast path: skip files without try-catch
|
|
2174
|
+
if (!/\bcatch\s*\(/.test(code)) continue;
|
|
2175
|
+
|
|
2176
|
+
let ast;
|
|
2188
2177
|
try {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2178
|
+
ast = parseFile(code, fileAbs);
|
|
2179
|
+
} catch {
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
try {
|
|
2184
|
+
traverse(ast, {
|
|
2185
|
+
CatchClause(pathNode) {
|
|
2186
|
+
const catchBody = pathNode.node.body;
|
|
2187
|
+
const catchParam = pathNode.node.param?.name || "error";
|
|
2188
|
+
|
|
2189
|
+
// Check if catch body is empty or only has comments
|
|
2190
|
+
if (!catchBody.body || catchBody.body.length === 0) {
|
|
2191
|
+
// Empty catch - already covered by findEmptyCatch
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// Check if catch actually handles the error
|
|
2196
|
+
let logsError = false;
|
|
2197
|
+
let rethrowsError = false;
|
|
2198
|
+
let showsUserError = false;
|
|
2199
|
+
let hasConditionalReturn = false;
|
|
2200
|
+
|
|
2201
|
+
pathNode.traverse({
|
|
2202
|
+
CallExpression(inner) {
|
|
2203
|
+
const callee = inner.node.callee;
|
|
2204
|
+
const args = inner.node.arguments;
|
|
2205
|
+
|
|
2206
|
+
// Check for console.error, console.log, logger.error, etc.
|
|
2207
|
+
if (t.isMemberExpression(callee)) {
|
|
2208
|
+
const obj = callee.object?.name || "";
|
|
2209
|
+
const prop = callee.property?.name || "";
|
|
2210
|
+
if ((obj === "console" || /logger|log/i.test(obj)) && /error|warn|log/.test(prop)) {
|
|
2211
|
+
// Check if it logs the error variable
|
|
2212
|
+
const argsStr = args.map(a => code.slice(a.start, a.end)).join(",");
|
|
2213
|
+
if (argsStr.includes(catchParam) || argsStr.includes("error") || argsStr.includes("err")) {
|
|
2214
|
+
logsError = true;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
// Check for toast.error, notification.error, etc.
|
|
2218
|
+
if (/toast|notification|alert|message/i.test(obj) && /error|warn|fail/.test(prop)) {
|
|
2219
|
+
showsUserError = true;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
},
|
|
2223
|
+
ThrowStatement() {
|
|
2224
|
+
rethrowsError = true;
|
|
2225
|
+
},
|
|
2226
|
+
ReturnStatement(ret) {
|
|
2227
|
+
// Returning early might be intentional error handling
|
|
2228
|
+
const ifParent = ret.findParent(p => p.isIfStatement());
|
|
2229
|
+
if (ifParent) hasConditionalReturn = true;
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
|
|
2233
|
+
// Silent catch: doesn't log, doesn't rethrow, doesn't show user error
|
|
2234
|
+
if (!logsError && !rethrowsError && !showsUserError && !hasConditionalReturn) {
|
|
2235
|
+
const loc = pathNode.node.loc;
|
|
2236
|
+
findings.push({
|
|
2237
|
+
id: stableId("F_SILENT_CATCH", `${fileRel}:${loc?.start?.line || 0}`),
|
|
2238
|
+
severity: "WARN",
|
|
2239
|
+
category: "SilentCatch",
|
|
2240
|
+
title: "Catch block swallows error silently",
|
|
2241
|
+
why: "Errors are caught but not logged, reported, or shown to users. This makes debugging nearly impossible and hides failures.",
|
|
2242
|
+
confidence: "med",
|
|
2243
|
+
evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Silent catch block")].filter(Boolean),
|
|
2244
|
+
fixHints: [
|
|
2245
|
+
`Add console.error(${catchParam}) or a logger call.`,
|
|
2246
|
+
"Show user-friendly error message (toast, alert, etc.).",
|
|
2247
|
+
"Re-throw the error if it should propagate.",
|
|
2248
|
+
"If intentionally ignoring, add a comment explaining why.",
|
|
2249
|
+
],
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
} catch {
|
|
2213
2255
|
continue;
|
|
2214
2256
|
}
|
|
2215
2257
|
}
|
|
@@ -2218,176 +2260,199 @@ function findErrorHandlingIssues(repoRoot) {
|
|
|
2218
2260
|
}
|
|
2219
2261
|
|
|
2220
2262
|
/* ============================================================================
|
|
2221
|
-
*
|
|
2222
|
-
*
|
|
2263
|
+
* METHOD MISMATCH DETECTOR
|
|
2264
|
+
* Finds client-side GET requests to POST-only endpoints and vice versa
|
|
2223
2265
|
* ========================================================================== */
|
|
2224
2266
|
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2227
|
-
const findings = [];
|
|
2267
|
+
function findMethodMismatch(truthpack) {
|
|
2268
|
+
if (!truthpack?.routes) return [];
|
|
2228
2269
|
|
|
2229
|
-
|
|
2230
|
-
const
|
|
2231
|
-
|
|
2270
|
+
const findings = [];
|
|
2271
|
+
const serverRoutes = truthpack.routes.server || [];
|
|
2272
|
+
const clientRefs = truthpack.routes.clientRefs || [];
|
|
2232
2273
|
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2274
|
+
// Build a map of route -> allowed methods
|
|
2275
|
+
const routeMethodMap = new Map();
|
|
2276
|
+
for (const route of serverRoutes) {
|
|
2277
|
+
const key = normalizeRoutePath(route.path);
|
|
2278
|
+
if (!routeMethodMap.has(key)) {
|
|
2279
|
+
routeMethodMap.set(key, new Set());
|
|
2280
|
+
}
|
|
2281
|
+
if (route.method) {
|
|
2282
|
+
routeMethodMap.get(key).add(route.method.toUpperCase());
|
|
2283
|
+
}
|
|
2241
2284
|
}
|
|
2242
2285
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
why: "Database anti-patterns cause performance issues, data integrity problems, and security vulnerabilities.",
|
|
2275
|
-
confidence: finding.confidence,
|
|
2276
|
-
evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
|
|
2277
|
-
fixHints: [finding.fixHint].filter(Boolean),
|
|
2278
|
-
});
|
|
2279
|
-
}
|
|
2280
|
-
} catch (err) {
|
|
2281
|
-
continue;
|
|
2286
|
+
// Check client refs for method mismatches
|
|
2287
|
+
for (const ref of clientRefs) {
|
|
2288
|
+
if (!ref.method || !ref.path) continue;
|
|
2289
|
+
|
|
2290
|
+
const clientMethod = ref.method.toUpperCase();
|
|
2291
|
+
const normalizedPath = normalizeRoutePath(ref.path);
|
|
2292
|
+
|
|
2293
|
+
// Find matching server route
|
|
2294
|
+
const allowedMethods = routeMethodMap.get(normalizedPath);
|
|
2295
|
+
|
|
2296
|
+
if (allowedMethods && allowedMethods.size > 0 && !allowedMethods.has(clientMethod)) {
|
|
2297
|
+
// Method mismatch found
|
|
2298
|
+
const allowed = Array.from(allowedMethods).join(", ");
|
|
2299
|
+
findings.push({
|
|
2300
|
+
id: stableId("F_METHOD_MISMATCH", `${ref.file || "unknown"}:${ref.line || 0}:${ref.path}`),
|
|
2301
|
+
severity: "BLOCK",
|
|
2302
|
+
category: "MethodMismatch",
|
|
2303
|
+
title: `${clientMethod} request to ${allowed}-only endpoint: ${ref.path}`,
|
|
2304
|
+
why: `Client makes ${clientMethod} request but server only accepts ${allowed}. This will fail with 405 Method Not Allowed.`,
|
|
2305
|
+
confidence: "high",
|
|
2306
|
+
evidence: ref.file ? [{
|
|
2307
|
+
file: ref.file,
|
|
2308
|
+
line: ref.line,
|
|
2309
|
+
reason: `Client ${clientMethod} to ${allowed}-only route`,
|
|
2310
|
+
}] : [],
|
|
2311
|
+
fixHints: [
|
|
2312
|
+
`Change client request method from ${clientMethod} to ${allowed}.`,
|
|
2313
|
+
`Add ${clientMethod} handler to the server route.`,
|
|
2314
|
+
"Verify the API contract matches documentation.",
|
|
2315
|
+
],
|
|
2316
|
+
});
|
|
2282
2317
|
}
|
|
2283
2318
|
}
|
|
2284
|
-
|
|
2319
|
+
|
|
2285
2320
|
return findings;
|
|
2286
2321
|
}
|
|
2287
2322
|
|
|
2323
|
+
// Helper to normalize route paths for comparison
|
|
2324
|
+
function normalizeRoutePath(routePath) {
|
|
2325
|
+
if (!routePath) return "";
|
|
2326
|
+
return routePath
|
|
2327
|
+
.replace(/\[([^\]]+)\]/g, ":$1") // [id] -> :id
|
|
2328
|
+
.replace(/\/+/g, "/") // multiple slashes -> single
|
|
2329
|
+
.replace(/\/$/, "") // remove trailing slash
|
|
2330
|
+
.toLowerCase();
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2288
2333
|
/* ============================================================================
|
|
2289
|
-
*
|
|
2290
|
-
*
|
|
2334
|
+
* DEAD UI DETECTOR (Enhanced)
|
|
2335
|
+
* Finds buttons, forms, and links that do nothing
|
|
2291
2336
|
* ========================================================================== */
|
|
2292
2337
|
|
|
2293
|
-
function
|
|
2294
|
-
const engines = require("./engines/vibecheck-engines");
|
|
2295
|
-
if (!engines.analyzeAsyncPatterns) return [];
|
|
2296
|
-
|
|
2338
|
+
function findDeadUI(repoRoot) {
|
|
2297
2339
|
const findings = [];
|
|
2298
|
-
const files = fg.sync(["**/*.{
|
|
2340
|
+
const files = fg.sync(["**/*.{tsx,jsx}"], {
|
|
2299
2341
|
cwd: repoRoot,
|
|
2300
2342
|
absolute: true,
|
|
2301
2343
|
ignore: STANDARD_IGNORE_PATTERNS,
|
|
2302
2344
|
});
|
|
2303
2345
|
|
|
2304
2346
|
for (const fileAbs of files) {
|
|
2347
|
+
const code = readFileCached(fileAbs);
|
|
2348
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
2349
|
+
|
|
2350
|
+
// Fast path: skip files without interactive elements
|
|
2351
|
+
if (!/<(button|Button|form|Form|a|Link)\b/.test(code)) continue;
|
|
2352
|
+
|
|
2353
|
+
let ast;
|
|
2305
2354
|
try {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
const engineFindings = engines.analyzeAsyncPatterns(code, fileRel);
|
|
2310
|
-
|
|
2311
|
-
// Only include BLOCK and WARN severity
|
|
2312
|
-
for (const finding of engineFindings) {
|
|
2313
|
-
if (finding.severity === "INFO") continue;
|
|
2314
|
-
|
|
2315
|
-
findings.push({
|
|
2316
|
-
id: stableId("F_ASYNC", `${fileRel}:${finding.type}:${finding.line}`),
|
|
2317
|
-
severity: finding.severity,
|
|
2318
|
-
category: finding.category || "AsyncPatterns",
|
|
2319
|
-
title: finding.title,
|
|
2320
|
-
message: finding.message,
|
|
2321
|
-
file: finding.file,
|
|
2322
|
-
line: finding.line,
|
|
2323
|
-
why: "Async anti-patterns cause race conditions, memory leaks, and hard-to-debug failures.",
|
|
2324
|
-
confidence: finding.confidence,
|
|
2325
|
-
evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
|
|
2326
|
-
fixHints: [finding.fixHint].filter(Boolean),
|
|
2327
|
-
});
|
|
2328
|
-
}
|
|
2329
|
-
} catch (err) {
|
|
2355
|
+
ast = parseFile(code, fileAbs);
|
|
2356
|
+
} catch {
|
|
2330
2357
|
continue;
|
|
2331
2358
|
}
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
return findings;
|
|
2335
|
-
}
|
|
2336
2359
|
|
|
2337
|
-
/* ============================================================================
|
|
2338
|
-
* BUNDLE SIZE ANALYZER
|
|
2339
|
-
* Only runs on client-side code to detect heavy imports
|
|
2340
|
-
* ========================================================================== */
|
|
2341
|
-
|
|
2342
|
-
function findBundleSizeIssues(repoRoot) {
|
|
2343
|
-
const engines = require("./engines/vibecheck-engines");
|
|
2344
|
-
if (!engines.bundleSizeEngine?.analyzeBundleSize) return [];
|
|
2345
|
-
|
|
2346
|
-
const findings = [];
|
|
2347
|
-
|
|
2348
|
-
// Focus on likely client-side code paths
|
|
2349
|
-
const files = fg.sync([
|
|
2350
|
-
"**/app/**/*.{ts,tsx,js,jsx}",
|
|
2351
|
-
"**/pages/**/*.{ts,tsx,js,jsx}",
|
|
2352
|
-
"**/src/**/*.{ts,tsx,js,jsx}",
|
|
2353
|
-
"**/components/**/*.{ts,tsx,js,jsx}",
|
|
2354
|
-
], {
|
|
2355
|
-
cwd: repoRoot,
|
|
2356
|
-
absolute: true,
|
|
2357
|
-
ignore: [
|
|
2358
|
-
...STANDARD_IGNORE_PATTERNS,
|
|
2359
|
-
"**/api/**", // Skip API routes (server-side)
|
|
2360
|
-
"**/server/**",
|
|
2361
|
-
"**/lib/server/**",
|
|
2362
|
-
],
|
|
2363
|
-
});
|
|
2364
|
-
|
|
2365
|
-
for (const fileAbs of files) {
|
|
2366
2360
|
try {
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2361
|
+
traverse(ast, {
|
|
2362
|
+
JSXElement(pathNode) {
|
|
2363
|
+
const opening = pathNode.node.openingElement;
|
|
2364
|
+
const tagName = opening.name?.name || opening.name?.property?.name || "";
|
|
2365
|
+
|
|
2366
|
+
// Check for buttons, forms, links
|
|
2367
|
+
if (!/^(button|Button|form|Form|a|Link)$/i.test(tagName)) return;
|
|
2368
|
+
|
|
2369
|
+
const attrs = opening.attributes || [];
|
|
2370
|
+
let hasOnClick = false;
|
|
2371
|
+
let hasOnSubmit = false;
|
|
2372
|
+
let hasHref = false;
|
|
2373
|
+
let hasAction = false;
|
|
2374
|
+
let hasType = false;
|
|
2375
|
+
let isDisabled = false;
|
|
2376
|
+
|
|
2377
|
+
for (const attr of attrs) {
|
|
2378
|
+
if (!t.isJSXAttribute(attr)) continue;
|
|
2379
|
+
const name = attr.name?.name || "";
|
|
2380
|
+
|
|
2381
|
+
if (name === "onClick" || name === "onPress") hasOnClick = true;
|
|
2382
|
+
if (name === "onSubmit") hasOnSubmit = true;
|
|
2383
|
+
if (name === "href" || name === "to") hasHref = true;
|
|
2384
|
+
if (name === "action") hasAction = true;
|
|
2385
|
+
if (name === "type") hasType = true;
|
|
2386
|
+
if (name === "disabled") isDisabled = true;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// Button without onClick (unless it's a submit button or disabled)
|
|
2390
|
+
if (/button/i.test(tagName) && !hasOnClick && !isDisabled) {
|
|
2391
|
+
const isSubmitType = attrs.some(a =>
|
|
2392
|
+
t.isJSXAttribute(a) &&
|
|
2393
|
+
a.name?.name === "type" &&
|
|
2394
|
+
t.isStringLiteral(a.value) &&
|
|
2395
|
+
a.value.value === "submit"
|
|
2396
|
+
);
|
|
2397
|
+
|
|
2398
|
+
if (!isSubmitType) {
|
|
2399
|
+
const loc = opening.loc;
|
|
2400
|
+
findings.push({
|
|
2401
|
+
id: stableId("F_DEAD_UI", `${fileRel}:${loc?.start?.line || 0}:button`),
|
|
2402
|
+
severity: "WARN",
|
|
2403
|
+
category: "DeadUI",
|
|
2404
|
+
title: "Button without onClick handler",
|
|
2405
|
+
why: "This button does nothing when clicked. Users expect buttons to perform actions.",
|
|
2406
|
+
confidence: "med",
|
|
2407
|
+
evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Dead button")].filter(Boolean),
|
|
2408
|
+
fixHints: [
|
|
2409
|
+
"<Button onClick={() => handleAction()}>Click Me</Button>",
|
|
2410
|
+
"For submit: <Button type=\"submit\">Submit</Button>",
|
|
2411
|
+
"For disabled: <Button disabled>Coming Soon</Button>",
|
|
2412
|
+
],
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// Form without onSubmit or action
|
|
2418
|
+
if (/form/i.test(tagName) && !hasOnSubmit && !hasAction) {
|
|
2419
|
+
const loc = opening.loc;
|
|
2420
|
+
findings.push({
|
|
2421
|
+
id: stableId("F_DEAD_UI", `${fileRel}:${loc?.start?.line || 0}:form`),
|
|
2422
|
+
severity: "WARN",
|
|
2423
|
+
category: "DeadUI",
|
|
2424
|
+
title: "Form without onSubmit or action",
|
|
2425
|
+
why: "This form does nothing when submitted. Form data won't be processed.",
|
|
2426
|
+
confidence: "med",
|
|
2427
|
+
evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Dead form")].filter(Boolean),
|
|
2428
|
+
fixHints: [
|
|
2429
|
+
"Add an onSubmit handler to process form data.",
|
|
2430
|
+
"Or add an action attribute for server-side submission.",
|
|
2431
|
+
],
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// Link without href
|
|
2436
|
+
if (/^(a|Link)$/i.test(tagName) && !hasHref) {
|
|
2437
|
+
const loc = opening.loc;
|
|
2438
|
+
findings.push({
|
|
2439
|
+
id: stableId("F_DEAD_UI", `${fileRel}:${loc?.start?.line || 0}:link`),
|
|
2440
|
+
severity: "WARN",
|
|
2441
|
+
category: "DeadUI",
|
|
2442
|
+
title: "Link without href or to prop",
|
|
2443
|
+
why: "This link goes nowhere. Users expect links to navigate somewhere.",
|
|
2444
|
+
confidence: "med",
|
|
2445
|
+
evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Dead link")].filter(Boolean),
|
|
2446
|
+
fixHints: [
|
|
2447
|
+
"Add href prop with the target URL.",
|
|
2448
|
+
"If using Next.js Link, ensure href is provided.",
|
|
2449
|
+
"If it's a button styled as link, use a button element instead.",
|
|
2450
|
+
],
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
} catch {
|
|
2391
2456
|
continue;
|
|
2392
2457
|
}
|
|
2393
2458
|
}
|
|
@@ -2395,123 +2460,14 @@ function findBundleSizeIssues(repoRoot) {
|
|
|
2395
2460
|
return findings;
|
|
2396
2461
|
}
|
|
2397
2462
|
|
|
2398
|
-
/* ============================================================================
|
|
2399
|
-
* V6: BULLETPROOF FINDINGS FILTER
|
|
2400
|
-
* Applies noise reduction and false positive prevention to any findings array
|
|
2401
|
-
* ========================================================================== */
|
|
2402
|
-
|
|
2403
|
-
/**
|
|
2404
|
-
* Apply bulletproof filtering to findings
|
|
2405
|
-
* @param {Array} findings - Raw findings from analyzers
|
|
2406
|
-
* @param {Object} options - Configuration options
|
|
2407
|
-
* @returns {Object} { findings, stats }
|
|
2408
|
-
*/
|
|
2409
|
-
function bulletproofFindings(findings, options = {}) {
|
|
2410
|
-
const {
|
|
2411
|
-
projectPath = ".",
|
|
2412
|
-
projectStats = {},
|
|
2413
|
-
config = {},
|
|
2414
|
-
minConfidence = 0.4,
|
|
2415
|
-
minQualityScore = 0.35,
|
|
2416
|
-
verbose = false,
|
|
2417
|
-
} = options;
|
|
2418
|
-
|
|
2419
|
-
let processed = [...findings];
|
|
2420
|
-
const stats = {
|
|
2421
|
-
input: findings.length,
|
|
2422
|
-
afterFalsePositiveFilter: 0,
|
|
2423
|
-
afterNoiseReduction: 0,
|
|
2424
|
-
output: 0,
|
|
2425
|
-
};
|
|
2426
|
-
|
|
2427
|
-
// Step 1: Filter false positives
|
|
2428
|
-
if (falsePositivePrevention?.filterFalsePositives) {
|
|
2429
|
-
const fpResult = falsePositivePrevention.filterFalsePositives(processed, {
|
|
2430
|
-
projectPath,
|
|
2431
|
-
minConfidence,
|
|
2432
|
-
});
|
|
2433
|
-
processed = fpResult.findings;
|
|
2434
|
-
stats.afterFalsePositiveFilter = processed.length;
|
|
2435
|
-
|
|
2436
|
-
if (verbose && fpResult.removed.length > 0) {
|
|
2437
|
-
console.log(` [FP Filter] Removed ${fpResult.removed.length} false positives`);
|
|
2438
|
-
}
|
|
2439
|
-
} else {
|
|
2440
|
-
stats.afterFalsePositiveFilter = processed.length;
|
|
2441
|
-
}
|
|
2442
|
-
|
|
2443
|
-
// Step 2: Apply noise reduction
|
|
2444
|
-
if (noiseReduction?.reduceNoise) {
|
|
2445
|
-
const nrResult = noiseReduction.reduceNoise(processed, {
|
|
2446
|
-
projectStats,
|
|
2447
|
-
config,
|
|
2448
|
-
minQualityScore,
|
|
2449
|
-
verbose,
|
|
2450
|
-
});
|
|
2451
|
-
processed = nrResult.findings;
|
|
2452
|
-
stats.afterNoiseReduction = processed.length;
|
|
2453
|
-
stats.noiseStats = nrResult.stats;
|
|
2454
|
-
} else {
|
|
2455
|
-
stats.afterNoiseReduction = processed.length;
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
stats.output = processed.length;
|
|
2459
|
-
stats.reduction = Math.round((1 - stats.output / Math.max(stats.input, 1)) * 100);
|
|
2460
|
-
|
|
2461
|
-
return { findings: processed, stats };
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
/**
|
|
2465
|
-
* Get project statistics for adaptive caps
|
|
2466
|
-
*/
|
|
2467
|
-
function getProjectStats(projectPath) {
|
|
2468
|
-
let fileCount = 0;
|
|
2469
|
-
let lineCount = 0;
|
|
2470
|
-
|
|
2471
|
-
try {
|
|
2472
|
-
const files = fg.sync(["**/*.{ts,tsx,js,jsx,mjs,cjs}"], {
|
|
2473
|
-
cwd: projectPath,
|
|
2474
|
-
absolute: true,
|
|
2475
|
-
ignore: STANDARD_IGNORE_PATTERNS,
|
|
2476
|
-
});
|
|
2477
|
-
|
|
2478
|
-
fileCount = files.length;
|
|
2479
|
-
|
|
2480
|
-
// Sample line count from up to 100 files for performance
|
|
2481
|
-
const sampleFiles = files.slice(0, 100);
|
|
2482
|
-
let sampleLines = 0;
|
|
2483
|
-
for (const file of sampleFiles) {
|
|
2484
|
-
try {
|
|
2485
|
-
const content = fs.readFileSync(file, "utf8");
|
|
2486
|
-
sampleLines += content.split("\n").length;
|
|
2487
|
-
} catch {
|
|
2488
|
-
// Ignore errors
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// Extrapolate line count
|
|
2493
|
-
if (sampleFiles.length > 0) {
|
|
2494
|
-
lineCount = Math.round((sampleLines / sampleFiles.length) * fileCount);
|
|
2495
|
-
}
|
|
2496
|
-
} catch {
|
|
2497
|
-
// Ignore errors
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
return { fileCount, lineCount };
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
2463
|
module.exports = {
|
|
2504
|
-
// V6: Bulletproof filtering (apply to any findings array)
|
|
2505
|
-
bulletproofFindings,
|
|
2506
|
-
getProjectStats,
|
|
2507
|
-
|
|
2508
2464
|
// V3: Cache management - call after scan completes to prevent memory leaks
|
|
2509
2465
|
clearFileCache,
|
|
2510
2466
|
|
|
2511
2467
|
// V3: Entropy helper - exported for testing/reuse
|
|
2512
2468
|
getShannonEntropy,
|
|
2513
2469
|
|
|
2514
|
-
//
|
|
2470
|
+
// Analyzers
|
|
2515
2471
|
findMissingRoutes,
|
|
2516
2472
|
findEnvGaps,
|
|
2517
2473
|
findFakeSuccess,
|
|
@@ -2519,8 +2475,6 @@ module.exports = {
|
|
|
2519
2475
|
findStripeWebhookViolations,
|
|
2520
2476
|
findPaidSurfaceNotEnforced,
|
|
2521
2477
|
findOwnerModeBypass,
|
|
2522
|
-
|
|
2523
|
-
// Code quality analyzers (mock, TODO, console, etc.)
|
|
2524
2478
|
findMockData,
|
|
2525
2479
|
findTodoFixme,
|
|
2526
2480
|
findConsoleLogs,
|
|
@@ -2529,22 +2483,18 @@ module.exports = {
|
|
|
2529
2483
|
findDeprecatedApis,
|
|
2530
2484
|
findEmptyCatch,
|
|
2531
2485
|
findUnsafeRegex,
|
|
2532
|
-
|
|
2533
|
-
// Enhanced analyzers (security, performance, quality)
|
|
2486
|
+
// Enhanced analyzers
|
|
2534
2487
|
findSecurityVulnerabilities,
|
|
2535
2488
|
findPerformanceIssues,
|
|
2536
2489
|
findCodeQualityIssues,
|
|
2537
|
-
|
|
2538
2490
|
// Advanced analyzers
|
|
2539
2491
|
findCrossFileIssues,
|
|
2540
2492
|
findTypeSafetyIssues,
|
|
2541
2493
|
findAccessibilityIssues,
|
|
2542
2494
|
findAPIConsistencyIssues,
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
findAsyncPatternIssues, // Promise/async anti-patterns
|
|
2549
|
-
findBundleSizeIssues, // Heavy imports, bundle bloat
|
|
2495
|
+
// NEW: AI Hallucination Detectors
|
|
2496
|
+
findOptimisticNoRollback,
|
|
2497
|
+
findSilentCatch,
|
|
2498
|
+
findMethodMismatch,
|
|
2499
|
+
findDeadUI,
|
|
2550
2500
|
};
|