@quantracode/vibecheck 0.0.1 → 0.0.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/README.md +6 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +7902 -8
- package/package.json +13 -7
- package/dist/__tests__/cli.test.d.ts +0 -2
- package/dist/__tests__/cli.test.d.ts.map +0 -1
- package/dist/__tests__/cli.test.js +0 -243
- package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +0 -36
- package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +0 -28
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +0 -4
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +0 -1
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +0 -6
- package/dist/__tests__/scanners/env-config.test.d.ts +0 -2
- package/dist/__tests__/scanners/env-config.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/env-config.test.js +0 -142
- package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +0 -2
- package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/nextjs-middleware.test.js +0 -193
- package/dist/__tests__/scanners/scanner-packs.test.d.ts +0 -2
- package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/scanner-packs.test.js +0 -126
- package/dist/__tests__/scanners/unused-security-imports.test.d.ts +0 -2
- package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/unused-security-imports.test.js +0 -145
- package/dist/commands/demo-artifact.d.ts +0 -7
- package/dist/commands/demo-artifact.d.ts.map +0 -1
- package/dist/commands/demo-artifact.js +0 -322
- package/dist/commands/evaluate.d.ts +0 -30
- package/dist/commands/evaluate.d.ts.map +0 -1
- package/dist/commands/evaluate.js +0 -258
- package/dist/commands/explain.d.ts +0 -12
- package/dist/commands/explain.d.ts.map +0 -1
- package/dist/commands/explain.js +0 -214
- package/dist/commands/index.d.ts +0 -7
- package/dist/commands/index.d.ts.map +0 -1
- package/dist/commands/index.js +0 -6
- package/dist/commands/intent.d.ts +0 -21
- package/dist/commands/intent.d.ts.map +0 -1
- package/dist/commands/intent.js +0 -192
- package/dist/commands/scan.d.ts +0 -44
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js +0 -497
- package/dist/commands/waivers.d.ts +0 -30
- package/dist/commands/waivers.d.ts.map +0 -1
- package/dist/commands/waivers.js +0 -249
- package/dist/index.d.ts.map +0 -1
- package/dist/phase3/index.d.ts +0 -11
- package/dist/phase3/index.d.ts.map +0 -1
- package/dist/phase3/index.js +0 -12
- package/dist/phase3/intent-miner.d.ts +0 -32
- package/dist/phase3/intent-miner.d.ts.map +0 -1
- package/dist/phase3/intent-miner.js +0 -323
- package/dist/phase3/proof-trace-builder.d.ts +0 -42
- package/dist/phase3/proof-trace-builder.d.ts.map +0 -1
- package/dist/phase3/proof-trace-builder.js +0 -441
- package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +0 -15
- package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +0 -1
- package/dist/phase3/scanners/auth-by-ui-server-gap.js +0 -237
- package/dist/phase3/scanners/comment-claim-unproven.d.ts +0 -14
- package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +0 -1
- package/dist/phase3/scanners/comment-claim-unproven.js +0 -161
- package/dist/phase3/scanners/index.d.ts +0 -31
- package/dist/phase3/scanners/index.d.ts.map +0 -1
- package/dist/phase3/scanners/index.js +0 -40
- package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +0 -14
- package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +0 -1
- package/dist/phase3/scanners/middleware-assumed-not-matching.js +0 -172
- package/dist/phase3/scanners/validation-claimed-missing.d.ts +0 -15
- package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +0 -1
- package/dist/phase3/scanners/validation-claimed-missing.js +0 -204
- package/dist/scanners/abuse/compute-abuse.d.ts +0 -20
- package/dist/scanners/abuse/compute-abuse.d.ts.map +0 -1
- package/dist/scanners/abuse/compute-abuse.js +0 -509
- package/dist/scanners/abuse/index.d.ts +0 -12
- package/dist/scanners/abuse/index.d.ts.map +0 -1
- package/dist/scanners/abuse/index.js +0 -15
- package/dist/scanners/auth/index.d.ts +0 -5
- package/dist/scanners/auth/index.d.ts.map +0 -1
- package/dist/scanners/auth/index.js +0 -10
- package/dist/scanners/auth/middleware-gap.d.ts +0 -22
- package/dist/scanners/auth/middleware-gap.d.ts.map +0 -1
- package/dist/scanners/auth/middleware-gap.js +0 -203
- package/dist/scanners/auth/unprotected-api-route.d.ts +0 -12
- package/dist/scanners/auth/unprotected-api-route.d.ts.map +0 -1
- package/dist/scanners/auth/unprotected-api-route.js +0 -126
- package/dist/scanners/config/index.d.ts +0 -5
- package/dist/scanners/config/index.d.ts.map +0 -1
- package/dist/scanners/config/index.js +0 -10
- package/dist/scanners/config/insecure-defaults.d.ts +0 -12
- package/dist/scanners/config/insecure-defaults.d.ts.map +0 -1
- package/dist/scanners/config/insecure-defaults.js +0 -77
- package/dist/scanners/config/undocumented-env.d.ts +0 -24
- package/dist/scanners/config/undocumented-env.d.ts.map +0 -1
- package/dist/scanners/config/undocumented-env.js +0 -159
- package/dist/scanners/crypto/index.d.ts +0 -6
- package/dist/scanners/crypto/index.d.ts.map +0 -1
- package/dist/scanners/crypto/index.js +0 -11
- package/dist/scanners/crypto/jwt-decode-unverified.d.ts +0 -14
- package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +0 -1
- package/dist/scanners/crypto/jwt-decode-unverified.js +0 -87
- package/dist/scanners/crypto/math-random-tokens.d.ts +0 -13
- package/dist/scanners/crypto/math-random-tokens.d.ts.map +0 -1
- package/dist/scanners/crypto/math-random-tokens.js +0 -80
- package/dist/scanners/crypto/weak-hashing.d.ts +0 -11
- package/dist/scanners/crypto/weak-hashing.d.ts.map +0 -1
- package/dist/scanners/crypto/weak-hashing.js +0 -95
- package/dist/scanners/env-config.d.ts +0 -24
- package/dist/scanners/env-config.d.ts.map +0 -1
- package/dist/scanners/env-config.js +0 -164
- package/dist/scanners/hallucinations/index.d.ts +0 -4
- package/dist/scanners/hallucinations/index.d.ts.map +0 -1
- package/dist/scanners/hallucinations/index.js +0 -8
- package/dist/scanners/hallucinations/unused-security-imports.d.ts +0 -36
- package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +0 -1
- package/dist/scanners/hallucinations/unused-security-imports.js +0 -309
- package/dist/scanners/helpers/ast-helpers.d.ts +0 -6
- package/dist/scanners/helpers/ast-helpers.d.ts.map +0 -1
- package/dist/scanners/helpers/ast-helpers.js +0 -945
- package/dist/scanners/helpers/context-builder.d.ts +0 -17
- package/dist/scanners/helpers/context-builder.d.ts.map +0 -1
- package/dist/scanners/helpers/context-builder.js +0 -148
- package/dist/scanners/helpers/index.d.ts +0 -3
- package/dist/scanners/helpers/index.d.ts.map +0 -1
- package/dist/scanners/helpers/index.js +0 -2
- package/dist/scanners/index.d.ts +0 -30
- package/dist/scanners/index.d.ts.map +0 -1
- package/dist/scanners/index.js +0 -102
- package/dist/scanners/middleware/index.d.ts +0 -4
- package/dist/scanners/middleware/index.d.ts.map +0 -1
- package/dist/scanners/middleware/index.js +0 -7
- package/dist/scanners/middleware/missing-rate-limit.d.ts +0 -13
- package/dist/scanners/middleware/missing-rate-limit.d.ts.map +0 -1
- package/dist/scanners/middleware/missing-rate-limit.js +0 -140
- package/dist/scanners/network/cors-misconfiguration.d.ts +0 -14
- package/dist/scanners/network/cors-misconfiguration.d.ts.map +0 -1
- package/dist/scanners/network/cors-misconfiguration.js +0 -89
- package/dist/scanners/network/index.d.ts +0 -7
- package/dist/scanners/network/index.d.ts.map +0 -1
- package/dist/scanners/network/index.js +0 -18
- package/dist/scanners/network/missing-timeout.d.ts +0 -15
- package/dist/scanners/network/missing-timeout.d.ts.map +0 -1
- package/dist/scanners/network/missing-timeout.js +0 -93
- package/dist/scanners/network/open-redirect.d.ts +0 -15
- package/dist/scanners/network/open-redirect.d.ts.map +0 -1
- package/dist/scanners/network/open-redirect.js +0 -88
- package/dist/scanners/network/ssrf-prone-fetch.d.ts +0 -12
- package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +0 -1
- package/dist/scanners/network/ssrf-prone-fetch.js +0 -90
- package/dist/scanners/nextjs-middleware.d.ts +0 -26
- package/dist/scanners/nextjs-middleware.d.ts.map +0 -1
- package/dist/scanners/nextjs-middleware.js +0 -246
- package/dist/scanners/privacy/debug-flags.d.ts +0 -13
- package/dist/scanners/privacy/debug-flags.d.ts.map +0 -1
- package/dist/scanners/privacy/debug-flags.js +0 -124
- package/dist/scanners/privacy/index.d.ts +0 -6
- package/dist/scanners/privacy/index.d.ts.map +0 -1
- package/dist/scanners/privacy/index.js +0 -11
- package/dist/scanners/privacy/over-broad-response.d.ts +0 -15
- package/dist/scanners/privacy/over-broad-response.d.ts.map +0 -1
- package/dist/scanners/privacy/over-broad-response.js +0 -109
- package/dist/scanners/privacy/sensitive-logging.d.ts +0 -11
- package/dist/scanners/privacy/sensitive-logging.d.ts.map +0 -1
- package/dist/scanners/privacy/sensitive-logging.js +0 -78
- package/dist/scanners/types.d.ts +0 -456
- package/dist/scanners/types.d.ts.map +0 -1
- package/dist/scanners/types.js +0 -16
- package/dist/scanners/unused-security-imports.d.ts +0 -34
- package/dist/scanners/unused-security-imports.d.ts.map +0 -1
- package/dist/scanners/unused-security-imports.js +0 -206
- package/dist/scanners/uploads/index.d.ts +0 -5
- package/dist/scanners/uploads/index.d.ts.map +0 -1
- package/dist/scanners/uploads/index.js +0 -9
- package/dist/scanners/uploads/missing-constraints.d.ts +0 -15
- package/dist/scanners/uploads/missing-constraints.d.ts.map +0 -1
- package/dist/scanners/uploads/missing-constraints.js +0 -109
- package/dist/scanners/uploads/public-path.d.ts +0 -11
- package/dist/scanners/uploads/public-path.d.ts.map +0 -1
- package/dist/scanners/uploads/public-path.js +0 -87
- package/dist/scanners/validation/client-side-only.d.ts +0 -14
- package/dist/scanners/validation/client-side-only.d.ts.map +0 -1
- package/dist/scanners/validation/client-side-only.js +0 -140
- package/dist/scanners/validation/ignored-validation.d.ts +0 -12
- package/dist/scanners/validation/ignored-validation.d.ts.map +0 -1
- package/dist/scanners/validation/ignored-validation.js +0 -119
- package/dist/scanners/validation/index.d.ts +0 -5
- package/dist/scanners/validation/index.d.ts.map +0 -1
- package/dist/scanners/validation/index.js +0 -9
- package/dist/utils/exclude-patterns.d.ts +0 -35
- package/dist/utils/exclude-patterns.d.ts.map +0 -1
- package/dist/utils/exclude-patterns.js +0 -78
- package/dist/utils/file-utils.d.ts +0 -37
- package/dist/utils/file-utils.d.ts.map +0 -1
- package/dist/utils/file-utils.js +0 -77
- package/dist/utils/fingerprint.d.ts +0 -25
- package/dist/utils/fingerprint.d.ts.map +0 -1
- package/dist/utils/fingerprint.js +0 -28
- package/dist/utils/git-info.d.ts +0 -14
- package/dist/utils/git-info.d.ts.map +0 -1
- package/dist/utils/git-info.js +0 -55
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -3
- package/dist/utils/progress.d.ts +0 -42
- package/dist/utils/progress.d.ts.map +0 -1
- package/dist/utils/progress.js +0 -165
- package/dist/utils/sarif-formatter.d.ts +0 -92
- package/dist/utils/sarif-formatter.d.ts.map +0 -1
- package/dist/utils/sarif-formatter.js +0 -172
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VC-AUTH-010: Auth-by-UI with Server Gap
|
|
3
|
-
*
|
|
4
|
-
* Detects patterns where authentication appears to be enforced only
|
|
5
|
-
* on the client side (via useSession, conditionally rendering UI, etc.)
|
|
6
|
-
* but the corresponding API routes lack server-side auth checks.
|
|
7
|
-
*
|
|
8
|
-
* Severity: Critical
|
|
9
|
-
* Category: auth
|
|
10
|
-
* Confidence: 0.85
|
|
11
|
-
*/
|
|
12
|
-
import crypto from "node:crypto";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
import { buildRouteMap, buildAllProofTraces } from "../proof-trace-builder.js";
|
|
15
|
-
const RULE_ID = "VC-AUTH-010";
|
|
16
|
-
/**
|
|
17
|
-
* Client-side auth patterns that suggest UI-level protection
|
|
18
|
-
*/
|
|
19
|
-
const CLIENT_AUTH_PATTERNS = [
|
|
20
|
-
// React/Next.js session hooks
|
|
21
|
-
/\buseSession\s*\(/,
|
|
22
|
-
/\buseAuth\s*\(/,
|
|
23
|
-
/\buseUser\s*\(/,
|
|
24
|
-
/\bsession\s*&&/,
|
|
25
|
-
/\bsession\s*\?/,
|
|
26
|
-
/\bisAuthenticated\s*&&/,
|
|
27
|
-
/\bisLoggedIn\s*&&/,
|
|
28
|
-
// Conditional rendering based on auth
|
|
29
|
-
/{\s*session\s*&&/,
|
|
30
|
-
/{\s*user\s*&&/,
|
|
31
|
-
/{\s*isAuthenticated\s*&&/,
|
|
32
|
-
// Auth redirects in client components
|
|
33
|
-
/redirect\([^)]*login/i,
|
|
34
|
-
/router\.push\([^)]*login/i,
|
|
35
|
-
/useRouter.*login/i,
|
|
36
|
-
];
|
|
37
|
-
/**
|
|
38
|
-
* Patterns indicating the component makes API calls
|
|
39
|
-
*/
|
|
40
|
-
const API_CALL_PATTERNS = [
|
|
41
|
-
// Fetch to API routes
|
|
42
|
-
/fetch\s*\(\s*['"`]\/api\//,
|
|
43
|
-
/fetch\s*\(\s*['"`]\.\.?\/api\//,
|
|
44
|
-
// Axios to API routes
|
|
45
|
-
/axios\.[a-z]+\s*\(\s*['"`]\/api\//,
|
|
46
|
-
// Form actions
|
|
47
|
-
/action\s*=\s*['"`]\/api\//,
|
|
48
|
-
// SWR/React Query to API routes
|
|
49
|
-
/useSWR\s*\(\s*['"`]\/api\//,
|
|
50
|
-
/useQuery\s*\([^)]*['"`]\/api\//,
|
|
51
|
-
];
|
|
52
|
-
export async function scanAuthByUiServerGap(ctx) {
|
|
53
|
-
const findings = [];
|
|
54
|
-
// Build route map and proof traces
|
|
55
|
-
const routes = buildRouteMap(ctx);
|
|
56
|
-
const proofTraces = buildAllProofTraces(ctx, routes);
|
|
57
|
-
// Find state-changing routes without auth
|
|
58
|
-
const unprotectedRoutes = routes.filter((r) => {
|
|
59
|
-
if (!["POST", "PUT", "PATCH", "DELETE"].includes(r.method)) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
const trace = proofTraces.get(r.routeId);
|
|
63
|
-
return trace && !trace.authProven && !trace.middlewareCovered;
|
|
64
|
-
});
|
|
65
|
-
if (unprotectedRoutes.length === 0) {
|
|
66
|
-
return findings;
|
|
67
|
-
}
|
|
68
|
-
// Scan client components for UI-level auth patterns
|
|
69
|
-
const clientComponents = ctx.fileIndex.allSourceFiles.filter((f) => (f.endsWith(".tsx") || f.endsWith(".jsx")) &&
|
|
70
|
-
!f.includes("/api/") &&
|
|
71
|
-
!f.includes("route."));
|
|
72
|
-
for (const componentFile of clientComponents) {
|
|
73
|
-
const sourceFile = ctx.helpers.parseFile(componentFile);
|
|
74
|
-
if (!sourceFile)
|
|
75
|
-
continue;
|
|
76
|
-
const fullText = sourceFile.getFullText();
|
|
77
|
-
const relPath = path.relative(ctx.repoRoot, componentFile).replace(/\\/g, "/");
|
|
78
|
-
// Check for client-side auth patterns
|
|
79
|
-
const hasClientAuth = CLIENT_AUTH_PATTERNS.some((p) => p.test(fullText));
|
|
80
|
-
if (!hasClientAuth)
|
|
81
|
-
continue;
|
|
82
|
-
// Find API calls in this component
|
|
83
|
-
const apiCalls = findApiCalls(fullText, relPath, sourceFile);
|
|
84
|
-
if (apiCalls.length === 0)
|
|
85
|
-
continue;
|
|
86
|
-
// Check if any API calls target unprotected routes
|
|
87
|
-
for (const apiCall of apiCalls) {
|
|
88
|
-
const matchingUnprotected = findMatchingUnprotectedRoute(apiCall.path, apiCall.method, unprotectedRoutes);
|
|
89
|
-
if (matchingUnprotected) {
|
|
90
|
-
const clientAuthLocation = findClientAuthLocation(sourceFile, fullText);
|
|
91
|
-
findings.push({
|
|
92
|
-
id: `f-${crypto.randomUUID().slice(0, 8)}`,
|
|
93
|
-
severity: "critical",
|
|
94
|
-
confidence: 0.85,
|
|
95
|
-
category: "auth",
|
|
96
|
-
ruleId: RULE_ID,
|
|
97
|
-
title: `Client-side auth with unprotected server endpoint ${matchingUnprotected.method} ${matchingUnprotected.path}`,
|
|
98
|
-
description: generateDescription(relPath, matchingUnprotected, apiCall),
|
|
99
|
-
evidence: [
|
|
100
|
-
{
|
|
101
|
-
file: relPath,
|
|
102
|
-
startLine: clientAuthLocation.line,
|
|
103
|
-
endLine: clientAuthLocation.line,
|
|
104
|
-
snippet: clientAuthLocation.snippet,
|
|
105
|
-
label: "Client-side auth check",
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
file: relPath,
|
|
109
|
-
startLine: apiCall.line,
|
|
110
|
-
endLine: apiCall.line,
|
|
111
|
-
snippet: apiCall.snippet,
|
|
112
|
-
label: "API call to unprotected endpoint",
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
file: matchingUnprotected.file,
|
|
116
|
-
startLine: matchingUnprotected.startLine,
|
|
117
|
-
endLine: matchingUnprotected.endLine,
|
|
118
|
-
snippet: `export async function ${matchingUnprotected.method}(request: Request)`,
|
|
119
|
-
label: "Server endpoint without auth check",
|
|
120
|
-
},
|
|
121
|
-
],
|
|
122
|
-
remediation: {
|
|
123
|
-
recommendedFix: generateRemediation(matchingUnprotected),
|
|
124
|
-
},
|
|
125
|
-
fingerprint: generateFingerprint(relPath, matchingUnprotected),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return findings;
|
|
131
|
-
}
|
|
132
|
-
function findApiCalls(fullText, relPath, sourceFile) {
|
|
133
|
-
const calls = [];
|
|
134
|
-
// Find fetch calls to /api routes
|
|
135
|
-
const fetchMatches = fullText.matchAll(/fetch\s*\(\s*['"`](\/api\/[^'"`]+)['"`](?:,\s*\{[^}]*method:\s*['"`](\w+)['"`])?/g);
|
|
136
|
-
for (const match of fetchMatches) {
|
|
137
|
-
const pos = match.index || 0;
|
|
138
|
-
const line = sourceFile.getLineAndColumnAtPos(pos).line;
|
|
139
|
-
calls.push({
|
|
140
|
-
path: match[1],
|
|
141
|
-
method: match[2]?.toUpperCase() || "GET",
|
|
142
|
-
line,
|
|
143
|
-
snippet: match[0].slice(0, 80),
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
// Find axios calls
|
|
147
|
-
const axiosMatches = fullText.matchAll(/axios\.(\w+)\s*\(\s*['"`](\/api\/[^'"`]+)['"`]/g);
|
|
148
|
-
for (const match of axiosMatches) {
|
|
149
|
-
const pos = match.index || 0;
|
|
150
|
-
const line = sourceFile.getLineAndColumnAtPos(pos).line;
|
|
151
|
-
calls.push({
|
|
152
|
-
path: match[2],
|
|
153
|
-
method: match[1].toUpperCase(),
|
|
154
|
-
line,
|
|
155
|
-
snippet: match[0].slice(0, 80),
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
// Find form actions
|
|
159
|
-
const actionMatches = fullText.matchAll(/action\s*=\s*['"`](\/api\/[^'"`]+)['"`]/g);
|
|
160
|
-
for (const match of actionMatches) {
|
|
161
|
-
const pos = match.index || 0;
|
|
162
|
-
const line = sourceFile.getLineAndColumnAtPos(pos).line;
|
|
163
|
-
calls.push({
|
|
164
|
-
path: match[1],
|
|
165
|
-
method: "POST", // Form actions are typically POST
|
|
166
|
-
line,
|
|
167
|
-
snippet: match[0],
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
return calls;
|
|
171
|
-
}
|
|
172
|
-
function findMatchingUnprotectedRoute(apiPath, method, unprotectedRoutes) {
|
|
173
|
-
// Normalize the API path
|
|
174
|
-
const normalizedPath = apiPath.split("?")[0]; // Remove query string
|
|
175
|
-
for (const route of unprotectedRoutes) {
|
|
176
|
-
// Check method match
|
|
177
|
-
if (route.method !== method)
|
|
178
|
-
continue;
|
|
179
|
-
// Check path match (accounting for dynamic segments)
|
|
180
|
-
if (pathsMatch(normalizedPath, route.path)) {
|
|
181
|
-
return route;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return undefined;
|
|
185
|
-
}
|
|
186
|
-
function pathsMatch(clientPath, routePath) {
|
|
187
|
-
// Exact match
|
|
188
|
-
if (clientPath === routePath)
|
|
189
|
-
return true;
|
|
190
|
-
// Convert route path pattern to regex
|
|
191
|
-
const routePattern = routePath
|
|
192
|
-
.replace(/\[([^\]]+)\]/g, "[^/]+") // Replace [param] with regex
|
|
193
|
-
.replace(/\//g, "\\/"); // Escape slashes
|
|
194
|
-
try {
|
|
195
|
-
const regex = new RegExp(`^${routePattern}$`);
|
|
196
|
-
return regex.test(clientPath);
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function findClientAuthLocation(sourceFile, fullText) {
|
|
203
|
-
// Find the first client auth pattern
|
|
204
|
-
for (const pattern of CLIENT_AUTH_PATTERNS) {
|
|
205
|
-
const match = fullText.match(pattern);
|
|
206
|
-
if (match) {
|
|
207
|
-
const pos = match.index || 0;
|
|
208
|
-
const line = sourceFile.getLineAndColumnAtPos(pos).line;
|
|
209
|
-
return {
|
|
210
|
-
line,
|
|
211
|
-
snippet: match[0],
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return { line: 1, snippet: "Client-side auth" };
|
|
216
|
-
}
|
|
217
|
-
function generateDescription(componentFile, route, apiCall) {
|
|
218
|
-
return (`The component ${componentFile} uses client-side authentication checks ` +
|
|
219
|
-
`(useSession, session &&, etc.) before calling the API endpoint ` +
|
|
220
|
-
`${route.method} ${route.path}. However, this endpoint lacks server-side ` +
|
|
221
|
-
`authentication verification. An attacker can bypass the UI and directly ` +
|
|
222
|
-
`call the API endpoint without authentication, potentially gaining ` +
|
|
223
|
-
`unauthorized access to sensitive operations.`);
|
|
224
|
-
}
|
|
225
|
-
function generateRemediation(route) {
|
|
226
|
-
return (`Add server-side authentication check to the ${route.method} ${route.path} handler. ` +
|
|
227
|
-
`Example:\n` +
|
|
228
|
-
`const session = await getServerSession();\n` +
|
|
229
|
-
`if (!session) {\n` +
|
|
230
|
-
` return Response.json({ error: "Unauthorized" }, { status: 401 });\n` +
|
|
231
|
-
`}\n\n` +
|
|
232
|
-
`Client-side auth checks are for UX only and must never be the sole protection mechanism.`);
|
|
233
|
-
}
|
|
234
|
-
function generateFingerprint(componentFile, route) {
|
|
235
|
-
const data = `${RULE_ID}:${componentFile}:${route.file}:${route.method}`;
|
|
236
|
-
return `sha256:${crypto.createHash("sha256").update(data).digest("hex")}`;
|
|
237
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VC-HALL-010: Comment Claims Protection But Unproven
|
|
3
|
-
*
|
|
4
|
-
* Detects comments that claim security protection but the actual
|
|
5
|
-
* implementation doesn't prove it.
|
|
6
|
-
*
|
|
7
|
-
* Severity: Medium
|
|
8
|
-
* Category: hallucinations
|
|
9
|
-
* Confidence: 0.75
|
|
10
|
-
*/
|
|
11
|
-
import type { Finding } from "@vibecheck/schema";
|
|
12
|
-
import type { ScanContext } from "../../scanners/types.js";
|
|
13
|
-
export declare function scanCommentClaimUnproven(ctx: ScanContext): Promise<Finding[]>;
|
|
14
|
-
//# sourceMappingURL=comment-claim-unproven.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"comment-claim-unproven.d.ts","sourceRoot":"","sources":["../../../src/phase3/scanners/comment-claim-unproven.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAsC,MAAM,yBAAyB,CAAC;AAM/F,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,OAAO,EAAE,CAAC,CAsDpB"}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VC-HALL-010: Comment Claims Protection But Unproven
|
|
3
|
-
*
|
|
4
|
-
* Detects comments that claim security protection but the actual
|
|
5
|
-
* implementation doesn't prove it.
|
|
6
|
-
*
|
|
7
|
-
* Severity: Medium
|
|
8
|
-
* Category: hallucinations
|
|
9
|
-
* Confidence: 0.75
|
|
10
|
-
*/
|
|
11
|
-
import crypto from "node:crypto";
|
|
12
|
-
import { buildRouteMap, buildAllProofTraces } from "../proof-trace-builder.js";
|
|
13
|
-
import { mineAllIntentClaims } from "../intent-miner.js";
|
|
14
|
-
const RULE_ID = "VC-HALL-010";
|
|
15
|
-
export async function scanCommentClaimUnproven(ctx) {
|
|
16
|
-
const findings = [];
|
|
17
|
-
// Build route map and proof traces
|
|
18
|
-
const routes = buildRouteMap(ctx);
|
|
19
|
-
const proofTraces = buildAllProofTraces(ctx, routes);
|
|
20
|
-
// Mine intent claims from comments
|
|
21
|
-
const allClaims = mineAllIntentClaims(ctx, routes);
|
|
22
|
-
const commentClaims = allClaims.filter((c) => c.source === "comment");
|
|
23
|
-
// Find claims that aren't proven
|
|
24
|
-
const unprovenClaims = findUnprovenCommentClaims(commentClaims, proofTraces, routes);
|
|
25
|
-
for (const claim of unprovenClaims) {
|
|
26
|
-
const route = routes.find((r) => r.routeId === claim.targetRouteId);
|
|
27
|
-
const trace = claim.targetRouteId ? proofTraces.get(claim.targetRouteId) : undefined;
|
|
28
|
-
findings.push({
|
|
29
|
-
id: generateFindingId(claim),
|
|
30
|
-
severity: "medium",
|
|
31
|
-
confidence: 0.75,
|
|
32
|
-
category: "hallucinations",
|
|
33
|
-
ruleId: RULE_ID,
|
|
34
|
-
title: `Comment claims ${formatClaimType(claim.type)} but implementation doesn't prove it`,
|
|
35
|
-
description: generateDescription(claim, route, trace),
|
|
36
|
-
evidence: [
|
|
37
|
-
{
|
|
38
|
-
file: claim.location.file,
|
|
39
|
-
startLine: claim.location.startLine,
|
|
40
|
-
endLine: claim.location.endLine,
|
|
41
|
-
snippet: claim.textEvidence,
|
|
42
|
-
label: "Security claim in comment",
|
|
43
|
-
},
|
|
44
|
-
...(route
|
|
45
|
-
? [
|
|
46
|
-
{
|
|
47
|
-
file: route.file,
|
|
48
|
-
startLine: route.startLine,
|
|
49
|
-
endLine: route.endLine,
|
|
50
|
-
snippet: `${route.method} ${route.path}`,
|
|
51
|
-
label: "Associated route without proof",
|
|
52
|
-
},
|
|
53
|
-
]
|
|
54
|
-
: []),
|
|
55
|
-
],
|
|
56
|
-
remediation: {
|
|
57
|
-
recommendedFix: generateRemediation(claim),
|
|
58
|
-
},
|
|
59
|
-
fingerprint: generateFingerprint(claim),
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
return findings;
|
|
63
|
-
}
|
|
64
|
-
function findUnprovenCommentClaims(claims, proofTraces, routes) {
|
|
65
|
-
const unproven = [];
|
|
66
|
-
for (const claim of claims) {
|
|
67
|
-
// Skip claims without route association for auth/validation
|
|
68
|
-
if ((claim.type === "AUTH_ENFORCED" || claim.type === "INPUT_VALIDATED") &&
|
|
69
|
-
!claim.targetRouteId) {
|
|
70
|
-
// Check if there are ANY routes in the file without proof
|
|
71
|
-
const fileRoutes = routes.filter((r) => r.file === claim.location.file);
|
|
72
|
-
for (const route of fileRoutes) {
|
|
73
|
-
const proof = proofTraces.get(route.routeId);
|
|
74
|
-
if (claim.type === "AUTH_ENFORCED" && proof && !proof.authProven && !proof.middlewareCovered) {
|
|
75
|
-
unproven.push({ ...claim, targetRouteId: route.routeId });
|
|
76
|
-
}
|
|
77
|
-
else if (claim.type === "INPUT_VALIDATED" && proof && !proof.validationProven) {
|
|
78
|
-
unproven.push({ ...claim, targetRouteId: route.routeId });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (!claim.targetRouteId)
|
|
84
|
-
continue;
|
|
85
|
-
const proof = proofTraces.get(claim.targetRouteId);
|
|
86
|
-
if (!proof) {
|
|
87
|
-
unproven.push(claim);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
// Check specific claim types
|
|
91
|
-
if (claim.type === "AUTH_ENFORCED" && !proof.authProven && !proof.middlewareCovered) {
|
|
92
|
-
unproven.push(claim);
|
|
93
|
-
}
|
|
94
|
-
else if (claim.type === "INPUT_VALIDATED" && !proof.validationProven) {
|
|
95
|
-
unproven.push(claim);
|
|
96
|
-
}
|
|
97
|
-
else if (claim.type === "MIDDLEWARE_PROTECTED" && !proof.middlewareCovered) {
|
|
98
|
-
unproven.push(claim);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return unproven;
|
|
102
|
-
}
|
|
103
|
-
function formatClaimType(type) {
|
|
104
|
-
switch (type) {
|
|
105
|
-
case "AUTH_ENFORCED":
|
|
106
|
-
return "authentication";
|
|
107
|
-
case "INPUT_VALIDATED":
|
|
108
|
-
return "input validation";
|
|
109
|
-
case "MIDDLEWARE_PROTECTED":
|
|
110
|
-
return "middleware protection";
|
|
111
|
-
case "CSRF_ENABLED":
|
|
112
|
-
return "CSRF protection";
|
|
113
|
-
case "RATE_LIMITED":
|
|
114
|
-
return "rate limiting";
|
|
115
|
-
default:
|
|
116
|
-
return type.toLowerCase().replace(/_/g, " ");
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function generateDescription(claim, route, trace) {
|
|
120
|
-
let desc = `A comment claims that ${formatClaimType(claim.type)} is in place`;
|
|
121
|
-
if (route) {
|
|
122
|
-
desc += ` for the ${route.method} ${route.path} endpoint`;
|
|
123
|
-
}
|
|
124
|
-
desc += ", but static analysis couldn't verify this claim.";
|
|
125
|
-
if (trace) {
|
|
126
|
-
const missing = [];
|
|
127
|
-
if (claim.type === "AUTH_ENFORCED" && !trace.authProven && !trace.middlewareCovered) {
|
|
128
|
-
missing.push("authentication check");
|
|
129
|
-
}
|
|
130
|
-
if (claim.type === "INPUT_VALIDATED" && !trace.validationProven) {
|
|
131
|
-
missing.push("validation usage");
|
|
132
|
-
}
|
|
133
|
-
if (claim.type === "MIDDLEWARE_PROTECTED" && !trace.middlewareCovered) {
|
|
134
|
-
missing.push("middleware coverage");
|
|
135
|
-
}
|
|
136
|
-
if (missing.length > 0) {
|
|
137
|
-
desc += ` Missing: ${missing.join(", ")}.`;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
desc += " This could be a documentation issue or missing implementation.";
|
|
141
|
-
return desc;
|
|
142
|
-
}
|
|
143
|
-
function generateRemediation(claim) {
|
|
144
|
-
switch (claim.type) {
|
|
145
|
-
case "AUTH_ENFORCED":
|
|
146
|
-
return "Add authentication check using getServerSession(), auth(), or similar before performing operations. Alternatively, update the comment if the claim is incorrect.";
|
|
147
|
-
case "INPUT_VALIDATED":
|
|
148
|
-
return "Add input validation using Zod, Yup, or Joi and ensure the validated result is used. Alternatively, update the comment if validation isn't needed.";
|
|
149
|
-
case "MIDDLEWARE_PROTECTED":
|
|
150
|
-
return "Ensure the middleware matcher covers this route, or add explicit auth check in the handler.";
|
|
151
|
-
default:
|
|
152
|
-
return "Verify the security claim matches the implementation, or update documentation to reflect actual behavior.";
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function generateFindingId(claim) {
|
|
156
|
-
return `f-${crypto.randomUUID().slice(0, 8)}`;
|
|
157
|
-
}
|
|
158
|
-
function generateFingerprint(claim) {
|
|
159
|
-
const data = `${RULE_ID}:${claim.location.file}:${claim.location.startLine}:${claim.type}`;
|
|
160
|
-
return `sha256:${crypto.createHash("sha256").update(data).digest("hex")}`;
|
|
161
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 3 Scanner Exports
|
|
3
|
-
*/
|
|
4
|
-
export { scanCommentClaimUnproven } from "./comment-claim-unproven.js";
|
|
5
|
-
export { scanMiddlewareAssumedNotMatching } from "./middleware-assumed-not-matching.js";
|
|
6
|
-
export { scanValidationClaimedMissing } from "./validation-claimed-missing.js";
|
|
7
|
-
export { scanAuthByUiServerGap } from "./auth-by-ui-server-gap.js";
|
|
8
|
-
import type { Scanner } from "../../scanners/types.js";
|
|
9
|
-
import { scanCommentClaimUnproven } from "./comment-claim-unproven.js";
|
|
10
|
-
import { scanAuthByUiServerGap } from "./auth-by-ui-server-gap.js";
|
|
11
|
-
/**
|
|
12
|
-
* All Phase 3 scanners
|
|
13
|
-
*/
|
|
14
|
-
export declare const phase3Scanners: Scanner[];
|
|
15
|
-
/**
|
|
16
|
-
* Phase 3 Hallucination Pack
|
|
17
|
-
*/
|
|
18
|
-
export declare const hallucinationsPack: {
|
|
19
|
-
id: string;
|
|
20
|
-
name: string;
|
|
21
|
-
scanners: (typeof scanCommentClaimUnproven)[];
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Phase 3 Auth Pack extension
|
|
25
|
-
*/
|
|
26
|
-
export declare const authPackPhase3: {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
scanners: (typeof scanAuthByUiServerGap)[];
|
|
30
|
-
};
|
|
31
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/phase3/scanners/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,gCAAgC,EAAE,MAAM,sCAAsC,CAAC;AACxF,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,EAKnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;CAQ9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc;;;;CAI1B,CAAC"}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 3 Scanner Exports
|
|
3
|
-
*/
|
|
4
|
-
export { scanCommentClaimUnproven } from "./comment-claim-unproven.js";
|
|
5
|
-
export { scanMiddlewareAssumedNotMatching } from "./middleware-assumed-not-matching.js";
|
|
6
|
-
export { scanValidationClaimedMissing } from "./validation-claimed-missing.js";
|
|
7
|
-
export { scanAuthByUiServerGap } from "./auth-by-ui-server-gap.js";
|
|
8
|
-
import { scanCommentClaimUnproven } from "./comment-claim-unproven.js";
|
|
9
|
-
import { scanMiddlewareAssumedNotMatching } from "./middleware-assumed-not-matching.js";
|
|
10
|
-
import { scanValidationClaimedMissing } from "./validation-claimed-missing.js";
|
|
11
|
-
import { scanAuthByUiServerGap } from "./auth-by-ui-server-gap.js";
|
|
12
|
-
/**
|
|
13
|
-
* All Phase 3 scanners
|
|
14
|
-
*/
|
|
15
|
-
export const phase3Scanners = [
|
|
16
|
-
scanCommentClaimUnproven,
|
|
17
|
-
scanMiddlewareAssumedNotMatching,
|
|
18
|
-
scanValidationClaimedMissing,
|
|
19
|
-
scanAuthByUiServerGap,
|
|
20
|
-
];
|
|
21
|
-
/**
|
|
22
|
-
* Phase 3 Hallucination Pack
|
|
23
|
-
*/
|
|
24
|
-
export const hallucinationsPack = {
|
|
25
|
-
id: "hallucinations",
|
|
26
|
-
name: "Hallucinations Pack (Phase 3)",
|
|
27
|
-
scanners: [
|
|
28
|
-
scanCommentClaimUnproven,
|
|
29
|
-
scanMiddlewareAssumedNotMatching,
|
|
30
|
-
scanValidationClaimedMissing,
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Phase 3 Auth Pack extension
|
|
35
|
-
*/
|
|
36
|
-
export const authPackPhase3 = {
|
|
37
|
-
id: "auth-phase3",
|
|
38
|
-
name: "Auth Pack (Phase 3)",
|
|
39
|
-
scanners: [scanAuthByUiServerGap],
|
|
40
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VC-HALL-011: Middleware Assumed But Not Matching
|
|
3
|
-
*
|
|
4
|
-
* Detects routes that appear to expect middleware protection
|
|
5
|
-
* but are not covered by the middleware matcher patterns.
|
|
6
|
-
*
|
|
7
|
-
* Severity: High
|
|
8
|
-
* Category: hallucinations
|
|
9
|
-
* Confidence: 0.70
|
|
10
|
-
*/
|
|
11
|
-
import type { Finding } from "@vibecheck/schema";
|
|
12
|
-
import type { ScanContext } from "../../scanners/types.js";
|
|
13
|
-
export declare function scanMiddlewareAssumedNotMatching(ctx: ScanContext): Promise<Finding[]>;
|
|
14
|
-
//# sourceMappingURL=middleware-assumed-not-matching.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"middleware-assumed-not-matching.d.ts","sourceRoot":"","sources":["../../../src/phase3/scanners/middleware-assumed-not-matching.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,yBAAyB,CAAC;AA0BnF,wBAAsB,gCAAgC,CACpD,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,OAAO,EAAE,CAAC,CAqEpB"}
|