@fourteensystems/prodcheck 0.3.0

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 (239) hide show
  1. package/README.md +252 -0
  2. package/bin/prodcheck.mjs +2 -0
  3. package/dist/cli/commands/baseline.d.ts +7 -0
  4. package/dist/cli/commands/baseline.d.ts.map +1 -0
  5. package/dist/cli/commands/baseline.js +22 -0
  6. package/dist/cli/commands/baseline.js.map +1 -0
  7. package/dist/cli/commands/ci.d.ts +14 -0
  8. package/dist/cli/commands/ci.d.ts.map +1 -0
  9. package/dist/cli/commands/ci.js +104 -0
  10. package/dist/cli/commands/ci.js.map +1 -0
  11. package/dist/cli/commands/explain.d.ts +2 -0
  12. package/dist/cli/commands/explain.d.ts.map +1 -0
  13. package/dist/cli/commands/explain.js +20 -0
  14. package/dist/cli/commands/explain.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts +7 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -0
  17. package/dist/cli/commands/init.js +127 -0
  18. package/dist/cli/commands/init.js.map +1 -0
  19. package/dist/cli/commands/rules.d.ts +2 -0
  20. package/dist/cli/commands/rules.d.ts.map +1 -0
  21. package/dist/cli/commands/rules.js +13 -0
  22. package/dist/cli/commands/rules.js.map +1 -0
  23. package/dist/cli/commands/scan.d.ts +10 -0
  24. package/dist/cli/commands/scan.d.ts.map +1 -0
  25. package/dist/cli/commands/scan.js +65 -0
  26. package/dist/cli/commands/scan.js.map +1 -0
  27. package/dist/cli/commands/waive.d.ts +8 -0
  28. package/dist/cli/commands/waive.d.ts.map +1 -0
  29. package/dist/cli/commands/waive.js +34 -0
  30. package/dist/cli/commands/waive.js.map +1 -0
  31. package/dist/cli/index.d.ts +2 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +64 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/engine/baseline.d.ts +11 -0
  36. package/dist/engine/baseline.d.ts.map +1 -0
  37. package/dist/engine/baseline.js +39 -0
  38. package/dist/engine/baseline.js.map +1 -0
  39. package/dist/engine/baseline.test.d.ts +2 -0
  40. package/dist/engine/baseline.test.d.ts.map +1 -0
  41. package/dist/engine/baseline.test.js +135 -0
  42. package/dist/engine/baseline.test.js.map +1 -0
  43. package/dist/engine/config.d.ts +8 -0
  44. package/dist/engine/config.d.ts.map +1 -0
  45. package/dist/engine/config.js +134 -0
  46. package/dist/engine/config.js.map +1 -0
  47. package/dist/engine/config.test.d.ts +2 -0
  48. package/dist/engine/config.test.d.ts.map +1 -0
  49. package/dist/engine/config.test.js +107 -0
  50. package/dist/engine/config.test.js.map +1 -0
  51. package/dist/engine/extensions/load.d.ts +11 -0
  52. package/dist/engine/extensions/load.d.ts.map +1 -0
  53. package/dist/engine/extensions/load.js +26 -0
  54. package/dist/engine/extensions/load.js.map +1 -0
  55. package/dist/engine/extensions/registry.d.ts +5 -0
  56. package/dist/engine/extensions/registry.d.ts.map +1 -0
  57. package/dist/engine/extensions/registry.js +11 -0
  58. package/dist/engine/extensions/registry.js.map +1 -0
  59. package/dist/engine/extensions/types.d.ts +51 -0
  60. package/dist/engine/extensions/types.d.ts.map +1 -0
  61. package/dist/engine/extensions/types.js +2 -0
  62. package/dist/engine/extensions/types.js.map +1 -0
  63. package/dist/engine/license.d.ts +40 -0
  64. package/dist/engine/license.d.ts.map +1 -0
  65. package/dist/engine/license.js +104 -0
  66. package/dist/engine/license.js.map +1 -0
  67. package/dist/engine/report.d.ts +5 -0
  68. package/dist/engine/report.d.ts.map +1 -0
  69. package/dist/engine/report.js +115 -0
  70. package/dist/engine/report.js.map +1 -0
  71. package/dist/engine/run.d.ts +11 -0
  72. package/dist/engine/run.d.ts.map +1 -0
  73. package/dist/engine/run.js +105 -0
  74. package/dist/engine/run.js.map +1 -0
  75. package/dist/engine/sarif.d.ts +3 -0
  76. package/dist/engine/sarif.d.ts.map +1 -0
  77. package/dist/engine/sarif.js +58 -0
  78. package/dist/engine/sarif.js.map +1 -0
  79. package/dist/engine/sarif.test.d.ts +2 -0
  80. package/dist/engine/sarif.test.d.ts.map +1 -0
  81. package/dist/engine/sarif.test.js +152 -0
  82. package/dist/engine/sarif.test.js.map +1 -0
  83. package/dist/engine/score.d.ts +13 -0
  84. package/dist/engine/score.d.ts.map +1 -0
  85. package/dist/engine/score.js +116 -0
  86. package/dist/engine/score.js.map +1 -0
  87. package/dist/engine/score.test.d.ts +2 -0
  88. package/dist/engine/score.test.d.ts.map +1 -0
  89. package/dist/engine/score.test.js +227 -0
  90. package/dist/engine/score.test.js.map +1 -0
  91. package/dist/engine/types.d.ts +123 -0
  92. package/dist/engine/types.d.ts.map +1 -0
  93. package/dist/engine/types.js +2 -0
  94. package/dist/engine/types.js.map +1 -0
  95. package/dist/engine/version.d.ts +5 -0
  96. package/dist/engine/version.d.ts.map +1 -0
  97. package/dist/engine/version.js +15 -0
  98. package/dist/engine/version.js.map +1 -0
  99. package/dist/engine/waivers.d.ts +9 -0
  100. package/dist/engine/waivers.d.ts.map +1 -0
  101. package/dist/engine/waivers.js +55 -0
  102. package/dist/engine/waivers.js.map +1 -0
  103. package/dist/engine/waivers.test.d.ts +2 -0
  104. package/dist/engine/waivers.test.d.ts.map +1 -0
  105. package/dist/engine/waivers.test.js +147 -0
  106. package/dist/engine/waivers.test.js.map +1 -0
  107. package/dist/index.d.ts +14 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +12 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/next/deps.d.ts +4 -0
  112. package/dist/next/deps.d.ts.map +1 -0
  113. package/dist/next/deps.js +118 -0
  114. package/dist/next/deps.js.map +1 -0
  115. package/dist/next/deps.test.d.ts +2 -0
  116. package/dist/next/deps.test.d.ts.map +1 -0
  117. package/dist/next/deps.test.js +249 -0
  118. package/dist/next/deps.test.js.map +1 -0
  119. package/dist/next/detect.d.ts +10 -0
  120. package/dist/next/detect.d.ts.map +1 -0
  121. package/dist/next/detect.js +57 -0
  122. package/dist/next/detect.js.map +1 -0
  123. package/dist/next/detect.test.d.ts +2 -0
  124. package/dist/next/detect.test.d.ts.map +1 -0
  125. package/dist/next/detect.test.js +74 -0
  126. package/dist/next/detect.test.js.map +1 -0
  127. package/dist/next/index.d.ts +5 -0
  128. package/dist/next/index.d.ts.map +1 -0
  129. package/dist/next/index.js +59 -0
  130. package/dist/next/index.js.map +1 -0
  131. package/dist/next/middleware.d.ts +3 -0
  132. package/dist/next/middleware.d.ts.map +1 -0
  133. package/dist/next/middleware.js +48 -0
  134. package/dist/next/middleware.js.map +1 -0
  135. package/dist/next/middleware.test.d.ts +2 -0
  136. package/dist/next/middleware.test.d.ts.map +1 -0
  137. package/dist/next/middleware.test.js +203 -0
  138. package/dist/next/middleware.test.js.map +1 -0
  139. package/dist/next/routes.d.ts +10 -0
  140. package/dist/next/routes.d.ts.map +1 -0
  141. package/dist/next/routes.js +172 -0
  142. package/dist/next/routes.js.map +1 -0
  143. package/dist/next/routes.test.d.ts +2 -0
  144. package/dist/next/routes.test.d.ts.map +1 -0
  145. package/dist/next/routes.test.js +175 -0
  146. package/dist/next/routes.test.js.map +1 -0
  147. package/dist/next/server-actions.d.ts +4 -0
  148. package/dist/next/server-actions.d.ts.map +1 -0
  149. package/dist/next/server-actions.js +107 -0
  150. package/dist/next/server-actions.js.map +1 -0
  151. package/dist/next/server-actions.test.d.ts +2 -0
  152. package/dist/next/server-actions.test.d.ts.map +1 -0
  153. package/dist/next/server-actions.test.js +138 -0
  154. package/dist/next/server-actions.test.js.map +1 -0
  155. package/dist/next/trpc.d.ts +3 -0
  156. package/dist/next/trpc.d.ts.map +1 -0
  157. package/dist/next/trpc.js +312 -0
  158. package/dist/next/trpc.js.map +1 -0
  159. package/dist/next/types.d.ts +144 -0
  160. package/dist/next/types.d.ts.map +1 -0
  161. package/dist/next/types.js +2 -0
  162. package/dist/next/types.js.map +1 -0
  163. package/dist/next/wrappers.d.ts +10 -0
  164. package/dist/next/wrappers.d.ts.map +1 -0
  165. package/dist/next/wrappers.js +536 -0
  166. package/dist/next/wrappers.js.map +1 -0
  167. package/dist/next/wrappers.test.d.ts +2 -0
  168. package/dist/next/wrappers.test.d.ts.map +1 -0
  169. package/dist/next/wrappers.test.js +361 -0
  170. package/dist/next/wrappers.test.js.map +1 -0
  171. package/dist/rules/auth-boundary-missing.d.ts +5 -0
  172. package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
  173. package/dist/rules/auth-boundary-missing.js +463 -0
  174. package/dist/rules/auth-boundary-missing.js.map +1 -0
  175. package/dist/rules/auth-boundary-missing.test.d.ts +2 -0
  176. package/dist/rules/auth-boundary-missing.test.d.ts.map +1 -0
  177. package/dist/rules/auth-boundary-missing.test.js +492 -0
  178. package/dist/rules/auth-boundary-missing.test.js.map +1 -0
  179. package/dist/rules/index.d.ts +12 -0
  180. package/dist/rules/index.d.ts.map +1 -0
  181. package/dist/rules/index.js +95 -0
  182. package/dist/rules/index.js.map +1 -0
  183. package/dist/rules/input-validation-missing.d.ts +5 -0
  184. package/dist/rules/input-validation-missing.d.ts.map +1 -0
  185. package/dist/rules/input-validation-missing.js +272 -0
  186. package/dist/rules/input-validation-missing.js.map +1 -0
  187. package/dist/rules/input-validation-missing.test.d.ts +2 -0
  188. package/dist/rules/input-validation-missing.test.d.ts.map +1 -0
  189. package/dist/rules/input-validation-missing.test.js +449 -0
  190. package/dist/rules/input-validation-missing.test.js.map +1 -0
  191. package/dist/rules/rate-limit-missing.d.ts +5 -0
  192. package/dist/rules/rate-limit-missing.d.ts.map +1 -0
  193. package/dist/rules/rate-limit-missing.js +316 -0
  194. package/dist/rules/rate-limit-missing.js.map +1 -0
  195. package/dist/rules/rate-limit-missing.test.d.ts +2 -0
  196. package/dist/rules/rate-limit-missing.test.d.ts.map +1 -0
  197. package/dist/rules/rate-limit-missing.test.js +381 -0
  198. package/dist/rules/rate-limit-missing.test.js.map +1 -0
  199. package/dist/rules/tenancy-scope-missing.d.ts +5 -0
  200. package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
  201. package/dist/rules/tenancy-scope-missing.js +149 -0
  202. package/dist/rules/tenancy-scope-missing.js.map +1 -0
  203. package/dist/rules/wrapper-unrecognized.d.ts +5 -0
  204. package/dist/rules/wrapper-unrecognized.d.ts.map +1 -0
  205. package/dist/rules/wrapper-unrecognized.js +81 -0
  206. package/dist/rules/wrapper-unrecognized.js.map +1 -0
  207. package/dist/util/hof.d.ts +22 -0
  208. package/dist/util/hof.d.ts.map +1 -0
  209. package/dist/util/hof.js +99 -0
  210. package/dist/util/hof.js.map +1 -0
  211. package/dist/util/hof.test.d.ts +2 -0
  212. package/dist/util/hof.test.d.ts.map +1 -0
  213. package/dist/util/hof.test.js +79 -0
  214. package/dist/util/hof.test.js.map +1 -0
  215. package/dist/util/monorepo.d.ts +6 -0
  216. package/dist/util/monorepo.d.ts.map +1 -0
  217. package/dist/util/monorepo.js +29 -0
  218. package/dist/util/monorepo.js.map +1 -0
  219. package/dist/util/outbound-fetch.d.ts +14 -0
  220. package/dist/util/outbound-fetch.d.ts.map +1 -0
  221. package/dist/util/outbound-fetch.js +59 -0
  222. package/dist/util/outbound-fetch.js.map +1 -0
  223. package/dist/util/outbound-fetch.test.d.ts +2 -0
  224. package/dist/util/outbound-fetch.test.d.ts.map +1 -0
  225. package/dist/util/outbound-fetch.test.js +83 -0
  226. package/dist/util/outbound-fetch.test.js.map +1 -0
  227. package/dist/util/paths.d.ts +6 -0
  228. package/dist/util/paths.d.ts.map +1 -0
  229. package/dist/util/paths.js +18 -0
  230. package/dist/util/paths.js.map +1 -0
  231. package/dist/util/resolve.d.ts +30 -0
  232. package/dist/util/resolve.d.ts.map +1 -0
  233. package/dist/util/resolve.js +306 -0
  234. package/dist/util/resolve.js.map +1 -0
  235. package/dist/util/resolve.test.d.ts +2 -0
  236. package/dist/util/resolve.test.d.ts.map +1 -0
  237. package/dist/util/resolve.test.js +186 -0
  238. package/dist/util/resolve.test.js.map +1 -0
  239. package/package.json +56 -0
@@ -0,0 +1,81 @@
1
+ export const RULE_ID = "WRAPPER-UNRECOGNIZED";
2
+ const SEVERITY_RANK = { critical: 4, high: 3, med: 2, low: 1 };
3
+ function capSeverity(computed, max) {
4
+ const maxRank = SEVERITY_RANK[max] ?? 4;
5
+ const computedRank = SEVERITY_RANK[computed] ?? 2;
6
+ return computedRank > maxRank ? max : computed;
7
+ }
8
+ export function run(index, config) {
9
+ const findings = [];
10
+ const maxSeverity = config.rules[RULE_ID]?.severity ?? "high";
11
+ for (const [name, wrapper] of index.wrappers.wrappers) {
12
+ // Skip fully resolved wrappers where both auth AND rate-limit are enforced
13
+ if (wrapper.resolved && wrapper.evidence.authEnforced && wrapper.evidence.rateLimitEnforced) {
14
+ continue;
15
+ }
16
+ // Determine what this wrapper WOULD have triggered
17
+ const wouldTrigger = [];
18
+ // Check if any wrapped routes are mutation routes (need auth)
19
+ const mutationFileSet = new Set(index.routes.mutationRoutes.map((r) => r.file));
20
+ const wrappedMutationFiles = wrapper.usageFiles.filter((f) => mutationFileSet.has(f));
21
+ if (wrappedMutationFiles.length > 0) {
22
+ if (!wrapper.resolved || !wrapper.evidence.authEnforced) {
23
+ wouldTrigger.push("AUTH-BOUNDARY-MISSING");
24
+ }
25
+ }
26
+ // Check if any wrapped routes are API routes (need rate limiting)
27
+ // Exclude routes that are already exempt from rate-limit (cron, webhook, etc.)
28
+ const apiFileSet = new Set(index.routes.all.filter((r) => r.isApi).map((r) => r.file));
29
+ const wrappedApiFiles = wrapper.usageFiles.filter((f) => apiFileSet.has(f) && !isRateLimitExemptPath(f));
30
+ if (wrappedApiFiles.length > 0) {
31
+ if (!wrapper.resolved || !wrapper.evidence.rateLimitEnforced) {
32
+ wouldTrigger.push("RATE-LIMIT-MISSING");
33
+ }
34
+ }
35
+ if (wouldTrigger.length === 0)
36
+ continue;
37
+ // Severity = high if wrapping mutation routes, med otherwise
38
+ const computedSeverity = wrappedMutationFiles.length > 0 ? "high" : "med";
39
+ const status = !wrapper.resolved
40
+ ? "could not be resolved"
41
+ : wrapper.evidence.authCallPresent && !wrapper.evidence.authEnforced
42
+ ? "calls auth but enforcement not proven"
43
+ : wrapper.evidence.rateLimitCallPresent && !wrapper.evidence.rateLimitEnforced
44
+ ? "calls rate limiter but enforcement not proven"
45
+ : "missing protections";
46
+ const evidence = [
47
+ `${name}() wraps ${wrapper.usageCount} route handler(s) (${wrapper.mutationRouteCount} mutation)`,
48
+ `Would have triggered: ${wouldTrigger.join(", ")}`,
49
+ `Top routes: ${wrapper.usageFiles.slice(0, 5).join(", ")}${wrapper.usageCount > 5 ? ` (+${wrapper.usageCount - 5} more)` : ""}`,
50
+ ];
51
+ if (wrapper.evidence.authCallPresent) {
52
+ evidence.push(`Auth call detected: ${wrapper.evidence.authDetails.join(", ")}`);
53
+ }
54
+ if (wrapper.evidence.rateLimitCallPresent) {
55
+ evidence.push(`Rate-limit call detected: ${wrapper.evidence.rateLimitDetails.join(", ")}`);
56
+ }
57
+ findings.push({
58
+ ruleId: RULE_ID,
59
+ severity: capSeverity(computedSeverity, maxSeverity),
60
+ confidence: "high",
61
+ message: `Wrapper "${name}" wraps ${wrapper.usageCount} handler(s); ${status}`,
62
+ file: wrapper.usageFiles[0],
63
+ evidence,
64
+ confidenceRationale: "High: wrapper usage is certain, but protection cannot be verified",
65
+ remediation: [
66
+ `If ${name} enforces auth: add "${name}" to hints.auth.functions`,
67
+ `If ${name} enforces rate limiting: add "${name}" to hints.rateLimit.wrappers`,
68
+ ...(wrapper.definitionFile
69
+ ? [`Verify wrapper implementation at ${wrapper.definitionFile}`]
70
+ : [`Wrapper definition could not be found — check import paths`]),
71
+ ],
72
+ tags: ["wrapper", "config"],
73
+ });
74
+ }
75
+ return findings;
76
+ }
77
+ /** Paths exempt from rate-limit — mirrors EXEMPT_PATH_PATTERNS + WEBHOOK patterns in rate-limit-missing. */
78
+ function isRateLimitExemptPath(file) {
79
+ return /\/cron\//.test(file) || /webhook/i.test(file) || /\/tasks\//.test(file);
80
+ }
81
+ //# sourceMappingURL=wrapper-unrecognized.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper-unrecognized.js","sourceRoot":"","sources":["../../src/rules/wrapper-unrecognized.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared Higher-Order Function (HOF) detection utilities.
3
+ * Used by wrapper analysis and rules.
4
+ */
5
+ /**
6
+ * Extract the ordered chain of HOF wrapper names from route source.
7
+ * E.g., `export const POST = withWorkspace(withErrorBoundary(handler))` → ["withWorkspace", "withErrorBoundary"]
8
+ * E.g., `export default withAuth(handler)` → ["withAuth"]
9
+ * Returns empty array if no HOF wrapper detected.
10
+ */
11
+ export declare function extractHofWrapperChain(src: string): string[];
12
+ /**
13
+ * Check if route source is exported via a specific known function (HOF pattern).
14
+ * E.g., `export const POST = withAuth(handler)` with functionName="withAuth" → true
15
+ */
16
+ export declare function isWrappedByFunction(src: string, functionName: string): boolean;
17
+ /**
18
+ * Find the import source for a given identifier in the source.
19
+ * Returns the module specifier or undefined if not imported (same-file definition).
20
+ */
21
+ export declare function findImportSource(src: string, identifierName: string): string | undefined;
22
+ //# sourceMappingURL=hof.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hof.d.ts","sourceRoot":"","sources":["../../src/util/hof.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB5D;AA2CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAY9E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoBxF"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Shared Higher-Order Function (HOF) detection utilities.
3
+ * Used by wrapper analysis and rules.
4
+ */
5
+ const HTTP_METHODS = "GET|POST|PUT|PATCH|DELETE";
6
+ /**
7
+ * Extract the ordered chain of HOF wrapper names from route source.
8
+ * E.g., `export const POST = withWorkspace(withErrorBoundary(handler))` → ["withWorkspace", "withErrorBoundary"]
9
+ * E.g., `export default withAuth(handler)` → ["withAuth"]
10
+ * Returns empty array if no HOF wrapper detected.
11
+ */
12
+ export function extractHofWrapperChain(src) {
13
+ const chains = [];
14
+ // Pattern 1: export const METHOD = wrapper(...)
15
+ const constPattern = new RegExp(`export\\s+(?:const|let|var)\\s+(?:${HTTP_METHODS})\\s*=\\s*(.+)`, "gm");
16
+ for (const m of src.matchAll(constPattern)) {
17
+ chains.push(...extractCallChain(m[1]));
18
+ }
19
+ // Pattern 2: export default wrapper(...)
20
+ const defaultPattern = /export\s+default\s+([a-zA-Z_]\w*)\s*\(/gm;
21
+ for (const m of src.matchAll(defaultPattern)) {
22
+ // Only add if not already captured
23
+ if (!chains.includes(m[1])) {
24
+ chains.push(m[1]);
25
+ }
26
+ }
27
+ return [...new Set(chains)];
28
+ }
29
+ /**
30
+ * Extract function names from a call chain expression.
31
+ * E.g., "withWorkspace(withErrorBoundary(handler))" → ["withWorkspace", "withErrorBoundary"]
32
+ * E.g., "withAuth(async (req) => { ... })" → ["withAuth"]
33
+ *
34
+ * Only extracts the leading nested call chain — stops at the first
35
+ * non-wrapper token (e.g., `async`, handler body) to avoid picking
36
+ * up identifiers deep inside the handler.
37
+ */
38
+ function extractCallChain(expr) {
39
+ const names = [];
40
+ let pos = 0;
41
+ while (pos < expr.length) {
42
+ // Skip whitespace
43
+ while (pos < expr.length && /\s/.test(expr[pos]))
44
+ pos++;
45
+ // Try to match identifier(
46
+ const remaining = expr.slice(pos);
47
+ const match = remaining.match(/^([a-zA-Z_]\w*)\s*\(/);
48
+ if (!match)
49
+ break;
50
+ const name = match[1];
51
+ if (SKIP_IDENTIFIERS.has(name))
52
+ break; // Not a wrapper, stop
53
+ names.push(name);
54
+ pos += match[0].length;
55
+ }
56
+ return names;
57
+ }
58
+ const SKIP_IDENTIFIERS = new Set([
59
+ "async", "await", "function", "return", "new", "typeof", "void",
60
+ "if", "else", "for", "while", "switch", "case", "try", "catch",
61
+ "throw", "const", "let", "var", "class", "import", "export",
62
+ "console", "Error", "Promise", "Array", "Object", "String", "Number",
63
+ "Boolean", "JSON", "Math", "Date", "RegExp", "Map", "Set",
64
+ "Response", "Request", "Headers", "NextResponse", "NextRequest",
65
+ ]);
66
+ /**
67
+ * Check if route source is exported via a specific known function (HOF pattern).
68
+ * E.g., `export const POST = withAuth(handler)` with functionName="withAuth" → true
69
+ */
70
+ export function isWrappedByFunction(src, functionName) {
71
+ const escaped = functionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
72
+ // export const METHOD = fn(...)
73
+ const hofPattern = new RegExp(`export\\s+(?:const|let|var)\\s+(?:${HTTP_METHODS})\\s*=\\s*${escaped}\\s*\\(`, "m");
74
+ if (hofPattern.test(src))
75
+ return true;
76
+ // export default fn(...)
77
+ const defaultPattern = new RegExp(`export\\s+default\\s+${escaped}\\s*\\(`, "m");
78
+ return defaultPattern.test(src);
79
+ }
80
+ /**
81
+ * Find the import source for a given identifier in the source.
82
+ * Returns the module specifier or undefined if not imported (same-file definition).
83
+ */
84
+ export function findImportSource(src, identifierName) {
85
+ const escaped = identifierName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
86
+ // Named import: import { name } from "source" or import { other as name } from "source"
87
+ const namedPattern = new RegExp(`import\\s*\\{[^}]*\\b(?:${escaped}|\\w+\\s+as\\s+${escaped})\\b[^}]*\\}\\s*from\\s*["']([^"']+)["']`);
88
+ const namedMatch = src.match(namedPattern);
89
+ if (namedMatch)
90
+ return namedMatch[1];
91
+ // Default import: import name from "source"
92
+ const defaultPattern = new RegExp(`import\\s+${escaped}\\s+from\\s*["']([^"']+)["']`);
93
+ const defaultMatch = src.match(defaultPattern);
94
+ if (defaultMatch)
95
+ return defaultMatch[1];
96
+ // Namespace import + property access won't match here — that's fine for v1
97
+ return undefined;
98
+ }
99
+ //# sourceMappingURL=hof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hof.js","sourceRoot":"","sources":["../../src/util/hof.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hof.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hof.test.d.ts","sourceRoot":"","sources":["../../src/util/hof.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { extractHofWrapperChain, isWrappedByFunction, findImportSource } from "./hof.js";
3
+ describe("extractHofWrapperChain", () => {
4
+ it("extracts single wrapper from const export", () => {
5
+ const src = `export const POST = withWorkspace(async (req) => { return Response.json({}); });`;
6
+ expect(extractHofWrapperChain(src)).toEqual(["withWorkspace"]);
7
+ });
8
+ it("extracts chained wrappers", () => {
9
+ const src = `export const POST = withWorkspace(withErrorBoundary(async (req) => { return Response.json({}); }));`;
10
+ expect(extractHofWrapperChain(src)).toEqual(["withWorkspace", "withErrorBoundary"]);
11
+ });
12
+ it("extracts from export default", () => {
13
+ const src = `export default withAuth(handler);`;
14
+ expect(extractHofWrapperChain(src)).toEqual(["withAuth"]);
15
+ });
16
+ it("extracts from multiple method exports", () => {
17
+ const src = `
18
+ export const GET = withWorkspace(getHandler);
19
+ export const POST = withWorkspace(postHandler);
20
+ `;
21
+ // Should deduplicate
22
+ expect(extractHofWrapperChain(src)).toEqual(["withWorkspace"]);
23
+ });
24
+ it("returns empty for regular function exports", () => {
25
+ const src = `export async function POST(req: Request) { return Response.json({}); }`;
26
+ expect(extractHofWrapperChain(src)).toEqual([]);
27
+ });
28
+ it("skips JavaScript keywords", () => {
29
+ const src = `export const POST = async function(req) { return new Response(); };`;
30
+ // "async" and "Response" should be filtered out
31
+ const chain = extractHofWrapperChain(src);
32
+ expect(chain).not.toContain("async");
33
+ expect(chain).not.toContain("Response");
34
+ });
35
+ });
36
+ describe("isWrappedByFunction", () => {
37
+ it("matches const export pattern", () => {
38
+ const src = `export const POST = withAuth(async (req) => {});`;
39
+ expect(isWrappedByFunction(src, "withAuth")).toBe(true);
40
+ });
41
+ it("matches default export pattern", () => {
42
+ const src = `export default withAuth(handler);`;
43
+ expect(isWrappedByFunction(src, "withAuth")).toBe(true);
44
+ });
45
+ it("does not match different function", () => {
46
+ const src = `export const POST = withAuth(handler);`;
47
+ expect(isWrappedByFunction(src, "withRateLimit")).toBe(false);
48
+ });
49
+ it("does not match function call inside handler", () => {
50
+ const src = `export async function POST(req: Request) { withAuth(); }`;
51
+ expect(isWrappedByFunction(src, "withAuth")).toBe(false);
52
+ });
53
+ });
54
+ describe("findImportSource", () => {
55
+ it("finds named import source", () => {
56
+ const src = `import { withWorkspace } from "@/lib/auth";`;
57
+ expect(findImportSource(src, "withWorkspace")).toBe("@/lib/auth");
58
+ });
59
+ it("finds default import source", () => {
60
+ const src = `import withAuth from "@/lib/auth";`;
61
+ expect(findImportSource(src, "withAuth")).toBe("@/lib/auth");
62
+ });
63
+ it("returns undefined for same-file definition", () => {
64
+ const src = `
65
+ function withAuth(handler: any) { return handler; }
66
+ export const POST = withAuth(handler);
67
+ `;
68
+ expect(findImportSource(src, "withAuth")).toBeUndefined();
69
+ });
70
+ it("handles aliased imports", () => {
71
+ const src = `import { myAuth as withAuth } from "@/lib/auth";`;
72
+ expect(findImportSource(src, "withAuth")).toBe("@/lib/auth");
73
+ });
74
+ it("handles multiple named imports", () => {
75
+ const src = `import { foo, withWorkspace, bar } from "@/lib/auth";`;
76
+ expect(findImportSource(src, "withWorkspace")).toBe("@/lib/auth");
77
+ });
78
+ });
79
+ //# sourceMappingURL=hof.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hof.test.js","sourceRoot":"","sources":["../../src/util/hof.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Walk up from startDir to find a monorepo workspace root.
3
+ * Returns null if no workspace root is found (i.e., not a monorepo).
4
+ */
5
+ export declare function findWorkspaceRoot(startDir: string): string | null;
6
+ //# sourceMappingURL=monorepo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monorepo.d.ts","sourceRoot":"","sources":["../../src/util/monorepo.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjE"}
@@ -0,0 +1,29 @@
1
+ import path from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ /**
4
+ * Walk up from startDir to find a monorepo workspace root.
5
+ * Returns null if no workspace root is found (i.e., not a monorepo).
6
+ */
7
+ export function findWorkspaceRoot(startDir) {
8
+ let dir = path.dirname(startDir);
9
+ while (dir !== path.dirname(dir)) {
10
+ if (existsSync(path.join(dir, "pnpm-workspace.yaml")))
11
+ return dir;
12
+ if (existsSync(path.join(dir, "turbo.json")))
13
+ return dir;
14
+ const pkgPath = path.join(dir, "package.json");
15
+ if (existsSync(pkgPath)) {
16
+ try {
17
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
18
+ if (pkg.workspaces)
19
+ return dir;
20
+ }
21
+ catch {
22
+ // Ignore parse errors in parent package.json
23
+ }
24
+ }
25
+ dir = path.dirname(dir);
26
+ }
27
+ return null;
28
+ }
29
+ //# sourceMappingURL=monorepo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monorepo.js","sourceRoot":"","sources":["../../src/util/monorepo.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAClE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC/C,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;gBACtD,IAAI,GAAG,CAAC,UAAU;oBAAE,OAAO,GAAG,CAAC;YACjC,CAAC;YAAC,MAAM,CAAC;gBACP,6CAA6C;YAC/C,CAAC;QACH,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Detect outbound HTTP fetch calls with user-influenced URLs.
3
+ * Used by RL and INPUT-VALIDATION rules to identify SSRF surface
4
+ * on public-intent endpoints.
5
+ */
6
+ export interface OutboundFetchResult {
7
+ hasOutboundFetch: boolean;
8
+ hasUserInfluencedUrl: boolean;
9
+ /** True when both outbound fetch AND user-influenced URL are present */
10
+ isRisky: boolean;
11
+ evidence: string[];
12
+ }
13
+ export declare function detectOutboundFetcher(src: string): OutboundFetchResult;
14
+ //# sourceMappingURL=outbound-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-fetch.d.ts","sourceRoot":"","sources":["../../src/util/outbound-fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA8BD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CA6BtE"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Detect outbound HTTP fetch calls with user-influenced URLs.
3
+ * Used by RL and INPUT-VALIDATION rules to identify SSRF surface
4
+ * on public-intent endpoints.
5
+ */
6
+ /**
7
+ * Outbound fetch patterns — HTTP client calls that make external requests.
8
+ * Excludes false positives like fetchUser(), fetchData() by requiring
9
+ * non-word char or start-of-line before "fetch".
10
+ */
11
+ const OUTBOUND_FETCH_PATTERNS = [
12
+ { pattern: /(?:^|[^.\w])fetch\s*\(/, label: "fetch()" },
13
+ { pattern: /axios\s*[.(]/, label: "axios" },
14
+ { pattern: /(?:^|[^.\w])got\s*[.(]/, label: "got()" },
15
+ { pattern: /undici\.request\s*\(/, label: "undici.request()" },
16
+ { pattern: /https?\.(?:get|request)\s*\(/, label: "http.get/request()" },
17
+ ];
18
+ /**
19
+ * User-influenced URL patterns — evidence that the fetch target
20
+ * is constructed from user-supplied request data.
21
+ */
22
+ const USER_INPUT_PATTERNS = [
23
+ { pattern: /searchParams\.get\s*\(/, label: "reads searchParams" },
24
+ { pattern: /searchParams\.\w/, label: "accesses searchParams" },
25
+ { pattern: /new\s+URL\s*\(\s*(?:request|req)\.url/, label: "parses request URL" },
26
+ { pattern: /(?:request|req)\.url\b/, label: "reads request.url" },
27
+ { pattern: /(?:request|req)\.json\s*\(/, label: "reads request body" },
28
+ { pattern: /req\.body\b/, label: "reads req.body" },
29
+ { pattern: /req\.query\b/, label: "reads req.query" },
30
+ { pattern: /params\.\w/, label: "reads route params" },
31
+ ];
32
+ export function detectOutboundFetcher(src) {
33
+ const evidence = [];
34
+ let hasOutboundFetch = false;
35
+ let hasUserInfluencedUrl = false;
36
+ for (const { pattern, label } of OUTBOUND_FETCH_PATTERNS) {
37
+ if (pattern.test(src)) {
38
+ hasOutboundFetch = true;
39
+ evidence.push(`outbound HTTP call: ${label}`);
40
+ break; // one is enough
41
+ }
42
+ }
43
+ if (hasOutboundFetch) {
44
+ for (const { pattern, label } of USER_INPUT_PATTERNS) {
45
+ if (pattern.test(src)) {
46
+ hasUserInfluencedUrl = true;
47
+ evidence.push(`user-controlled input: ${label}`);
48
+ break; // one is enough
49
+ }
50
+ }
51
+ }
52
+ return {
53
+ hasOutboundFetch,
54
+ hasUserInfluencedUrl,
55
+ isRisky: hasOutboundFetch && hasUserInfluencedUrl,
56
+ evidence,
57
+ };
58
+ }
59
+ //# sourceMappingURL=outbound-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-fetch.js","sourceRoot":"","sources":["../../src/util/outbound-fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH;;;;GAIG;AACH,MAAM,uBAAuB,GAAyC;IACpE,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,SAAS,EAAE;IACvD,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE;IAC3C,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,OAAO,EAAE;IACrD,EAAE,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,EAAE;IAC9D,EAAE,OAAO,EAAE,8BAA8B,EAAE,KAAK,EAAE,oBAAoB,EAAE;CACzE,CAAC;AAEF;;;GAGG;AACH,MAAM,mBAAmB,GAAyC;IAChE,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,oBAAoB,EAAE;IAClE,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,uBAAuB,EAAE;IAC/D,EAAE,OAAO,EAAE,uCAAuC,EAAE,KAAK,EAAE,oBAAoB,EAAE;IACjF,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,mBAAmB,EAAE;IACjE,EAAE,OAAO,EAAE,4BAA4B,EAAE,KAAK,EAAE,oBAAoB,EAAE;IACtE,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACnD,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,iBAAiB,EAAE;IACrD,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,oBAAoB,EAAE;CACvD,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,IAAI,oBAAoB,GAAG,KAAK,CAAC;IAEjC,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,uBAAuB,EAAE,CAAC;QACzD,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,gBAAgB,GAAG,IAAI,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;YAC9C,MAAM,CAAC,gBAAgB;QACzB,CAAC;IACH,CAAC;IAED,IAAI,gBAAgB,EAAE,CAAC;QACrB,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,mBAAmB,EAAE,CAAC;YACrD,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,oBAAoB,GAAG,IAAI,CAAC;gBAC5B,QAAQ,CAAC,IAAI,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;gBACjD,MAAM,CAAC,gBAAgB;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,gBAAgB;QAChB,oBAAoB;QACpB,OAAO,EAAE,gBAAgB,IAAI,oBAAoB;QACjD,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=outbound-fetch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-fetch.test.d.ts","sourceRoot":"","sources":["../../src/util/outbound-fetch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { detectOutboundFetcher } from "./outbound-fetch.js";
3
+ describe("detectOutboundFetcher", () => {
4
+ it("detects fetch() with user-influenced URL", () => {
5
+ const src = `
6
+ export async function GET(request: Request) {
7
+ const url = new URL(request.url).searchParams.get("target");
8
+ const response = await fetch(url);
9
+ return Response.json(await response.json());
10
+ }`;
11
+ const result = detectOutboundFetcher(src);
12
+ expect(result.hasOutboundFetch).toBe(true);
13
+ expect(result.hasUserInfluencedUrl).toBe(true);
14
+ expect(result.isRisky).toBe(true);
15
+ expect(result.evidence.length).toBeGreaterThanOrEqual(2);
16
+ });
17
+ it("detects axios with user-influenced URL", () => {
18
+ const src = `
19
+ export async function POST(request: Request) {
20
+ const body = await request.json();
21
+ const response = await axios.get(body.url);
22
+ return Response.json(response.data);
23
+ }`;
24
+ const result = detectOutboundFetcher(src);
25
+ expect(result.isRisky).toBe(true);
26
+ });
27
+ it("does NOT flag fetch with hardcoded URL", () => {
28
+ const src = `
29
+ export async function GET() {
30
+ const response = await fetch("https://api.example.com/data");
31
+ return Response.json(await response.json());
32
+ }`;
33
+ const result = detectOutboundFetcher(src);
34
+ expect(result.hasOutboundFetch).toBe(true);
35
+ expect(result.hasUserInfluencedUrl).toBe(false);
36
+ expect(result.isRisky).toBe(false);
37
+ });
38
+ it("does NOT flag when no fetch present", () => {
39
+ const src = `
40
+ export async function POST(request: Request) {
41
+ const body = await request.json();
42
+ await prisma.user.create({ data: body });
43
+ return Response.json({ ok: true });
44
+ }`;
45
+ const result = detectOutboundFetcher(src);
46
+ expect(result.hasOutboundFetch).toBe(false);
47
+ expect(result.isRisky).toBe(false);
48
+ expect(result.evidence).toHaveLength(0);
49
+ });
50
+ it("does NOT match fetchUser() as outbound fetch", () => {
51
+ const src = `
52
+ export async function GET(request: Request) {
53
+ const url = new URL(request.url);
54
+ const user = await fetchUser(url.searchParams.get("id"));
55
+ return Response.json(user);
56
+ }`;
57
+ const result = detectOutboundFetcher(src);
58
+ expect(result.hasOutboundFetch).toBe(false);
59
+ });
60
+ it("detects got() with user input", () => {
61
+ const src = `
62
+ import got from "got";
63
+ export async function GET(request: Request) {
64
+ const target = new URL(request.url).searchParams.get("url");
65
+ const response = await got(target);
66
+ return Response.json(response.body);
67
+ }`;
68
+ const result = detectOutboundFetcher(src);
69
+ expect(result.isRisky).toBe(true);
70
+ });
71
+ it("detects undici.request with user input", () => {
72
+ const src = `
73
+ import { request as undiciRequest } from "undici";
74
+ export async function POST(req: Request) {
75
+ const body = await req.json();
76
+ const { body: responseBody } = await undici.request(body.endpoint);
77
+ return Response.json(responseBody);
78
+ }`;
79
+ const result = detectOutboundFetcher(src);
80
+ expect(result.isRisky).toBe(true);
81
+ });
82
+ });
83
+ //# sourceMappingURL=outbound-fetch.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-fetch.test.js","sourceRoot":"","sources":["../../src/util/outbound-fetch.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG;;;;;EAKd,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,GAAG,GAAG;;;;;EAKd,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,GAAG,GAAG;;;;EAId,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,GAAG,GAAG;;;;;EAKd,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,GAAG,GAAG;;;;;EAKd,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG;;;;;;EAMd,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,GAAG,GAAG;;;;;;EAMd,CAAC;QACC,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Check if a file path matches any of the allowlist glob patterns.
3
+ * Used by rules to skip files/routes that the user has marked as exempt.
4
+ */
5
+ export declare function isAllowlisted(filePath: string, allowlistPaths: string[]): boolean;
6
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/util/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAGjF"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Check if a file path matches any of the allowlist glob patterns.
3
+ * Used by rules to skip files/routes that the user has marked as exempt.
4
+ */
5
+ export function isAllowlisted(filePath, allowlistPaths) {
6
+ if (allowlistPaths.length === 0)
7
+ return false;
8
+ return allowlistPaths.some((pattern) => matchGlob(filePath, pattern));
9
+ }
10
+ function matchGlob(filePath, pattern) {
11
+ const regexStr = pattern
12
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
13
+ .replace(/\*\*/g, "{{GLOBSTAR}}")
14
+ .replace(/\*/g, "[^/]*")
15
+ .replace(/\{\{GLOBSTAR\}\}/g, ".*");
16
+ return new RegExp(`^${regexStr}$`).test(filePath);
17
+ }
18
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/util/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,cAAwB;IACtE,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,OAAe;IAClD,MAAM,QAAQ,GAAG,OAAO;SACrB,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC;SACpC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC;SAChC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;SACvB,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACtC,OAAO,IAAI,MAAM,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,30 @@
1
+ export interface TsconfigPaths {
2
+ baseUrl?: string;
3
+ paths: Record<string, string[]>;
4
+ }
5
+ export interface ResolveOptions {
6
+ rootDir: string;
7
+ tsconfigPaths?: TsconfigPaths;
8
+ }
9
+ /**
10
+ * Load tsconfig.json (with extends chain) and extract compilerOptions.paths + baseUrl.
11
+ * Falls back to tsconfig.app.json if tsconfig.json not found.
12
+ */
13
+ export declare function loadTsconfigPaths(rootDir: string): TsconfigPaths | undefined;
14
+ /**
15
+ * Resolve an import specifier to a relative file path within rootDir.
16
+ * Returns the relative path (e.g., "src/lib/auth.ts") or undefined if unresolvable.
17
+ */
18
+ export declare function resolveImportPath(fromFile: string, importPath: string, opts: ResolveOptions): string | undefined;
19
+ /**
20
+ * Follow barrel re-exports to find the actual definition file.
21
+ * E.g., index.ts → export { withWorkspace } from "./workspace" → workspace.ts
22
+ *
23
+ * Returns the final file path and source, or undefined if not found.
24
+ * Follows up to maxHops (default 5) with cycle detection.
25
+ */
26
+ export declare function followReExport(symbolName: string, startFile: string, opts: ResolveOptions, maxHops?: number): {
27
+ file: string;
28
+ src: string;
29
+ } | undefined;
30
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/util/resolve.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAS5E;AAyID;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,cAAc,GACnB,MAAM,GAAG,SAAS,CA6BpB;AA4ED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,MAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAuD3C"}