@kevinrabun/judges 2.3.0 → 3.0.1

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 (321) hide show
  1. package/README.md +177 -12
  2. package/dist/api.d.ts +40 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +56 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/ast/cross-file-taint.d.ts +43 -0
  7. package/dist/ast/cross-file-taint.d.ts.map +1 -0
  8. package/dist/ast/cross-file-taint.js +713 -0
  9. package/dist/ast/cross-file-taint.js.map +1 -0
  10. package/dist/ast/index.d.ts +4 -0
  11. package/dist/ast/index.d.ts.map +1 -1
  12. package/dist/ast/index.js +5 -0
  13. package/dist/ast/index.js.map +1 -1
  14. package/dist/ast/structural-parser.d.ts.map +1 -1
  15. package/dist/ast/structural-parser.js +66 -11
  16. package/dist/ast/structural-parser.js.map +1 -1
  17. package/dist/ast/taint-tracker.d.ts +35 -0
  18. package/dist/ast/taint-tracker.d.ts.map +1 -0
  19. package/dist/ast/taint-tracker.js +518 -0
  20. package/dist/ast/taint-tracker.js.map +1 -0
  21. package/dist/ast/types.d.ts +2 -0
  22. package/dist/ast/types.d.ts.map +1 -1
  23. package/dist/ast/typescript-ast.d.ts.map +1 -1
  24. package/dist/ast/typescript-ast.js +25 -5
  25. package/dist/ast/typescript-ast.js.map +1 -1
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +10 -9
  28. package/dist/config.js.map +1 -1
  29. package/dist/dedup.d.ts +19 -0
  30. package/dist/dedup.d.ts.map +1 -0
  31. package/dist/dedup.js +222 -0
  32. package/dist/dedup.js.map +1 -0
  33. package/dist/errors.d.ts +37 -0
  34. package/dist/errors.d.ts.map +1 -0
  35. package/dist/errors.js +57 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/evaluators/accessibility.d.ts +1 -1
  38. package/dist/evaluators/accessibility.d.ts.map +1 -1
  39. package/dist/evaluators/accessibility.js +22 -16
  40. package/dist/evaluators/accessibility.js.map +1 -1
  41. package/dist/evaluators/agent-instructions.d.ts +1 -1
  42. package/dist/evaluators/agent-instructions.d.ts.map +1 -1
  43. package/dist/evaluators/agent-instructions.js +1 -2
  44. package/dist/evaluators/agent-instructions.js.map +1 -1
  45. package/dist/evaluators/ai-code-safety.d.ts +1 -1
  46. package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
  47. package/dist/evaluators/ai-code-safety.js +2 -6
  48. package/dist/evaluators/ai-code-safety.js.map +1 -1
  49. package/dist/evaluators/api-design.d.ts +1 -1
  50. package/dist/evaluators/api-design.d.ts.map +1 -1
  51. package/dist/evaluators/api-design.js +2 -1
  52. package/dist/evaluators/api-design.js.map +1 -1
  53. package/dist/evaluators/app-builder.d.ts +34 -0
  54. package/dist/evaluators/app-builder.d.ts.map +1 -0
  55. package/dist/evaluators/app-builder.js +156 -0
  56. package/dist/evaluators/app-builder.js.map +1 -0
  57. package/dist/evaluators/authentication.d.ts +1 -1
  58. package/dist/evaluators/authentication.d.ts.map +1 -1
  59. package/dist/evaluators/authentication.js +2 -66
  60. package/dist/evaluators/authentication.js.map +1 -1
  61. package/dist/evaluators/backwards-compatibility.d.ts +1 -1
  62. package/dist/evaluators/backwards-compatibility.d.ts.map +1 -1
  63. package/dist/evaluators/backwards-compatibility.js.map +1 -1
  64. package/dist/evaluators/caching.d.ts +1 -1
  65. package/dist/evaluators/caching.d.ts.map +1 -1
  66. package/dist/evaluators/caching.js.map +1 -1
  67. package/dist/evaluators/ci-cd.d.ts +1 -1
  68. package/dist/evaluators/ci-cd.d.ts.map +1 -1
  69. package/dist/evaluators/ci-cd.js +4 -4
  70. package/dist/evaluators/ci-cd.js.map +1 -1
  71. package/dist/evaluators/cloud-readiness.d.ts +1 -1
  72. package/dist/evaluators/cloud-readiness.d.ts.map +1 -1
  73. package/dist/evaluators/cloud-readiness.js.map +1 -1
  74. package/dist/evaluators/code-structure.d.ts +1 -1
  75. package/dist/evaluators/code-structure.d.ts.map +1 -1
  76. package/dist/evaluators/code-structure.js +2 -6
  77. package/dist/evaluators/code-structure.js.map +1 -1
  78. package/dist/evaluators/compliance.d.ts +1 -1
  79. package/dist/evaluators/compliance.d.ts.map +1 -1
  80. package/dist/evaluators/compliance.js +15 -6
  81. package/dist/evaluators/compliance.js.map +1 -1
  82. package/dist/evaluators/concurrency.d.ts +1 -1
  83. package/dist/evaluators/concurrency.d.ts.map +1 -1
  84. package/dist/evaluators/concurrency.js +9 -4
  85. package/dist/evaluators/concurrency.js.map +1 -1
  86. package/dist/evaluators/configuration-management.d.ts +1 -1
  87. package/dist/evaluators/configuration-management.d.ts.map +1 -1
  88. package/dist/evaluators/configuration-management.js +7 -2
  89. package/dist/evaluators/configuration-management.js.map +1 -1
  90. package/dist/evaluators/cost-effectiveness.d.ts +1 -1
  91. package/dist/evaluators/cost-effectiveness.d.ts.map +1 -1
  92. package/dist/evaluators/cost-effectiveness.js +1 -3
  93. package/dist/evaluators/cost-effectiveness.js.map +1 -1
  94. package/dist/evaluators/cybersecurity.d.ts +1 -1
  95. package/dist/evaluators/cybersecurity.d.ts.map +1 -1
  96. package/dist/evaluators/cybersecurity.js +50 -1
  97. package/dist/evaluators/cybersecurity.js.map +1 -1
  98. package/dist/evaluators/data-security.d.ts +1 -1
  99. package/dist/evaluators/data-security.d.ts.map +1 -1
  100. package/dist/evaluators/data-security.js +9 -66
  101. package/dist/evaluators/data-security.js.map +1 -1
  102. package/dist/evaluators/data-sovereignty.d.ts +1 -1
  103. package/dist/evaluators/data-sovereignty.d.ts.map +1 -1
  104. package/dist/evaluators/data-sovereignty.js +4 -2
  105. package/dist/evaluators/data-sovereignty.js.map +1 -1
  106. package/dist/evaluators/database.d.ts +1 -1
  107. package/dist/evaluators/database.d.ts.map +1 -1
  108. package/dist/evaluators/database.js +3 -1
  109. package/dist/evaluators/database.js.map +1 -1
  110. package/dist/evaluators/dependencies.d.ts +6 -0
  111. package/dist/evaluators/dependencies.d.ts.map +1 -0
  112. package/dist/evaluators/dependencies.js +204 -0
  113. package/dist/evaluators/dependencies.js.map +1 -0
  114. package/dist/evaluators/dependency-health.d.ts +1 -1
  115. package/dist/evaluators/dependency-health.d.ts.map +1 -1
  116. package/dist/evaluators/dependency-health.js +198 -6
  117. package/dist/evaluators/dependency-health.js.map +1 -1
  118. package/dist/evaluators/documentation.d.ts +1 -1
  119. package/dist/evaluators/documentation.d.ts.map +1 -1
  120. package/dist/evaluators/documentation.js +5 -2
  121. package/dist/evaluators/documentation.js.map +1 -1
  122. package/dist/evaluators/error-handling.d.ts +1 -1
  123. package/dist/evaluators/error-handling.d.ts.map +1 -1
  124. package/dist/evaluators/error-handling.js.map +1 -1
  125. package/dist/evaluators/ethics-bias.d.ts +1 -1
  126. package/dist/evaluators/ethics-bias.d.ts.map +1 -1
  127. package/dist/evaluators/ethics-bias.js +10 -5
  128. package/dist/evaluators/ethics-bias.js.map +1 -1
  129. package/dist/evaluators/framework-safety.d.ts +13 -0
  130. package/dist/evaluators/framework-safety.d.ts.map +1 -0
  131. package/dist/evaluators/framework-safety.js +424 -0
  132. package/dist/evaluators/framework-safety.js.map +1 -0
  133. package/dist/evaluators/index.d.ts +20 -24
  134. package/dist/evaluators/index.d.ts.map +1 -1
  135. package/dist/evaluators/index.js +294 -728
  136. package/dist/evaluators/index.js.map +1 -1
  137. package/dist/evaluators/internationalization.d.ts +1 -1
  138. package/dist/evaluators/internationalization.d.ts.map +1 -1
  139. package/dist/evaluators/internationalization.js +14 -6
  140. package/dist/evaluators/internationalization.js.map +1 -1
  141. package/dist/evaluators/logging-privacy.d.ts +1 -1
  142. package/dist/evaluators/logging-privacy.d.ts.map +1 -1
  143. package/dist/evaluators/logging-privacy.js +3 -1
  144. package/dist/evaluators/logging-privacy.js.map +1 -1
  145. package/dist/evaluators/maintainability.d.ts +1 -1
  146. package/dist/evaluators/maintainability.d.ts.map +1 -1
  147. package/dist/evaluators/maintainability.js +15 -9
  148. package/dist/evaluators/maintainability.js.map +1 -1
  149. package/dist/evaluators/observability.d.ts +1 -1
  150. package/dist/evaluators/observability.d.ts.map +1 -1
  151. package/dist/evaluators/observability.js +2 -1
  152. package/dist/evaluators/observability.js.map +1 -1
  153. package/dist/evaluators/performance.d.ts +1 -1
  154. package/dist/evaluators/performance.d.ts.map +1 -1
  155. package/dist/evaluators/performance.js +181 -4
  156. package/dist/evaluators/performance.js.map +1 -1
  157. package/dist/evaluators/portability.d.ts +1 -1
  158. package/dist/evaluators/portability.d.ts.map +1 -1
  159. package/dist/evaluators/portability.js +2 -1
  160. package/dist/evaluators/portability.js.map +1 -1
  161. package/dist/evaluators/project.d.ts +16 -0
  162. package/dist/evaluators/project.d.ts.map +1 -0
  163. package/dist/evaluators/project.js +353 -0
  164. package/dist/evaluators/project.js.map +1 -0
  165. package/dist/evaluators/rate-limiting.d.ts +1 -1
  166. package/dist/evaluators/rate-limiting.d.ts.map +1 -1
  167. package/dist/evaluators/rate-limiting.js.map +1 -1
  168. package/dist/evaluators/reliability.d.ts +1 -1
  169. package/dist/evaluators/reliability.d.ts.map +1 -1
  170. package/dist/evaluators/reliability.js.map +1 -1
  171. package/dist/evaluators/scalability.d.ts +1 -1
  172. package/dist/evaluators/scalability.d.ts.map +1 -1
  173. package/dist/evaluators/scalability.js +3 -1
  174. package/dist/evaluators/scalability.js.map +1 -1
  175. package/dist/evaluators/shared.d.ts +24 -2
  176. package/dist/evaluators/shared.d.ts.map +1 -1
  177. package/dist/evaluators/shared.js +190 -2
  178. package/dist/evaluators/shared.js.map +1 -1
  179. package/dist/evaluators/software-practices.d.ts +1 -1
  180. package/dist/evaluators/software-practices.d.ts.map +1 -1
  181. package/dist/evaluators/software-practices.js +3 -3
  182. package/dist/evaluators/software-practices.js.map +1 -1
  183. package/dist/evaluators/testing.d.ts +1 -1
  184. package/dist/evaluators/testing.d.ts.map +1 -1
  185. package/dist/evaluators/testing.js +12 -4
  186. package/dist/evaluators/testing.js.map +1 -1
  187. package/dist/evaluators/ux.d.ts +1 -1
  188. package/dist/evaluators/ux.d.ts.map +1 -1
  189. package/dist/evaluators/ux.js.map +1 -1
  190. package/dist/evaluators/v2.d.ts +1 -1
  191. package/dist/evaluators/v2.d.ts.map +1 -1
  192. package/dist/evaluators/v2.js +13 -35
  193. package/dist/evaluators/v2.js.map +1 -1
  194. package/dist/formatters/sarif.d.ts +75 -0
  195. package/dist/formatters/sarif.d.ts.map +1 -0
  196. package/dist/formatters/sarif.js +93 -0
  197. package/dist/formatters/sarif.js.map +1 -0
  198. package/dist/index.d.ts +4 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +9 -806
  201. package/dist/index.js.map +1 -1
  202. package/dist/judges/accessibility.d.ts +1 -1
  203. package/dist/judges/accessibility.d.ts.map +1 -1
  204. package/dist/judges/agent-instructions.d.ts +1 -1
  205. package/dist/judges/agent-instructions.d.ts.map +1 -1
  206. package/dist/judges/ai-code-safety.d.ts +1 -1
  207. package/dist/judges/ai-code-safety.d.ts.map +1 -1
  208. package/dist/judges/api-design.d.ts +1 -1
  209. package/dist/judges/api-design.d.ts.map +1 -1
  210. package/dist/judges/authentication.d.ts +1 -1
  211. package/dist/judges/authentication.d.ts.map +1 -1
  212. package/dist/judges/backwards-compatibility.d.ts +1 -1
  213. package/dist/judges/backwards-compatibility.d.ts.map +1 -1
  214. package/dist/judges/caching.d.ts +1 -1
  215. package/dist/judges/caching.d.ts.map +1 -1
  216. package/dist/judges/ci-cd.d.ts +1 -1
  217. package/dist/judges/ci-cd.d.ts.map +1 -1
  218. package/dist/judges/cloud-readiness.d.ts +1 -1
  219. package/dist/judges/cloud-readiness.d.ts.map +1 -1
  220. package/dist/judges/code-structure.d.ts +1 -1
  221. package/dist/judges/code-structure.d.ts.map +1 -1
  222. package/dist/judges/code-structure.js +7 -1
  223. package/dist/judges/code-structure.js.map +1 -1
  224. package/dist/judges/compliance.d.ts +1 -1
  225. package/dist/judges/compliance.d.ts.map +1 -1
  226. package/dist/judges/concurrency.d.ts +1 -1
  227. package/dist/judges/concurrency.d.ts.map +1 -1
  228. package/dist/judges/configuration-management.d.ts +1 -1
  229. package/dist/judges/configuration-management.d.ts.map +1 -1
  230. package/dist/judges/cost-effectiveness.d.ts +1 -1
  231. package/dist/judges/cost-effectiveness.d.ts.map +1 -1
  232. package/dist/judges/cybersecurity.d.ts +1 -1
  233. package/dist/judges/cybersecurity.d.ts.map +1 -1
  234. package/dist/judges/data-security.d.ts +1 -1
  235. package/dist/judges/data-security.d.ts.map +1 -1
  236. package/dist/judges/data-sovereignty.d.ts +1 -1
  237. package/dist/judges/data-sovereignty.d.ts.map +1 -1
  238. package/dist/judges/database.d.ts +1 -1
  239. package/dist/judges/database.d.ts.map +1 -1
  240. package/dist/judges/dependency-health.d.ts +1 -1
  241. package/dist/judges/dependency-health.d.ts.map +1 -1
  242. package/dist/judges/documentation.d.ts +1 -1
  243. package/dist/judges/documentation.d.ts.map +1 -1
  244. package/dist/judges/error-handling.d.ts +1 -1
  245. package/dist/judges/error-handling.d.ts.map +1 -1
  246. package/dist/judges/ethics-bias.d.ts +1 -1
  247. package/dist/judges/ethics-bias.d.ts.map +1 -1
  248. package/dist/judges/framework-safety.d.ts +3 -0
  249. package/dist/judges/framework-safety.d.ts.map +1 -0
  250. package/dist/judges/framework-safety.js +31 -0
  251. package/dist/judges/framework-safety.js.map +1 -0
  252. package/dist/judges/index.d.ts +1 -1
  253. package/dist/judges/index.d.ts.map +1 -1
  254. package/dist/judges/index.js +74 -0
  255. package/dist/judges/index.js.map +1 -1
  256. package/dist/judges/internationalization.d.ts +1 -1
  257. package/dist/judges/internationalization.d.ts.map +1 -1
  258. package/dist/judges/logging-privacy.d.ts +1 -1
  259. package/dist/judges/logging-privacy.d.ts.map +1 -1
  260. package/dist/judges/maintainability.d.ts +1 -1
  261. package/dist/judges/maintainability.d.ts.map +1 -1
  262. package/dist/judges/observability.d.ts +1 -1
  263. package/dist/judges/observability.d.ts.map +1 -1
  264. package/dist/judges/performance.d.ts +1 -1
  265. package/dist/judges/performance.d.ts.map +1 -1
  266. package/dist/judges/portability.d.ts +1 -1
  267. package/dist/judges/portability.d.ts.map +1 -1
  268. package/dist/judges/rate-limiting.d.ts +1 -1
  269. package/dist/judges/rate-limiting.d.ts.map +1 -1
  270. package/dist/judges/reliability.d.ts +1 -1
  271. package/dist/judges/reliability.d.ts.map +1 -1
  272. package/dist/judges/scalability.d.ts +1 -1
  273. package/dist/judges/scalability.d.ts.map +1 -1
  274. package/dist/judges/software-practices.d.ts +1 -1
  275. package/dist/judges/software-practices.d.ts.map +1 -1
  276. package/dist/judges/testing.d.ts +1 -1
  277. package/dist/judges/testing.d.ts.map +1 -1
  278. package/dist/judges/ux.d.ts +1 -1
  279. package/dist/judges/ux.d.ts.map +1 -1
  280. package/dist/language-patterns.d.ts +37 -0
  281. package/dist/language-patterns.d.ts.map +1 -1
  282. package/dist/language-patterns.js +58 -3
  283. package/dist/language-patterns.js.map +1 -1
  284. package/dist/patches/index.d.ts +10 -0
  285. package/dist/patches/index.d.ts.map +1 -0
  286. package/dist/patches/index.js +533 -0
  287. package/dist/patches/index.js.map +1 -0
  288. package/dist/reports/public-repo-report.d.ts +1 -1
  289. package/dist/reports/public-repo-report.d.ts.map +1 -1
  290. package/dist/scoring.d.ts +18 -0
  291. package/dist/scoring.d.ts.map +1 -0
  292. package/dist/scoring.js +178 -0
  293. package/dist/scoring.js.map +1 -0
  294. package/dist/tools/deep-review.d.ts +4 -0
  295. package/dist/tools/deep-review.d.ts.map +1 -0
  296. package/dist/tools/deep-review.js +56 -0
  297. package/dist/tools/deep-review.js.map +1 -0
  298. package/dist/tools/prompts.d.ts +8 -0
  299. package/dist/tools/prompts.d.ts.map +1 -0
  300. package/dist/tools/prompts.js +66 -0
  301. package/dist/tools/prompts.js.map +1 -0
  302. package/dist/tools/register-evaluation.d.ts +7 -0
  303. package/dist/tools/register-evaluation.d.ts.map +1 -0
  304. package/dist/tools/register-evaluation.js +303 -0
  305. package/dist/tools/register-evaluation.js.map +1 -0
  306. package/dist/tools/register-workflow.d.ts +7 -0
  307. package/dist/tools/register-workflow.d.ts.map +1 -0
  308. package/dist/tools/register-workflow.js +395 -0
  309. package/dist/tools/register-workflow.js.map +1 -0
  310. package/dist/tools/register.d.ts +7 -0
  311. package/dist/tools/register.d.ts.map +1 -0
  312. package/dist/tools/register.js +14 -0
  313. package/dist/tools/register.js.map +1 -0
  314. package/dist/tools/schemas.d.ts +26 -0
  315. package/dist/tools/schemas.d.ts.map +1 -0
  316. package/dist/tools/schemas.js +42 -0
  317. package/dist/tools/schemas.js.map +1 -0
  318. package/dist/types.d.ts +29 -2
  319. package/dist/types.d.ts.map +1 -1
  320. package/package.json +42 -3
  321. package/server.json +51 -3
@@ -0,0 +1,713 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Cross-File Taint Tracker — Multi-module data-flow analysis
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Extends the single-file taint tracker to propagate taint across module
5
+ // boundaries. When a tainted variable is exported from file A and imported
6
+ // by file B, the taint propagates to wherever file B uses that import.
7
+ //
8
+ // Architecture:
9
+ // 1. Run single-file taint analysis on every file to find sources & tainted vars
10
+ // 2. Build an export map: file → { exportedName → taint info }
11
+ // 3. Resolve imports: for each import, check if the exported binding is tainted
12
+ // 4. Run a second pass on importing files with injected cross-file taint seeds
13
+ // 5. Emit CrossFileTaintFlow findings with full file-to-file provenance
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ import { normalizeLanguage } from "../language-patterns.js";
16
+ // ─── Source / Sink pattern references (same as taint-tracker.ts) ─────────────
17
+ const SOURCE_PATTERNS = [
18
+ { pattern: /\breq(?:uest)?\.(?:body|query|params|headers|cookies)\b/i, kind: "http-param" },
19
+ { pattern: /\brequest\.(?:form|args|json|data|values|files|get)\b/i, kind: "http-param" },
20
+ { pattern: /\b(?:ctx|context)\.(?:query|params|request)\b/i, kind: "http-param" },
21
+ { pattern: /\bgetParameter\s*\(/i, kind: "http-param" },
22
+ { pattern: /\bRequest\.(?:Form|QueryString|Params)\b/i, kind: "http-param" },
23
+ { pattern: /\b(?:process\.argv|sys\.argv|os\.Args|args)\b/i, kind: "user-input" },
24
+ { pattern: /\b(?:prompt|readline|input)\s*\(/i, kind: "user-input" },
25
+ { pattern: /\bsearchParams\.get\s*\(/i, kind: "url-param" },
26
+ { pattern: /\.(?:useSearchParams|useParams)\b/i, kind: "url-param" },
27
+ ];
28
+ const SINK_PATTERNS = [
29
+ { pattern: /\beval\s*\(/i, kind: "code-execution" },
30
+ { pattern: /\bnew\s+Function\s*\(/i, kind: "code-execution" },
31
+ { pattern: /\b(?:exec|execSync|system|popen|subprocess\.(?:Popen|run|call)|os\.system)\s*\(/i, kind: "command-exec" },
32
+ { pattern: /\b(?:spawn|spawnSync)\s*\(/i, kind: "command-exec" },
33
+ { pattern: /\.(?:query|execute|exec)\s*\(/i, kind: "sql-query" },
34
+ { pattern: /\.innerHTML\s*=/i, kind: "xss" },
35
+ { pattern: /\bdocument\.write\s*\(/i, kind: "xss" },
36
+ { pattern: /\bdangerouslySetInnerHTML/i, kind: "xss" },
37
+ { pattern: /\b(?:readFile|readFileSync|open)\s*\(/i, kind: "path-traversal" },
38
+ { pattern: /\.redirect\s*\(/i, kind: "redirect" },
39
+ { pattern: /\b(?:render_template_string|nunjucks\.renderString|Handlebars\.compile)\s*\(/i, kind: "template" },
40
+ ];
41
+ const SANITIZER_PATTERNS = [
42
+ /\bDOMPurify\.sanitize\s*\(/i,
43
+ /\bsanitizeHtml\s*\(/i,
44
+ /\bescapeHtml\s*\(/i,
45
+ /\bencodeURIComponent\s*\(/i,
46
+ /\bvalidator\.\w+\s*\(/i,
47
+ /\b(?:joi|yup|zod|ajv)\b.*\.(?:validate|parse|safeParse)\s*\(/i,
48
+ /\$\d+/,
49
+ /\?\s*(?:,|\))/,
50
+ /\bpath\.(?:normalize|resolve|basename)\s*\(/i,
51
+ /\bPreparedStatement\b/i,
52
+ ];
53
+ function isSanitized(expression) {
54
+ return SANITIZER_PATTERNS.some((p) => p.test(expression));
55
+ }
56
+ // ─── Export Analysis ─────────────────────────────────────────────────────────
57
+ /**
58
+ * Analyze a file's exports to find which exported bindings carry taint.
59
+ * Detects:
60
+ * - Exported variables assigned from taint sources
61
+ * - Exported functions that return tainted data
62
+ * - Exported functions that pass tainted parameters through to dangerous sinks
63
+ */
64
+ function analyzeTaintedExports(code, filePath) {
65
+ const lines = code.split("\n");
66
+ const exports = [];
67
+ // Track tainted variables within the file
68
+ const taintedVars = new Map();
69
+ // Pass 1: Find tainted variable assignments
70
+ for (let i = 0; i < lines.length; i++) {
71
+ const line = lines[i];
72
+ const lineNum = i + 1;
73
+ // Variable assignment: const x = req.body.foo
74
+ const assignMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(.+)/);
75
+ if (assignMatch) {
76
+ const [, varName, rhs] = assignMatch;
77
+ if (isSanitized(rhs))
78
+ continue;
79
+ for (const src of SOURCE_PATTERNS) {
80
+ if (src.pattern.test(rhs)) {
81
+ taintedVars.set(varName, { sourceKind: src.kind, sourceExpr: rhs.trim(), sourceLine: lineNum });
82
+ break;
83
+ }
84
+ }
85
+ // Propagation from tainted variable
86
+ if (!taintedVars.has(varName)) {
87
+ for (const [tv, info] of taintedVars) {
88
+ if (new RegExp(`\\b${tv.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(rhs)) {
89
+ taintedVars.set(varName, info);
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ // Pass 2: Find exported tainted bindings
97
+ for (let i = 0; i < lines.length; i++) {
98
+ const line = lines[i];
99
+ const lineNum = i + 1;
100
+ // Direct export of tainted variable: export const x = req.body...
101
+ const directExportMatch = line.match(/export\s+(?:const|let|var)\s+(\w+)\s*=\s*(.+)/);
102
+ if (directExportMatch) {
103
+ const [, name, rhs] = directExportMatch;
104
+ for (const src of SOURCE_PATTERNS) {
105
+ if (src.pattern.test(rhs)) {
106
+ exports.push({
107
+ exportedName: name,
108
+ kind: "tainted-variable",
109
+ sourceKind: src.kind,
110
+ sourceExpression: rhs.trim(),
111
+ sourceLine: lineNum,
112
+ });
113
+ break;
114
+ }
115
+ }
116
+ }
117
+ // Named export of already-tainted variable: export { x }
118
+ const namedExportMatch = line.match(/export\s*\{([^}]+)\}/);
119
+ if (namedExportMatch) {
120
+ const names = namedExportMatch[1].split(",").map((n) => {
121
+ const parts = n.trim().split(/\s+as\s+/);
122
+ return { local: parts[0].trim(), exported: (parts[1] ?? parts[0]).trim() };
123
+ });
124
+ for (const { local, exported } of names) {
125
+ if (taintedVars.has(local)) {
126
+ const info = taintedVars.get(local);
127
+ exports.push({
128
+ exportedName: exported,
129
+ kind: "tainted-variable",
130
+ sourceKind: info.sourceKind,
131
+ sourceExpression: info.sourceExpr,
132
+ sourceLine: info.sourceLine,
133
+ });
134
+ }
135
+ }
136
+ }
137
+ // Exported function that takes params and passes them to sinks
138
+ // export function processInput(userInput: string) { exec(userInput); }
139
+ const exportFnMatch = line.match(/export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
140
+ if (exportFnMatch) {
141
+ const [, fnName, params] = exportFnMatch;
142
+ const paramNames = params
143
+ .split(",")
144
+ .map((p) => p
145
+ .trim()
146
+ .split(/[:\s=]/)[0]
147
+ .trim())
148
+ .filter(Boolean);
149
+ // Look ahead in the function body for sinks using these params
150
+ const bodyStart = i;
151
+ let braceDepth = 0;
152
+ let foundOpen = false;
153
+ for (let j = i; j < Math.min(i + 100, lines.length); j++) {
154
+ for (const ch of lines[j]) {
155
+ if (ch === "{") {
156
+ braceDepth++;
157
+ foundOpen = true;
158
+ }
159
+ if (ch === "}") {
160
+ braceDepth--;
161
+ }
162
+ }
163
+ if (foundOpen && braceDepth <= 0) {
164
+ // Scan body for param → sink flows
165
+ const body = lines.slice(bodyStart, j + 1).join("\n");
166
+ const taintedIndices = [];
167
+ for (let pi = 0; pi < paramNames.length; pi++) {
168
+ const pName = paramNames[pi];
169
+ if (!pName)
170
+ continue;
171
+ const pEsc = pName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
172
+ const pRe = new RegExp(`\\b${pEsc}\\b`);
173
+ for (const sink of SINK_PATTERNS) {
174
+ // Check each line of the body for a sink that uses this param
175
+ for (let bi = bodyStart; bi <= j && bi < lines.length; bi++) {
176
+ if (sink.pattern.test(lines[bi]) && pRe.test(lines[bi]) && !isSanitized(lines[bi])) {
177
+ taintedIndices.push(pi);
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ }
183
+ if (taintedIndices.length > 0) {
184
+ exports.push({
185
+ exportedName: fnName,
186
+ kind: "tainted-param-passthrough",
187
+ sourceKind: "external-data",
188
+ sourceExpression: `parameter(s) of ${fnName}()`,
189
+ sourceLine: lineNum,
190
+ taintedParamIndices: taintedIndices,
191
+ });
192
+ }
193
+ // Also check if the function returns tainted data
194
+ if (/\breturn\b/.test(body)) {
195
+ let foundReturn = false;
196
+ // Check if return statement directly contains a taint source
197
+ for (const src of SOURCE_PATTERNS) {
198
+ const returnSourceMatch = body.match(new RegExp(`return\\s+(.*${src.pattern.source}.*)`, "im"));
199
+ if (returnSourceMatch) {
200
+ exports.push({
201
+ exportedName: fnName,
202
+ kind: "tainted-return",
203
+ sourceKind: src.kind,
204
+ sourceExpression: returnSourceMatch[1].trim().replace(/;\s*$/, ""),
205
+ sourceLine: lineNum,
206
+ });
207
+ foundReturn = true;
208
+ break;
209
+ }
210
+ }
211
+ if (!foundReturn) {
212
+ for (const [tv, info] of taintedVars) {
213
+ const tvEsc = tv.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
214
+ if (new RegExp(`return\\s+.*\\b${tvEsc}\\b`).test(body)) {
215
+ exports.push({
216
+ exportedName: fnName,
217
+ kind: "tainted-return",
218
+ sourceKind: info.sourceKind,
219
+ sourceExpression: info.sourceExpr,
220
+ sourceLine: info.sourceLine,
221
+ });
222
+ break;
223
+ }
224
+ }
225
+ }
226
+ }
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ // export default function — same as above but with "default" as export name
232
+ const defaultFnMatch = line.match(/export\s+default\s+(?:async\s+)?function\s*(\w*)\s*\(([^)]*)\)/);
233
+ if (defaultFnMatch) {
234
+ const [, fnName, params] = defaultFnMatch;
235
+ const paramNames = params
236
+ .split(",")
237
+ .map((p) => p
238
+ .trim()
239
+ .split(/[:\s=]/)[0]
240
+ .trim())
241
+ .filter(Boolean);
242
+ const bodyStart = i;
243
+ let braceDepth = 0;
244
+ let foundOpen = false;
245
+ for (let j = i; j < Math.min(i + 100, lines.length); j++) {
246
+ for (const ch of lines[j]) {
247
+ if (ch === "{") {
248
+ braceDepth++;
249
+ foundOpen = true;
250
+ }
251
+ if (ch === "}") {
252
+ braceDepth--;
253
+ }
254
+ }
255
+ if (foundOpen && braceDepth <= 0) {
256
+ const body = lines.slice(bodyStart, j + 1).join("\n");
257
+ const taintedIndices = [];
258
+ for (let pi = 0; pi < paramNames.length; pi++) {
259
+ const pName = paramNames[pi];
260
+ if (!pName)
261
+ continue;
262
+ const pEsc = pName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
263
+ const pRe = new RegExp(`\\b${pEsc}\\b`);
264
+ for (const sink of SINK_PATTERNS) {
265
+ for (let bi = bodyStart; bi <= j && bi < lines.length; bi++) {
266
+ if (sink.pattern.test(lines[bi]) && pRe.test(lines[bi]) && !isSanitized(lines[bi])) {
267
+ taintedIndices.push(pi);
268
+ break;
269
+ }
270
+ }
271
+ }
272
+ }
273
+ if (taintedIndices.length > 0) {
274
+ exports.push({
275
+ exportedName: fnName || "default",
276
+ kind: "tainted-param-passthrough",
277
+ sourceKind: "external-data",
278
+ sourceExpression: `parameter(s) of ${fnName || "default"}()`,
279
+ sourceLine: lineNum,
280
+ taintedParamIndices: taintedIndices,
281
+ });
282
+ }
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ // CommonJS: module.exports = function(...) or module.exports = { ... }
288
+ const cjsExportFnMatch = line.match(/module\.exports\s*=\s*(?:async\s+)?function\s*(\w*)\s*\(([^)]*)\)/);
289
+ if (cjsExportFnMatch) {
290
+ const [, fnName, params] = cjsExportFnMatch;
291
+ const paramNames = params
292
+ .split(",")
293
+ .map((p) => p
294
+ .trim()
295
+ .split(/[:\s=]/)[0]
296
+ .trim())
297
+ .filter(Boolean);
298
+ const bodyStart = i;
299
+ let braceDepth = 0;
300
+ let foundOpen = false;
301
+ for (let j = i; j < Math.min(i + 100, lines.length); j++) {
302
+ for (const ch of lines[j]) {
303
+ if (ch === "{") {
304
+ braceDepth++;
305
+ foundOpen = true;
306
+ }
307
+ if (ch === "}") {
308
+ braceDepth--;
309
+ }
310
+ }
311
+ if (foundOpen && braceDepth <= 0) {
312
+ const body = lines.slice(bodyStart, j + 1).join("\n");
313
+ const taintedIndices = [];
314
+ for (let pi = 0; pi < paramNames.length; pi++) {
315
+ const pName = paramNames[pi];
316
+ if (!pName)
317
+ continue;
318
+ const pEsc = pName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
319
+ const pRe = new RegExp(`\\b${pEsc}\\b`);
320
+ for (const sink of SINK_PATTERNS) {
321
+ for (let bi = bodyStart; bi <= j && bi < lines.length; bi++) {
322
+ if (sink.pattern.test(lines[bi]) && pRe.test(lines[bi]) && !isSanitized(lines[bi])) {
323
+ taintedIndices.push(pi);
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ }
329
+ if (taintedIndices.length > 0) {
330
+ exports.push({
331
+ exportedName: "default",
332
+ kind: "tainted-param-passthrough",
333
+ sourceKind: "external-data",
334
+ sourceExpression: `parameter(s) of ${fnName || "default"}()`,
335
+ sourceLine: lineNum,
336
+ taintedParamIndices: taintedIndices,
337
+ });
338
+ }
339
+ // Check if return directly contains a source
340
+ if (/\breturn\b/.test(body)) {
341
+ for (const src of SOURCE_PATTERNS) {
342
+ const returnSourceMatch = body.match(new RegExp(`return\\s+(.*${src.pattern.source}.*)`, "im"));
343
+ if (returnSourceMatch) {
344
+ exports.push({
345
+ exportedName: "default",
346
+ kind: "tainted-return",
347
+ sourceKind: src.kind,
348
+ sourceExpression: returnSourceMatch[1].trim().replace(/;\s*$/, ""),
349
+ sourceLine: lineNum,
350
+ });
351
+ break;
352
+ }
353
+ }
354
+ }
355
+ break;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ return exports;
361
+ }
362
+ // ─── Import Parsing ──────────────────────────────────────────────────────────
363
+ /**
364
+ * Parse import statements from a source file. Supports:
365
+ * - import { foo } from "./module"
366
+ * - import { foo as bar } from "./module"
367
+ * - import defaultExport from "./module"
368
+ * - const { foo } = require("./module")
369
+ * - const foo = require("./module")
370
+ * - import * as ns from "./module"
371
+ */
372
+ function parseImports(code) {
373
+ const lines = code.split("\n");
374
+ const imports = [];
375
+ for (let i = 0; i < lines.length; i++) {
376
+ const line = lines[i];
377
+ const lineNum = i + 1;
378
+ // ES named imports: import { foo, bar as baz } from "./module"
379
+ const namedImportMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
380
+ if (namedImportMatch) {
381
+ const [, names, moduleSpec] = namedImportMatch;
382
+ if (!isRelativeImport(moduleSpec))
383
+ continue;
384
+ for (const name of names.split(",")) {
385
+ const parts = name.trim().split(/\s+as\s+/);
386
+ imports.push({
387
+ moduleSpecifier: moduleSpec,
388
+ importedName: parts[0].trim(),
389
+ localName: (parts[1] ?? parts[0]).trim(),
390
+ line: lineNum,
391
+ });
392
+ }
393
+ continue;
394
+ }
395
+ // ES default import: import foo from "./module"
396
+ const defaultImportMatch = line.match(/import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/);
397
+ if (defaultImportMatch) {
398
+ const [, name, moduleSpec] = defaultImportMatch;
399
+ if (!isRelativeImport(moduleSpec))
400
+ continue;
401
+ // Skip if it looks like import { which was already handled
402
+ if (name === "type")
403
+ continue;
404
+ imports.push({
405
+ moduleSpecifier: moduleSpec,
406
+ importedName: "default",
407
+ localName: name,
408
+ line: lineNum,
409
+ });
410
+ continue;
411
+ }
412
+ // Namespace import: import * as ns from "./module"
413
+ const nsImportMatch = line.match(/import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/);
414
+ if (nsImportMatch) {
415
+ const [, name, moduleSpec] = nsImportMatch;
416
+ if (!isRelativeImport(moduleSpec))
417
+ continue;
418
+ imports.push({
419
+ moduleSpecifier: moduleSpec,
420
+ importedName: "*",
421
+ localName: name,
422
+ line: lineNum,
423
+ });
424
+ continue;
425
+ }
426
+ // CommonJS: const foo = require("./module")
427
+ const requireMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
428
+ if (requireMatch) {
429
+ const [, name, moduleSpec] = requireMatch;
430
+ if (!isRelativeImport(moduleSpec))
431
+ continue;
432
+ imports.push({
433
+ moduleSpecifier: moduleSpec,
434
+ importedName: "default",
435
+ localName: name,
436
+ line: lineNum,
437
+ });
438
+ continue;
439
+ }
440
+ // CommonJS destructured: const { foo, bar } = require("./module")
441
+ const requireDestructMatch = line.match(/(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
442
+ if (requireDestructMatch) {
443
+ const [, names, moduleSpec] = requireDestructMatch;
444
+ if (!isRelativeImport(moduleSpec))
445
+ continue;
446
+ for (const name of names.split(",")) {
447
+ const parts = name.trim().split(/\s*:\s*/);
448
+ imports.push({
449
+ moduleSpecifier: moduleSpec,
450
+ importedName: parts[0].trim(),
451
+ localName: (parts[1] ?? parts[0]).trim(),
452
+ line: lineNum,
453
+ });
454
+ }
455
+ }
456
+ }
457
+ return imports;
458
+ }
459
+ function isRelativeImport(spec) {
460
+ return spec.startsWith("./") || spec.startsWith("../");
461
+ }
462
+ // ─── Module Resolution ───────────────────────────────────────────────────────
463
+ /**
464
+ * Resolve a relative import specifier to a file path in the project.
465
+ */
466
+ function resolveModulePath(importerPath, moduleSpecifier, knownPaths) {
467
+ // Compute the directory of the importing file
468
+ const dir = importerPath.replace(/\/[^/]+$/, "") || ".";
469
+ const parts = dir.split("/");
470
+ const importParts = moduleSpecifier.replace(/^\.\//, "").split("/");
471
+ for (const part of importParts) {
472
+ if (part === "..") {
473
+ parts.pop();
474
+ }
475
+ else if (part !== ".") {
476
+ parts.push(part);
477
+ }
478
+ }
479
+ const base = parts.join("/");
480
+ // Try exact match, then with common extensions
481
+ const candidates = [
482
+ base,
483
+ `${base}.ts`,
484
+ `${base}.js`,
485
+ `${base}.tsx`,
486
+ `${base}.jsx`,
487
+ `${base}/index.ts`,
488
+ `${base}/index.js`,
489
+ `${base}/index.tsx`,
490
+ `${base}/index.jsx`,
491
+ ];
492
+ for (const candidate of candidates) {
493
+ if (knownPaths.has(candidate))
494
+ return candidate;
495
+ // Also try without leading ./
496
+ const trimmed = candidate.replace(/^\.\//, "");
497
+ if (knownPaths.has(trimmed))
498
+ return trimmed;
499
+ }
500
+ return null;
501
+ }
502
+ // ─── Public API ──────────────────────────────────────────────────────────────
503
+ /**
504
+ * Analyze taint flows across multiple files in a project. Traces tainted data
505
+ * from sources in one file through export/import boundaries to sinks in
506
+ * another file.
507
+ *
508
+ * Returns both intra-file flows (from the standard taint tracker) and
509
+ * cross-file flows where taint crosses module boundaries.
510
+ */
511
+ export function analyzeCrossFileTaint(files) {
512
+ const crossFlows = [];
513
+ const knownPaths = new Set(files.map((f) => f.path));
514
+ // Step 1: Analyze each file's tainted exports
515
+ const exportsByFile = new Map();
516
+ for (const f of files) {
517
+ const lang = normalizeLanguage(f.language);
518
+ if (lang !== "javascript" && lang !== "typescript")
519
+ continue;
520
+ const taintedExports = analyzeTaintedExports(f.content, f.path);
521
+ if (taintedExports.length > 0) {
522
+ exportsByFile.set(f.path, taintedExports);
523
+ }
524
+ }
525
+ // If no files have tainted exports, no cross-file flows possible
526
+ if (exportsByFile.size === 0)
527
+ return crossFlows;
528
+ // Step 2: For each file, check if its imports reference tainted exports
529
+ for (const f of files) {
530
+ const lang = normalizeLanguage(f.language);
531
+ if (lang !== "javascript" && lang !== "typescript")
532
+ continue;
533
+ const imports = parseImports(f.content);
534
+ if (imports.length === 0)
535
+ continue;
536
+ const lines = f.content.split("\n");
537
+ for (const imp of imports) {
538
+ // Resolve which file this import points to
539
+ const resolvedPath = resolveModulePath(f.path, imp.moduleSpecifier, knownPaths);
540
+ if (!resolvedPath)
541
+ continue;
542
+ const fileExports = exportsByFile.get(resolvedPath);
543
+ if (!fileExports)
544
+ continue;
545
+ // Check if the imported binding matches a tainted export
546
+ for (const exp of fileExports) {
547
+ const nameMatches = imp.importedName === exp.exportedName ||
548
+ (imp.importedName === "default" && exp.exportedName === "default") ||
549
+ imp.importedName === "*"; // Namespace imports get all exports
550
+ if (!nameMatches)
551
+ continue;
552
+ // Determine the local name to track in this file
553
+ const localName = imp.importedName === "*" ? `${imp.localName}.${exp.exportedName}` : imp.localName;
554
+ const localNameEsc = localName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
555
+ const localNameRe = new RegExp(`\\b${localNameEsc}\\b`);
556
+ if (exp.kind === "tainted-variable" || exp.kind === "tainted-return") {
557
+ // The imported binding IS tainted — check if it reaches any sink
558
+ for (let li = 0; li < lines.length; li++) {
559
+ const line = lines[li];
560
+ const lineNum = li + 1;
561
+ if (lineNum === imp.line)
562
+ continue; // skip the import line itself
563
+ if (!localNameRe.test(line))
564
+ continue;
565
+ if (isSanitized(line))
566
+ continue;
567
+ for (const sink of SINK_PATTERNS) {
568
+ if (sink.pattern.test(line)) {
569
+ crossFlows.push({
570
+ sourceFile: resolvedPath,
571
+ sinkFile: f.path,
572
+ source: {
573
+ line: exp.sourceLine,
574
+ expression: exp.sourceExpression,
575
+ kind: exp.sourceKind,
576
+ },
577
+ sink: {
578
+ line: lineNum,
579
+ api: sink.pattern.source.slice(0, 40),
580
+ kind: sink.kind,
581
+ },
582
+ exportedBinding: exp.exportedName,
583
+ importedAs: localName,
584
+ confidence: 0.75, // Cross-file flows get slightly lower confidence
585
+ });
586
+ break; // One sink per line
587
+ }
588
+ }
589
+ // Also track propagation: if the imported tainted value is assigned
590
+ // to another variable, track that variable to sinks too
591
+ const reassignMatch = line.match(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*.*\\b${localNameEsc}\\b`));
592
+ if (reassignMatch) {
593
+ const derivedVar = reassignMatch[1];
594
+ const derivedEsc = derivedVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
595
+ const derivedRe = new RegExp(`\\b${derivedEsc}\\b`);
596
+ // Scan remaining lines for the derived variable reaching a sink
597
+ for (let dli = li + 1; dli < lines.length; dli++) {
598
+ const dline = lines[dli];
599
+ if (!derivedRe.test(dline))
600
+ continue;
601
+ if (isSanitized(dline))
602
+ continue;
603
+ for (const sink of SINK_PATTERNS) {
604
+ if (sink.pattern.test(dline)) {
605
+ crossFlows.push({
606
+ sourceFile: resolvedPath,
607
+ sinkFile: f.path,
608
+ source: {
609
+ line: exp.sourceLine,
610
+ expression: exp.sourceExpression,
611
+ kind: exp.sourceKind,
612
+ },
613
+ sink: {
614
+ line: dli + 1,
615
+ api: sink.pattern.source.slice(0, 40),
616
+ kind: sink.kind,
617
+ },
618
+ exportedBinding: exp.exportedName,
619
+ importedAs: `${localName} → ${derivedVar}`,
620
+ confidence: 0.65, // Lower confidence for derived propagation
621
+ });
622
+ break;
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ }
629
+ else if (exp.kind === "tainted-param-passthrough") {
630
+ // The exported function passes its params to sinks — check if callers
631
+ // pass tainted data to those parameters
632
+ const fnCallRe = new RegExp(`\\b${localNameEsc}\\s*\\(([^)]*(?:\\([^)]*\\)[^)]*)*)\\)`);
633
+ for (let li = 0; li < lines.length; li++) {
634
+ const line = lines[li];
635
+ const lineNum = li + 1;
636
+ const callMatch = line.match(fnCallRe);
637
+ if (!callMatch)
638
+ continue;
639
+ const argsStr = callMatch[1];
640
+ // Simple argument splitting (handles basic cases)
641
+ const args = splitArguments(argsStr);
642
+ for (const paramIdx of exp.taintedParamIndices ?? []) {
643
+ if (paramIdx >= args.length)
644
+ continue;
645
+ const arg = args[paramIdx].trim();
646
+ // Check if this argument is tainted (from a source pattern)
647
+ for (const src of SOURCE_PATTERNS) {
648
+ if (src.pattern.test(arg)) {
649
+ crossFlows.push({
650
+ sourceFile: f.path,
651
+ sinkFile: resolvedPath,
652
+ source: {
653
+ line: lineNum,
654
+ expression: arg,
655
+ kind: src.kind,
656
+ },
657
+ sink: {
658
+ line: exp.sourceLine,
659
+ api: `${exp.exportedName}() param[${paramIdx}]`,
660
+ kind: "code-execution", // Generic — the actual sink is in the callee
661
+ },
662
+ exportedBinding: exp.exportedName,
663
+ importedAs: localName,
664
+ confidence: 0.7,
665
+ });
666
+ break;
667
+ }
668
+ }
669
+ }
670
+ }
671
+ }
672
+ }
673
+ }
674
+ }
675
+ return deduplicateCrossFlows(crossFlows);
676
+ }
677
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
678
+ /**
679
+ * Split a function call's argument string into individual arguments.
680
+ * Handles nested parentheses but not template literals or complex expressions.
681
+ */
682
+ function splitArguments(argsStr) {
683
+ const args = [];
684
+ let current = "";
685
+ let depth = 0;
686
+ for (const ch of argsStr) {
687
+ if (ch === "(" || ch === "[" || ch === "{")
688
+ depth++;
689
+ if (ch === ")" || ch === "]" || ch === "}")
690
+ depth--;
691
+ if (ch === "," && depth === 0) {
692
+ args.push(current);
693
+ current = "";
694
+ }
695
+ else {
696
+ current += ch;
697
+ }
698
+ }
699
+ if (current.trim())
700
+ args.push(current);
701
+ return args;
702
+ }
703
+ function deduplicateCrossFlows(flows) {
704
+ const seen = new Set();
705
+ return flows.filter((f) => {
706
+ const key = `${f.sourceFile}:${f.source.line}→${f.sinkFile}:${f.sink.line}:${f.sink.kind}`;
707
+ if (seen.has(key))
708
+ return false;
709
+ seen.add(key);
710
+ return true;
711
+ });
712
+ }
713
+ //# sourceMappingURL=cross-file-taint.js.map