@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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"proof-trace-builder.d.ts","sourceRoot":"","sources":["../../src/phase3/proof-trace-builder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,cAAc,EACd,UAAU,EAGV,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAI9B;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgB5D;AAcD;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,EAAE,CA4B3D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,EAAE,CAwCrE;AA2BD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAqBT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,GACf,UAAU,CA0GZ;AA+MD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EAAE,EACnB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,aAAa,EAAE,cAAc,EAAE,GAC9B,eAAe,CA2CjB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,SAAS,EAAE,GAClB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CASzB"}
|
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 3: Cross-file Proof Trace Builder
|
|
3
|
-
*
|
|
4
|
-
* Lightweight call-graph tracing for Next.js App Router.
|
|
5
|
-
* Traces auth/validation through local imports (max depth 2).
|
|
6
|
-
* Deterministic, local-only.
|
|
7
|
-
*/
|
|
8
|
-
import crypto from "node:crypto";
|
|
9
|
-
import path from "node:path";
|
|
10
|
-
import { SyntaxKind } from "ts-morph";
|
|
11
|
-
const MAX_TRACE_DEPTH = 2;
|
|
12
|
-
/**
|
|
13
|
-
* Generate a stable route ID from path, method, and file
|
|
14
|
-
*/
|
|
15
|
-
export function generateRouteId(routePath, method, file) {
|
|
16
|
-
const normalized = `${method}:${routePath}:${file}`.toLowerCase();
|
|
17
|
-
return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Convert file path to URL path for Next.js App Router
|
|
21
|
-
* e.g., "app/api/users/[id]/route.ts" -> "/api/users/[id]"
|
|
22
|
-
*/
|
|
23
|
-
export function filePathToRoutePath(filePath) {
|
|
24
|
-
// Normalize to forward slashes
|
|
25
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
26
|
-
// Find app directory
|
|
27
|
-
const appIndex = normalized.indexOf("/app/");
|
|
28
|
-
if (appIndex === -1) {
|
|
29
|
-
// Try without leading slash
|
|
30
|
-
const appIndexAlt = normalized.indexOf("app/");
|
|
31
|
-
if (appIndexAlt === 0) {
|
|
32
|
-
return extractRoutePath(normalized.slice(4));
|
|
33
|
-
}
|
|
34
|
-
return "/";
|
|
35
|
-
}
|
|
36
|
-
return extractRoutePath(normalized.slice(appIndex + 5));
|
|
37
|
-
}
|
|
38
|
-
function extractRoutePath(routePart) {
|
|
39
|
-
// Remove route.ts/route.js suffix
|
|
40
|
-
const withoutRoute = routePart.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
|
|
41
|
-
// Handle root API route
|
|
42
|
-
if (withoutRoute === "" || withoutRoute === "api") {
|
|
43
|
-
return withoutRoute === "" ? "/" : "/api";
|
|
44
|
-
}
|
|
45
|
-
return "/" + withoutRoute;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Build route map from scan context
|
|
49
|
-
*/
|
|
50
|
-
export function buildRouteMap(ctx) {
|
|
51
|
-
const routes = [];
|
|
52
|
-
for (const routeFile of ctx.fileIndex.routeFiles) {
|
|
53
|
-
// routeFiles are relative paths, need to resolve to absolute for parseFile
|
|
54
|
-
const absolutePath = path.join(ctx.repoRoot, routeFile);
|
|
55
|
-
const sourceFile = ctx.helpers.parseFile(absolutePath);
|
|
56
|
-
if (!sourceFile)
|
|
57
|
-
continue;
|
|
58
|
-
const handlers = ctx.helpers.findRouteHandlers(sourceFile);
|
|
59
|
-
// routeFile is already relative to repoRoot, just normalize slashes
|
|
60
|
-
const relPath = routeFile.replace(/\\/g, "/");
|
|
61
|
-
const routePath = filePathToRoutePath(relPath);
|
|
62
|
-
for (const handler of handlers) {
|
|
63
|
-
const routeId = generateRouteId(routePath, handler.method, relPath);
|
|
64
|
-
routes.push({
|
|
65
|
-
routeId,
|
|
66
|
-
method: handler.method,
|
|
67
|
-
path: routePath,
|
|
68
|
-
file: relPath,
|
|
69
|
-
startLine: handler.startLine,
|
|
70
|
-
endLine: handler.endLine,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return routes;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Build middleware map from scan context
|
|
78
|
-
*/
|
|
79
|
-
export function buildMiddlewareMap(ctx) {
|
|
80
|
-
const middlewareList = [];
|
|
81
|
-
if (!ctx.fileIndex.middlewareFile) {
|
|
82
|
-
return middlewareList;
|
|
83
|
-
}
|
|
84
|
-
// middlewareFile is a relative path, resolve to absolute for parseFile
|
|
85
|
-
const absolutePath = path.join(ctx.repoRoot, ctx.fileIndex.middlewareFile);
|
|
86
|
-
const sourceFile = ctx.helpers.parseFile(absolutePath);
|
|
87
|
-
if (!sourceFile)
|
|
88
|
-
return middlewareList;
|
|
89
|
-
// middlewareFile is already relative, just normalize slashes
|
|
90
|
-
const relPath = ctx.fileIndex.middlewareFile.replace(/\\/g, "/");
|
|
91
|
-
// Find config.matcher export
|
|
92
|
-
const matchers = extractMiddlewareMatchers(sourceFile);
|
|
93
|
-
const protectsApi = matchers.some((m) => m.includes("/api") || m.includes("/(api)") || m === "/(.*)");
|
|
94
|
-
// Find the config declaration line
|
|
95
|
-
let startLine = 1;
|
|
96
|
-
sourceFile.forEachDescendant((node) => {
|
|
97
|
-
if (node.getKind() === SyntaxKind.VariableDeclaration &&
|
|
98
|
-
node.getText().includes("config")) {
|
|
99
|
-
startLine = node.getStartLineNumber();
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
middlewareList.push({
|
|
103
|
-
file: relPath,
|
|
104
|
-
matchers,
|
|
105
|
-
protectsApi,
|
|
106
|
-
startLine,
|
|
107
|
-
});
|
|
108
|
-
return middlewareList;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Extract matcher patterns from middleware config
|
|
112
|
-
*/
|
|
113
|
-
function extractMiddlewareMatchers(sourceFile) {
|
|
114
|
-
const matchers = [];
|
|
115
|
-
sourceFile.forEachDescendant((node) => {
|
|
116
|
-
// Look for config = { matcher: [...] }
|
|
117
|
-
if (node.getKind() === SyntaxKind.PropertyAssignment) {
|
|
118
|
-
const text = node.getText();
|
|
119
|
-
if (text.startsWith("matcher")) {
|
|
120
|
-
// Extract string literals from matcher array
|
|
121
|
-
node.forEachDescendant((child) => {
|
|
122
|
-
if (child.getKind() === SyntaxKind.StringLiteral) {
|
|
123
|
-
const value = child.getText().replace(/['"]/g, "");
|
|
124
|
-
matchers.push(value);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
return matchers;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Check if a route path is covered by middleware matchers
|
|
134
|
-
*/
|
|
135
|
-
export function isRouteCoveredByMiddleware(routePath, matchers) {
|
|
136
|
-
for (const matcher of matchers) {
|
|
137
|
-
// Convert Next.js matcher pattern to regex
|
|
138
|
-
const pattern = matcher
|
|
139
|
-
.replace(/\*/g, ".*")
|
|
140
|
-
.replace(/\/:path\*/g, "/.*")
|
|
141
|
-
.replace(/\(([^)]+)\)/g, "(?:$1)");
|
|
142
|
-
try {
|
|
143
|
-
const regex = new RegExp(`^${pattern}`);
|
|
144
|
-
if (regex.test(routePath)) {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// Invalid regex, try simple prefix match
|
|
150
|
-
if (routePath.startsWith(matcher.replace(/\/:path\*$/, ""))) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Trace auth/validation through a handler and its imports
|
|
159
|
-
*/
|
|
160
|
-
export function buildProofTrace(ctx, route) {
|
|
161
|
-
const steps = [];
|
|
162
|
-
let authProven = false;
|
|
163
|
-
let validationProven = false;
|
|
164
|
-
const sourceFile = ctx.helpers.parseFile(path.join(ctx.repoRoot, route.file));
|
|
165
|
-
if (!sourceFile) {
|
|
166
|
-
return {
|
|
167
|
-
routeId: route.routeId,
|
|
168
|
-
authProven: false,
|
|
169
|
-
validationProven: false,
|
|
170
|
-
middlewareCovered: false,
|
|
171
|
-
steps: [],
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
// Find the handler function
|
|
175
|
-
const handlers = ctx.helpers.findRouteHandlers(sourceFile);
|
|
176
|
-
const handler = handlers.find((h) => h.method === route.method);
|
|
177
|
-
if (!handler) {
|
|
178
|
-
return {
|
|
179
|
-
routeId: route.routeId,
|
|
180
|
-
authProven: false,
|
|
181
|
-
validationProven: false,
|
|
182
|
-
middlewareCovered: false,
|
|
183
|
-
steps: [],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
// Check handler directly for auth
|
|
187
|
-
if (ctx.helpers.containsAuthCheck(handler.functionNode)) {
|
|
188
|
-
authProven = true;
|
|
189
|
-
steps.push({
|
|
190
|
-
file: route.file,
|
|
191
|
-
line: handler.startLine,
|
|
192
|
-
snippet: truncateSnippet(handler.functionNode.getText(), 100),
|
|
193
|
-
label: "Auth check found in handler",
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
// Check handler directly for validation
|
|
197
|
-
const validationUsage = ctx.helpers.findValidationUsage(handler.functionNode);
|
|
198
|
-
if (validationUsage.length > 0 && validationUsage.some((v) => v.resultUsed)) {
|
|
199
|
-
validationProven = true;
|
|
200
|
-
steps.push({
|
|
201
|
-
file: route.file,
|
|
202
|
-
line: validationUsage[0].line,
|
|
203
|
-
snippet: truncateSnippet(ctx.helpers.getNodeText(validationUsage[0].node), 100),
|
|
204
|
-
label: "Validation found in handler",
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
// If not proven yet, trace through imports (max depth 2)
|
|
208
|
-
if (!authProven || !validationProven) {
|
|
209
|
-
const importedModules = getLocalImports(sourceFile, ctx.repoRoot, route.file);
|
|
210
|
-
for (const importInfo of importedModules) {
|
|
211
|
-
if (authProven && validationProven)
|
|
212
|
-
break;
|
|
213
|
-
const traceResult = traceImportedModule(ctx, importInfo, 1, // depth
|
|
214
|
-
!authProven, !validationProven);
|
|
215
|
-
if (traceResult.authProven && !authProven) {
|
|
216
|
-
authProven = true;
|
|
217
|
-
steps.push(...traceResult.steps.filter((s) => s.label.includes("Auth")));
|
|
218
|
-
}
|
|
219
|
-
if (traceResult.validationProven && !validationProven) {
|
|
220
|
-
validationProven = true;
|
|
221
|
-
steps.push(...traceResult.steps.filter((s) => s.label.includes("Validation")));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// Check middleware coverage
|
|
226
|
-
const middlewareMap = buildMiddlewareMap(ctx);
|
|
227
|
-
const allMatchers = middlewareMap.flatMap((m) => m.matchers);
|
|
228
|
-
const middlewareCovered = isRouteCoveredByMiddleware(route.path, allMatchers);
|
|
229
|
-
if (middlewareCovered) {
|
|
230
|
-
const middleware = middlewareMap[0];
|
|
231
|
-
if (middleware) {
|
|
232
|
-
steps.push({
|
|
233
|
-
file: middleware.file,
|
|
234
|
-
line: middleware.startLine,
|
|
235
|
-
snippet: `matcher: ${JSON.stringify(middleware.matchers)}`,
|
|
236
|
-
label: "Covered by middleware",
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
routeId: route.routeId,
|
|
242
|
-
authProven,
|
|
243
|
-
validationProven,
|
|
244
|
-
middlewareCovered,
|
|
245
|
-
steps,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Get local (relative) imports from a source file
|
|
250
|
-
*/
|
|
251
|
-
function getLocalImports(sourceFile, repoRoot, currentFile) {
|
|
252
|
-
const imports = [];
|
|
253
|
-
const currentDir = path.dirname(path.join(repoRoot, currentFile));
|
|
254
|
-
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
255
|
-
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
256
|
-
// Only process relative imports
|
|
257
|
-
if (!moduleSpecifier.startsWith(".")) {
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
// Resolve to absolute path
|
|
261
|
-
let absolutePath = path.resolve(currentDir, moduleSpecifier);
|
|
262
|
-
// Try common extensions
|
|
263
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
264
|
-
let resolved = false;
|
|
265
|
-
for (const ext of extensions) {
|
|
266
|
-
const withExt = absolutePath + ext;
|
|
267
|
-
try {
|
|
268
|
-
// Check if file exists by trying to parse it
|
|
269
|
-
if (sourceFile.getProject().getSourceFile(withExt)) {
|
|
270
|
-
absolutePath = withExt;
|
|
271
|
-
resolved = true;
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
// File doesn't exist
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
// Also try index files
|
|
280
|
-
if (!resolved) {
|
|
281
|
-
for (const ext of extensions) {
|
|
282
|
-
const indexPath = path.join(absolutePath, `index${ext}`);
|
|
283
|
-
try {
|
|
284
|
-
if (sourceFile.getProject().getSourceFile(indexPath)) {
|
|
285
|
-
absolutePath = indexPath;
|
|
286
|
-
resolved = true;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
catch {
|
|
291
|
-
// File doesn't exist
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
const importedNames = [];
|
|
296
|
-
// Get named imports
|
|
297
|
-
for (const namedImport of importDecl.getNamedImports()) {
|
|
298
|
-
importedNames.push(namedImport.getName());
|
|
299
|
-
}
|
|
300
|
-
// Get default import
|
|
301
|
-
const defaultImport = importDecl.getDefaultImport();
|
|
302
|
-
if (defaultImport) {
|
|
303
|
-
importedNames.push(defaultImport.getText());
|
|
304
|
-
}
|
|
305
|
-
imports.push({
|
|
306
|
-
modulePath: moduleSpecifier,
|
|
307
|
-
absolutePath,
|
|
308
|
-
importedNames,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
return imports;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Trace an imported module for auth/validation
|
|
315
|
-
*/
|
|
316
|
-
function traceImportedModule(ctx, importInfo, depth, needAuth, needValidation) {
|
|
317
|
-
const result = {
|
|
318
|
-
authProven: false,
|
|
319
|
-
validationProven: false,
|
|
320
|
-
steps: [],
|
|
321
|
-
};
|
|
322
|
-
if (depth > MAX_TRACE_DEPTH) {
|
|
323
|
-
return result;
|
|
324
|
-
}
|
|
325
|
-
const sourceFile = ctx.helpers.parseFile(importInfo.absolutePath);
|
|
326
|
-
if (!sourceFile) {
|
|
327
|
-
return result;
|
|
328
|
-
}
|
|
329
|
-
const relPath = path.relative(ctx.repoRoot, importInfo.absolutePath).replace(/\\/g, "/");
|
|
330
|
-
// Look for auth patterns in exported functions
|
|
331
|
-
if (needAuth) {
|
|
332
|
-
sourceFile.forEachDescendant((node) => {
|
|
333
|
-
if (result.authProven)
|
|
334
|
-
return;
|
|
335
|
-
// Check function declarations
|
|
336
|
-
if (node.getKind() === SyntaxKind.FunctionDeclaration ||
|
|
337
|
-
node.getKind() === SyntaxKind.ArrowFunction) {
|
|
338
|
-
const funcNode = node;
|
|
339
|
-
if (ctx.helpers.containsAuthCheck(funcNode)) {
|
|
340
|
-
result.authProven = true;
|
|
341
|
-
result.steps.push({
|
|
342
|
-
file: relPath,
|
|
343
|
-
line: node.getStartLineNumber(),
|
|
344
|
-
snippet: truncateSnippet(node.getText(), 100),
|
|
345
|
-
label: `Auth check found in imported module (depth ${depth})`,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
// Look for validation patterns
|
|
352
|
-
if (needValidation) {
|
|
353
|
-
sourceFile.forEachDescendant((node) => {
|
|
354
|
-
if (result.validationProven)
|
|
355
|
-
return;
|
|
356
|
-
if (node.getKind() === SyntaxKind.FunctionDeclaration ||
|
|
357
|
-
node.getKind() === SyntaxKind.ArrowFunction) {
|
|
358
|
-
const funcNode = node;
|
|
359
|
-
const validation = ctx.helpers.findValidationUsage(funcNode);
|
|
360
|
-
if (validation.length > 0 && validation.some((v) => v.resultUsed)) {
|
|
361
|
-
result.validationProven = true;
|
|
362
|
-
result.steps.push({
|
|
363
|
-
file: relPath,
|
|
364
|
-
line: validation[0].line,
|
|
365
|
-
snippet: truncateSnippet(ctx.helpers.getNodeText(validation[0].node), 100),
|
|
366
|
-
label: `Validation found in imported module (depth ${depth})`,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
// Recurse into this module's imports if still needed
|
|
373
|
-
if ((!result.authProven && needAuth) || (!result.validationProven && needValidation)) {
|
|
374
|
-
const nestedImports = getLocalImports(sourceFile, ctx.repoRoot, relPath);
|
|
375
|
-
for (const nested of nestedImports) {
|
|
376
|
-
const nestedResult = traceImportedModule(ctx, nested, depth + 1, needAuth && !result.authProven, needValidation && !result.validationProven);
|
|
377
|
-
if (nestedResult.authProven) {
|
|
378
|
-
result.authProven = true;
|
|
379
|
-
result.steps.push(...nestedResult.steps);
|
|
380
|
-
}
|
|
381
|
-
if (nestedResult.validationProven) {
|
|
382
|
-
result.validationProven = true;
|
|
383
|
-
result.steps.push(...nestedResult.steps);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return result;
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Truncate a code snippet to max length
|
|
391
|
-
*/
|
|
392
|
-
function truncateSnippet(text, maxLength) {
|
|
393
|
-
// Remove excessive whitespace
|
|
394
|
-
const cleaned = text.replace(/\s+/g, " ").trim();
|
|
395
|
-
if (cleaned.length <= maxLength) {
|
|
396
|
-
return cleaned;
|
|
397
|
-
}
|
|
398
|
-
return cleaned.slice(0, maxLength - 3) + "...";
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Calculate coverage metrics from routes and proof traces
|
|
402
|
-
*/
|
|
403
|
-
export function calculateCoverage(routes, proofTraces, middlewareMap) {
|
|
404
|
-
const stateChangingMethods = ["POST", "PUT", "PATCH", "DELETE"];
|
|
405
|
-
const stateChangingRoutes = routes.filter((r) => stateChangingMethods.includes(r.method));
|
|
406
|
-
// Auth coverage: state-changing routes with auth proven
|
|
407
|
-
const authCoveredCount = stateChangingRoutes.filter((r) => {
|
|
408
|
-
const trace = proofTraces.get(r.routeId);
|
|
409
|
-
return trace?.authProven || trace?.middlewareCovered;
|
|
410
|
-
}).length;
|
|
411
|
-
const authCoverage = stateChangingRoutes.length > 0
|
|
412
|
-
? authCoveredCount / stateChangingRoutes.length
|
|
413
|
-
: 1;
|
|
414
|
-
// Validation coverage: POST/PUT/PATCH routes with validation proven
|
|
415
|
-
const bodyRoutes = routes.filter((r) => ["POST", "PUT", "PATCH"].includes(r.method));
|
|
416
|
-
const validationCoveredCount = bodyRoutes.filter((r) => {
|
|
417
|
-
const trace = proofTraces.get(r.routeId);
|
|
418
|
-
return trace?.validationProven;
|
|
419
|
-
}).length;
|
|
420
|
-
const validationCoverage = bodyRoutes.length > 0 ? validationCoveredCount / bodyRoutes.length : 1;
|
|
421
|
-
// Middleware coverage: all routes covered by middleware
|
|
422
|
-
const allMatchers = middlewareMap.flatMap((m) => m.matchers);
|
|
423
|
-
const middlewareCoveredCount = routes.filter((r) => isRouteCoveredByMiddleware(r.path, allMatchers)).length;
|
|
424
|
-
const middlewareCoverage = routes.length > 0 ? middlewareCoveredCount / routes.length : 1;
|
|
425
|
-
return {
|
|
426
|
-
authCoverage: Math.round(authCoverage * 100) / 100,
|
|
427
|
-
validationCoverage: Math.round(validationCoverage * 100) / 100,
|
|
428
|
-
middlewareCoverage: Math.round(middlewareCoverage * 100) / 100,
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Build all proof traces for a scan context
|
|
433
|
-
*/
|
|
434
|
-
export function buildAllProofTraces(ctx, routes) {
|
|
435
|
-
const traces = new Map();
|
|
436
|
-
for (const route of routes) {
|
|
437
|
-
const trace = buildProofTrace(ctx, route);
|
|
438
|
-
traces.set(route.routeId, trace);
|
|
439
|
-
}
|
|
440
|
-
return traces;
|
|
441
|
-
}
|
|
@@ -1,15 +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 type { Finding } from "@vibecheck/schema";
|
|
13
|
-
import type { ScanContext } from "../../scanners/types.js";
|
|
14
|
-
export declare function scanAuthByUiServerGap(ctx: ScanContext): Promise<Finding[]>;
|
|
15
|
-
//# sourceMappingURL=auth-by-ui-server-gap.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-by-ui-server-gap.d.ts","sourceRoot":"","sources":["../../../src/phase3/scanners/auth-by-ui-server-gap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,yBAAyB,CAAC;AAgDtE,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,OAAO,EAAE,CAAC,CAmGpB"}
|