@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.
Files changed (208) hide show
  1. package/README.md +6 -6
  2. package/dist/index.d.ts +0 -2
  3. package/dist/index.js +7902 -8
  4. package/package.json +13 -7
  5. package/dist/__tests__/cli.test.d.ts +0 -2
  6. package/dist/__tests__/cli.test.d.ts.map +0 -1
  7. package/dist/__tests__/cli.test.js +0 -243
  8. package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +0 -36
  9. package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +0 -28
  10. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +0 -4
  11. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +0 -1
  12. package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +0 -6
  13. package/dist/__tests__/scanners/env-config.test.d.ts +0 -2
  14. package/dist/__tests__/scanners/env-config.test.d.ts.map +0 -1
  15. package/dist/__tests__/scanners/env-config.test.js +0 -142
  16. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +0 -2
  17. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +0 -1
  18. package/dist/__tests__/scanners/nextjs-middleware.test.js +0 -193
  19. package/dist/__tests__/scanners/scanner-packs.test.d.ts +0 -2
  20. package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +0 -1
  21. package/dist/__tests__/scanners/scanner-packs.test.js +0 -126
  22. package/dist/__tests__/scanners/unused-security-imports.test.d.ts +0 -2
  23. package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +0 -1
  24. package/dist/__tests__/scanners/unused-security-imports.test.js +0 -145
  25. package/dist/commands/demo-artifact.d.ts +0 -7
  26. package/dist/commands/demo-artifact.d.ts.map +0 -1
  27. package/dist/commands/demo-artifact.js +0 -322
  28. package/dist/commands/evaluate.d.ts +0 -30
  29. package/dist/commands/evaluate.d.ts.map +0 -1
  30. package/dist/commands/evaluate.js +0 -258
  31. package/dist/commands/explain.d.ts +0 -12
  32. package/dist/commands/explain.d.ts.map +0 -1
  33. package/dist/commands/explain.js +0 -214
  34. package/dist/commands/index.d.ts +0 -7
  35. package/dist/commands/index.d.ts.map +0 -1
  36. package/dist/commands/index.js +0 -6
  37. package/dist/commands/intent.d.ts +0 -21
  38. package/dist/commands/intent.d.ts.map +0 -1
  39. package/dist/commands/intent.js +0 -192
  40. package/dist/commands/scan.d.ts +0 -44
  41. package/dist/commands/scan.d.ts.map +0 -1
  42. package/dist/commands/scan.js +0 -497
  43. package/dist/commands/waivers.d.ts +0 -30
  44. package/dist/commands/waivers.d.ts.map +0 -1
  45. package/dist/commands/waivers.js +0 -249
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/phase3/index.d.ts +0 -11
  48. package/dist/phase3/index.d.ts.map +0 -1
  49. package/dist/phase3/index.js +0 -12
  50. package/dist/phase3/intent-miner.d.ts +0 -32
  51. package/dist/phase3/intent-miner.d.ts.map +0 -1
  52. package/dist/phase3/intent-miner.js +0 -323
  53. package/dist/phase3/proof-trace-builder.d.ts +0 -42
  54. package/dist/phase3/proof-trace-builder.d.ts.map +0 -1
  55. package/dist/phase3/proof-trace-builder.js +0 -441
  56. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +0 -15
  57. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +0 -1
  58. package/dist/phase3/scanners/auth-by-ui-server-gap.js +0 -237
  59. package/dist/phase3/scanners/comment-claim-unproven.d.ts +0 -14
  60. package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +0 -1
  61. package/dist/phase3/scanners/comment-claim-unproven.js +0 -161
  62. package/dist/phase3/scanners/index.d.ts +0 -31
  63. package/dist/phase3/scanners/index.d.ts.map +0 -1
  64. package/dist/phase3/scanners/index.js +0 -40
  65. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +0 -14
  66. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +0 -1
  67. package/dist/phase3/scanners/middleware-assumed-not-matching.js +0 -172
  68. package/dist/phase3/scanners/validation-claimed-missing.d.ts +0 -15
  69. package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +0 -1
  70. package/dist/phase3/scanners/validation-claimed-missing.js +0 -204
  71. package/dist/scanners/abuse/compute-abuse.d.ts +0 -20
  72. package/dist/scanners/abuse/compute-abuse.d.ts.map +0 -1
  73. package/dist/scanners/abuse/compute-abuse.js +0 -509
  74. package/dist/scanners/abuse/index.d.ts +0 -12
  75. package/dist/scanners/abuse/index.d.ts.map +0 -1
  76. package/dist/scanners/abuse/index.js +0 -15
  77. package/dist/scanners/auth/index.d.ts +0 -5
  78. package/dist/scanners/auth/index.d.ts.map +0 -1
  79. package/dist/scanners/auth/index.js +0 -10
  80. package/dist/scanners/auth/middleware-gap.d.ts +0 -22
  81. package/dist/scanners/auth/middleware-gap.d.ts.map +0 -1
  82. package/dist/scanners/auth/middleware-gap.js +0 -203
  83. package/dist/scanners/auth/unprotected-api-route.d.ts +0 -12
  84. package/dist/scanners/auth/unprotected-api-route.d.ts.map +0 -1
  85. package/dist/scanners/auth/unprotected-api-route.js +0 -126
  86. package/dist/scanners/config/index.d.ts +0 -5
  87. package/dist/scanners/config/index.d.ts.map +0 -1
  88. package/dist/scanners/config/index.js +0 -10
  89. package/dist/scanners/config/insecure-defaults.d.ts +0 -12
  90. package/dist/scanners/config/insecure-defaults.d.ts.map +0 -1
  91. package/dist/scanners/config/insecure-defaults.js +0 -77
  92. package/dist/scanners/config/undocumented-env.d.ts +0 -24
  93. package/dist/scanners/config/undocumented-env.d.ts.map +0 -1
  94. package/dist/scanners/config/undocumented-env.js +0 -159
  95. package/dist/scanners/crypto/index.d.ts +0 -6
  96. package/dist/scanners/crypto/index.d.ts.map +0 -1
  97. package/dist/scanners/crypto/index.js +0 -11
  98. package/dist/scanners/crypto/jwt-decode-unverified.d.ts +0 -14
  99. package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +0 -1
  100. package/dist/scanners/crypto/jwt-decode-unverified.js +0 -87
  101. package/dist/scanners/crypto/math-random-tokens.d.ts +0 -13
  102. package/dist/scanners/crypto/math-random-tokens.d.ts.map +0 -1
  103. package/dist/scanners/crypto/math-random-tokens.js +0 -80
  104. package/dist/scanners/crypto/weak-hashing.d.ts +0 -11
  105. package/dist/scanners/crypto/weak-hashing.d.ts.map +0 -1
  106. package/dist/scanners/crypto/weak-hashing.js +0 -95
  107. package/dist/scanners/env-config.d.ts +0 -24
  108. package/dist/scanners/env-config.d.ts.map +0 -1
  109. package/dist/scanners/env-config.js +0 -164
  110. package/dist/scanners/hallucinations/index.d.ts +0 -4
  111. package/dist/scanners/hallucinations/index.d.ts.map +0 -1
  112. package/dist/scanners/hallucinations/index.js +0 -8
  113. package/dist/scanners/hallucinations/unused-security-imports.d.ts +0 -36
  114. package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +0 -1
  115. package/dist/scanners/hallucinations/unused-security-imports.js +0 -309
  116. package/dist/scanners/helpers/ast-helpers.d.ts +0 -6
  117. package/dist/scanners/helpers/ast-helpers.d.ts.map +0 -1
  118. package/dist/scanners/helpers/ast-helpers.js +0 -945
  119. package/dist/scanners/helpers/context-builder.d.ts +0 -17
  120. package/dist/scanners/helpers/context-builder.d.ts.map +0 -1
  121. package/dist/scanners/helpers/context-builder.js +0 -148
  122. package/dist/scanners/helpers/index.d.ts +0 -3
  123. package/dist/scanners/helpers/index.d.ts.map +0 -1
  124. package/dist/scanners/helpers/index.js +0 -2
  125. package/dist/scanners/index.d.ts +0 -30
  126. package/dist/scanners/index.d.ts.map +0 -1
  127. package/dist/scanners/index.js +0 -102
  128. package/dist/scanners/middleware/index.d.ts +0 -4
  129. package/dist/scanners/middleware/index.d.ts.map +0 -1
  130. package/dist/scanners/middleware/index.js +0 -7
  131. package/dist/scanners/middleware/missing-rate-limit.d.ts +0 -13
  132. package/dist/scanners/middleware/missing-rate-limit.d.ts.map +0 -1
  133. package/dist/scanners/middleware/missing-rate-limit.js +0 -140
  134. package/dist/scanners/network/cors-misconfiguration.d.ts +0 -14
  135. package/dist/scanners/network/cors-misconfiguration.d.ts.map +0 -1
  136. package/dist/scanners/network/cors-misconfiguration.js +0 -89
  137. package/dist/scanners/network/index.d.ts +0 -7
  138. package/dist/scanners/network/index.d.ts.map +0 -1
  139. package/dist/scanners/network/index.js +0 -18
  140. package/dist/scanners/network/missing-timeout.d.ts +0 -15
  141. package/dist/scanners/network/missing-timeout.d.ts.map +0 -1
  142. package/dist/scanners/network/missing-timeout.js +0 -93
  143. package/dist/scanners/network/open-redirect.d.ts +0 -15
  144. package/dist/scanners/network/open-redirect.d.ts.map +0 -1
  145. package/dist/scanners/network/open-redirect.js +0 -88
  146. package/dist/scanners/network/ssrf-prone-fetch.d.ts +0 -12
  147. package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +0 -1
  148. package/dist/scanners/network/ssrf-prone-fetch.js +0 -90
  149. package/dist/scanners/nextjs-middleware.d.ts +0 -26
  150. package/dist/scanners/nextjs-middleware.d.ts.map +0 -1
  151. package/dist/scanners/nextjs-middleware.js +0 -246
  152. package/dist/scanners/privacy/debug-flags.d.ts +0 -13
  153. package/dist/scanners/privacy/debug-flags.d.ts.map +0 -1
  154. package/dist/scanners/privacy/debug-flags.js +0 -124
  155. package/dist/scanners/privacy/index.d.ts +0 -6
  156. package/dist/scanners/privacy/index.d.ts.map +0 -1
  157. package/dist/scanners/privacy/index.js +0 -11
  158. package/dist/scanners/privacy/over-broad-response.d.ts +0 -15
  159. package/dist/scanners/privacy/over-broad-response.d.ts.map +0 -1
  160. package/dist/scanners/privacy/over-broad-response.js +0 -109
  161. package/dist/scanners/privacy/sensitive-logging.d.ts +0 -11
  162. package/dist/scanners/privacy/sensitive-logging.d.ts.map +0 -1
  163. package/dist/scanners/privacy/sensitive-logging.js +0 -78
  164. package/dist/scanners/types.d.ts +0 -456
  165. package/dist/scanners/types.d.ts.map +0 -1
  166. package/dist/scanners/types.js +0 -16
  167. package/dist/scanners/unused-security-imports.d.ts +0 -34
  168. package/dist/scanners/unused-security-imports.d.ts.map +0 -1
  169. package/dist/scanners/unused-security-imports.js +0 -206
  170. package/dist/scanners/uploads/index.d.ts +0 -5
  171. package/dist/scanners/uploads/index.d.ts.map +0 -1
  172. package/dist/scanners/uploads/index.js +0 -9
  173. package/dist/scanners/uploads/missing-constraints.d.ts +0 -15
  174. package/dist/scanners/uploads/missing-constraints.d.ts.map +0 -1
  175. package/dist/scanners/uploads/missing-constraints.js +0 -109
  176. package/dist/scanners/uploads/public-path.d.ts +0 -11
  177. package/dist/scanners/uploads/public-path.d.ts.map +0 -1
  178. package/dist/scanners/uploads/public-path.js +0 -87
  179. package/dist/scanners/validation/client-side-only.d.ts +0 -14
  180. package/dist/scanners/validation/client-side-only.d.ts.map +0 -1
  181. package/dist/scanners/validation/client-side-only.js +0 -140
  182. package/dist/scanners/validation/ignored-validation.d.ts +0 -12
  183. package/dist/scanners/validation/ignored-validation.d.ts.map +0 -1
  184. package/dist/scanners/validation/ignored-validation.js +0 -119
  185. package/dist/scanners/validation/index.d.ts +0 -5
  186. package/dist/scanners/validation/index.d.ts.map +0 -1
  187. package/dist/scanners/validation/index.js +0 -9
  188. package/dist/utils/exclude-patterns.d.ts +0 -35
  189. package/dist/utils/exclude-patterns.d.ts.map +0 -1
  190. package/dist/utils/exclude-patterns.js +0 -78
  191. package/dist/utils/file-utils.d.ts +0 -37
  192. package/dist/utils/file-utils.d.ts.map +0 -1
  193. package/dist/utils/file-utils.js +0 -77
  194. package/dist/utils/fingerprint.d.ts +0 -25
  195. package/dist/utils/fingerprint.d.ts.map +0 -1
  196. package/dist/utils/fingerprint.js +0 -28
  197. package/dist/utils/git-info.d.ts +0 -14
  198. package/dist/utils/git-info.d.ts.map +0 -1
  199. package/dist/utils/git-info.js +0 -55
  200. package/dist/utils/index.d.ts +0 -4
  201. package/dist/utils/index.d.ts.map +0 -1
  202. package/dist/utils/index.js +0 -3
  203. package/dist/utils/progress.d.ts +0 -42
  204. package/dist/utils/progress.d.ts.map +0 -1
  205. package/dist/utils/progress.js +0 -165
  206. package/dist/utils/sarif-formatter.d.ts +0 -92
  207. package/dist/utils/sarif-formatter.d.ts.map +0 -1
  208. 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"}