circle-ir 4.16.1 → 4.18.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 (295) hide show
  1. package/dist/analysis/advisories.d.ts +186 -0
  2. package/dist/analysis/advisories.d.ts.map +1 -0
  3. package/dist/analysis/advisories.js +391 -0
  4. package/dist/analysis/advisories.js.map +1 -0
  5. package/dist/analysis/comment-lines.d.ts +14 -0
  6. package/dist/analysis/comment-lines.d.ts.map +1 -0
  7. package/dist/analysis/comment-lines.js +34 -0
  8. package/dist/analysis/comment-lines.js.map +1 -0
  9. package/dist/analysis/config-loader.d.ts.map +1 -1
  10. package/dist/analysis/config-loader.js +59 -5
  11. package/dist/analysis/config-loader.js.map +1 -1
  12. package/dist/analysis/constant-caller-gate.d.ts +2 -2
  13. package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
  14. package/dist/analysis/constant-caller-gate.js +205 -29
  15. package/dist/analysis/constant-caller-gate.js.map +1 -1
  16. package/dist/analysis/constant-propagation/index.d.ts +2 -0
  17. package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
  18. package/dist/analysis/constant-propagation/index.js +10 -0
  19. package/dist/analysis/constant-propagation/index.js.map +1 -1
  20. package/dist/analysis/constant-propagation/propagator.d.ts +15 -0
  21. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  22. package/dist/analysis/constant-propagation/propagator.js +224 -20
  23. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  24. package/dist/analysis/constant-propagation/types.d.ts +2 -0
  25. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  26. package/dist/analysis/constant-propagation.d.ts +1 -1
  27. package/dist/analysis/constant-propagation.d.ts.map +1 -1
  28. package/dist/analysis/constant-propagation.js +1 -1
  29. package/dist/analysis/constant-propagation.js.map +1 -1
  30. package/dist/analysis/entry-point-detection.d.ts.map +1 -1
  31. package/dist/analysis/entry-point-detection.js +72 -0
  32. package/dist/analysis/entry-point-detection.js.map +1 -1
  33. package/dist/analysis/interprocedural.d.ts.map +1 -1
  34. package/dist/analysis/interprocedural.js +75 -8
  35. package/dist/analysis/interprocedural.js.map +1 -1
  36. package/dist/analysis/maven-declared.d.ts +72 -0
  37. package/dist/analysis/maven-declared.d.ts.map +1 -0
  38. package/dist/analysis/maven-declared.js +216 -0
  39. package/dist/analysis/maven-declared.js.map +1 -0
  40. package/dist/analysis/maven-version.d.ts +28 -0
  41. package/dist/analysis/maven-version.d.ts.map +1 -0
  42. package/dist/analysis/maven-version.js +172 -0
  43. package/dist/analysis/maven-version.js.map +1 -0
  44. package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
  45. package/dist/analysis/passes/constant-propagation-pass.js +29 -1
  46. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
  47. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  48. package/dist/analysis/passes/cross-file-pass.js +84 -2
  49. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  50. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
  51. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  52. package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
  53. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  54. package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
  55. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  56. package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
  57. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  58. package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
  59. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  60. package/dist/analysis/passes/interprocedural-pass.js +562 -0
  61. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  62. package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
  63. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  64. package/dist/analysis/passes/language-sources-pass.js +1079 -14
  65. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  66. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  67. package/dist/analysis/passes/param-source-flow-gate-pass.js +82 -3
  68. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  69. package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
  70. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  71. package/dist/analysis/passes/sink-filter-pass.js +690 -12
  72. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  73. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  74. package/dist/analysis/passes/taint-propagation-pass.js +298 -19
  75. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  76. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
  77. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
  78. package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
  79. package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
  80. package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
  81. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  82. package/dist/analysis/passes/weak-random-pass.js +29 -0
  83. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  84. package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
  85. package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
  86. package/dist/analysis/promote-cross-file-flows.js +32 -0
  87. package/dist/analysis/promote-cross-file-flows.js.map +1 -0
  88. package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
  89. package/dist/analysis/rust-literal-guard.js +100 -1
  90. package/dist/analysis/rust-literal-guard.js.map +1 -1
  91. package/dist/analysis/sbom.d.ts +100 -0
  92. package/dist/analysis/sbom.d.ts.map +1 -1
  93. package/dist/analysis/sbom.js +514 -3
  94. package/dist/analysis/sbom.js.map +1 -1
  95. package/dist/analysis/semver-version.d.ts +30 -0
  96. package/dist/analysis/semver-version.d.ts.map +1 -0
  97. package/dist/analysis/semver-version.js +76 -0
  98. package/dist/analysis/semver-version.js.map +1 -0
  99. package/dist/analysis/taint-matcher.d.ts +5 -0
  100. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  101. package/dist/analysis/taint-matcher.js +226 -4
  102. package/dist/analysis/taint-matcher.js.map +1 -1
  103. package/dist/analyzer.d.ts +57 -0
  104. package/dist/analyzer.d.ts.map +1 -1
  105. package/dist/analyzer.js +69 -6
  106. package/dist/analyzer.js.map +1 -1
  107. package/dist/browser/circle-ir.js +3360 -314
  108. package/dist/core/circle-ir-core.cjs +783 -220
  109. package/dist/core/circle-ir-core.js +783 -220
  110. package/dist/core/extractors/dfg.js +205 -2
  111. package/dist/core/extractors/dfg.js.map +1 -1
  112. package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
  113. package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
  114. package/dist/core/extractors/ts-declaration-detail.js +266 -0
  115. package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
  116. package/dist/core/extractors/types.js +53 -9
  117. package/dist/core/extractors/types.js.map +1 -1
  118. package/dist/graph/analysis-pass.d.ts +4 -1
  119. package/dist/graph/analysis-pass.d.ts.map +1 -1
  120. package/dist/graph/analysis-pass.js +4 -1
  121. package/dist/graph/analysis-pass.js.map +1 -1
  122. package/dist/graph-facts/dead-code.d.ts +62 -0
  123. package/dist/graph-facts/dead-code.d.ts.map +1 -0
  124. package/dist/graph-facts/dead-code.js +90 -0
  125. package/dist/graph-facts/dead-code.js.map +1 -0
  126. package/dist/graph-facts/dependency-calls.d.ts +86 -0
  127. package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
  128. package/dist/graph-facts/dependency-calls.js +93 -0
  129. package/dist/graph-facts/dependency-calls.js.map +1 -0
  130. package/dist/graph-facts/index.d.ts +10 -0
  131. package/dist/graph-facts/index.d.ts.map +1 -0
  132. package/dist/graph-facts/index.js +10 -0
  133. package/dist/graph-facts/index.js.map +1 -0
  134. package/dist/graph-facts/maven-packages.d.ts +42 -0
  135. package/dist/graph-facts/maven-packages.d.ts.map +1 -0
  136. package/dist/graph-facts/maven-packages.js +133 -0
  137. package/dist/graph-facts/maven-packages.js.map +1 -0
  138. package/dist/graph-facts/reach.d.ts +203 -0
  139. package/dist/graph-facts/reach.d.ts.map +1 -0
  140. package/dist/graph-facts/reach.js +219 -0
  141. package/dist/graph-facts/reach.js.map +1 -0
  142. package/dist/graph-facts/removal-reach.d.ts +73 -0
  143. package/dist/graph-facts/removal-reach.d.ts.map +1 -0
  144. package/dist/graph-facts/removal-reach.js +64 -0
  145. package/dist/graph-facts/removal-reach.js.map +1 -0
  146. package/dist/index.d.ts +21 -6
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +20 -3
  149. package/dist/index.js.map +1 -1
  150. package/dist/navigation/go-graph.d.ts +68 -0
  151. package/dist/navigation/go-graph.d.ts.map +1 -0
  152. package/dist/navigation/go-graph.js +753 -0
  153. package/dist/navigation/go-graph.js.map +1 -0
  154. package/dist/navigation/index.d.ts +7 -1
  155. package/dist/navigation/index.d.ts.map +1 -1
  156. package/dist/navigation/index.js +6 -0
  157. package/dist/navigation/index.js.map +1 -1
  158. package/dist/navigation/java-entry-tables.d.ts +43 -0
  159. package/dist/navigation/java-entry-tables.d.ts.map +1 -0
  160. package/dist/navigation/java-entry-tables.js +56 -0
  161. package/dist/navigation/java-entry-tables.js.map +1 -0
  162. package/dist/navigation/java-graph.d.ts +64 -0
  163. package/dist/navigation/java-graph.d.ts.map +1 -0
  164. package/dist/navigation/java-graph.js +466 -0
  165. package/dist/navigation/java-graph.js.map +1 -0
  166. package/dist/navigation/navigation-index.d.ts +91 -2
  167. package/dist/navigation/navigation-index.d.ts.map +1 -1
  168. package/dist/navigation/navigation-index.js +372 -45
  169. package/dist/navigation/navigation-index.js.map +1 -1
  170. package/dist/navigation/python-graph.d.ts +73 -0
  171. package/dist/navigation/python-graph.d.ts.map +1 -0
  172. package/dist/navigation/python-graph.js +867 -0
  173. package/dist/navigation/python-graph.js.map +1 -0
  174. package/dist/navigation/ts-call-edges.d.ts +193 -0
  175. package/dist/navigation/ts-call-edges.d.ts.map +1 -0
  176. package/dist/navigation/ts-call-edges.js +2089 -0
  177. package/dist/navigation/ts-call-edges.js.map +1 -0
  178. package/dist/navigation/ts-entry-tables.d.ts +88 -0
  179. package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
  180. package/dist/navigation/ts-entry-tables.js +50 -0
  181. package/dist/navigation/ts-entry-tables.js.map +1 -0
  182. package/dist/path-set/branches.d.ts +52 -0
  183. package/dist/path-set/branches.d.ts.map +1 -0
  184. package/dist/path-set/branches.js +124 -0
  185. package/dist/path-set/branches.js.map +1 -0
  186. package/dist/path-set/finding-diff.d.ts +63 -0
  187. package/dist/path-set/finding-diff.d.ts.map +1 -0
  188. package/dist/path-set/finding-diff.js +150 -0
  189. package/dist/path-set/finding-diff.js.map +1 -0
  190. package/dist/path-set/index.d.ts +18 -0
  191. package/dist/path-set/index.d.ts.map +1 -0
  192. package/dist/path-set/index.js +12 -0
  193. package/dist/path-set/index.js.map +1 -0
  194. package/dist/path-set/line-diff.d.ts +19 -0
  195. package/dist/path-set/line-diff.d.ts.map +1 -0
  196. package/dist/path-set/line-diff.js +98 -0
  197. package/dist/path-set/line-diff.js.map +1 -0
  198. package/dist/path-set/path-set.d.ts +88 -0
  199. package/dist/path-set/path-set.d.ts.map +1 -0
  200. package/dist/path-set/path-set.js +716 -0
  201. package/dist/path-set/path-set.js.map +1 -0
  202. package/dist/path-set/templates.d.ts +42 -0
  203. package/dist/path-set/templates.d.ts.map +1 -0
  204. package/dist/path-set/templates.js +119 -0
  205. package/dist/path-set/templates.js.map +1 -0
  206. package/dist/path-set/types.d.ts +195 -0
  207. package/dist/path-set/types.d.ts.map +1 -0
  208. package/dist/path-set/types.js +8 -0
  209. package/dist/path-set/types.js.map +1 -0
  210. package/dist/remedy-class/index.d.ts +12 -0
  211. package/dist/remedy-class/index.d.ts.map +1 -0
  212. package/dist/remedy-class/index.js +9 -0
  213. package/dist/remedy-class/index.js.map +1 -0
  214. package/dist/remedy-class/match.d.ts +165 -0
  215. package/dist/remedy-class/match.d.ts.map +1 -0
  216. package/dist/remedy-class/match.js +705 -0
  217. package/dist/remedy-class/match.js.map +1 -0
  218. package/dist/remedy-class/output-context.d.ts +25 -0
  219. package/dist/remedy-class/output-context.d.ts.map +1 -0
  220. package/dist/remedy-class/output-context.js +150 -0
  221. package/dist/remedy-class/output-context.js.map +1 -0
  222. package/dist/remedy-class/sha256.d.ts +18 -0
  223. package/dist/remedy-class/sha256.d.ts.map +1 -0
  224. package/dist/remedy-class/sha256.js +89 -0
  225. package/dist/remedy-class/sha256.js.map +1 -0
  226. package/dist/remedy-class/table.d.ts +130 -0
  227. package/dist/remedy-class/table.d.ts.map +1 -0
  228. package/dist/remedy-class/table.js +457 -0
  229. package/dist/remedy-class/table.js.map +1 -0
  230. package/dist/resolution/cross-file.d.ts +18 -0
  231. package/dist/resolution/cross-file.d.ts.map +1 -1
  232. package/dist/resolution/cross-file.js +194 -5
  233. package/dist/resolution/cross-file.js.map +1 -1
  234. package/dist/stable-ids/builder.d.ts +20 -0
  235. package/dist/stable-ids/builder.d.ts.map +1 -0
  236. package/dist/stable-ids/builder.js +65 -0
  237. package/dist/stable-ids/builder.js.map +1 -0
  238. package/dist/stable-ids/diff.d.ts +18 -0
  239. package/dist/stable-ids/diff.d.ts.map +1 -0
  240. package/dist/stable-ids/diff.js +37 -0
  241. package/dist/stable-ids/diff.js.map +1 -0
  242. package/dist/stable-ids/go.d.ts +28 -0
  243. package/dist/stable-ids/go.d.ts.map +1 -0
  244. package/dist/stable-ids/go.js +160 -0
  245. package/dist/stable-ids/go.js.map +1 -0
  246. package/dist/stable-ids/hash.d.ts +22 -0
  247. package/dist/stable-ids/hash.d.ts.map +1 -0
  248. package/dist/stable-ids/hash.js +167 -0
  249. package/dist/stable-ids/hash.js.map +1 -0
  250. package/dist/stable-ids/index.d.ts +30 -0
  251. package/dist/stable-ids/index.d.ts.map +1 -0
  252. package/dist/stable-ids/index.js +59 -0
  253. package/dist/stable-ids/index.js.map +1 -0
  254. package/dist/stable-ids/java.d.ts +28 -0
  255. package/dist/stable-ids/java.d.ts.map +1 -0
  256. package/dist/stable-ids/java.js +224 -0
  257. package/dist/stable-ids/java.js.map +1 -0
  258. package/dist/stable-ids/moves.d.ts +87 -0
  259. package/dist/stable-ids/moves.d.ts.map +1 -0
  260. package/dist/stable-ids/moves.js +222 -0
  261. package/dist/stable-ids/moves.js.map +1 -0
  262. package/dist/stable-ids/nodes.d.ts +32 -0
  263. package/dist/stable-ids/nodes.d.ts.map +1 -0
  264. package/dist/stable-ids/nodes.js +92 -0
  265. package/dist/stable-ids/nodes.js.map +1 -0
  266. package/dist/stable-ids/normalize.d.ts +35 -0
  267. package/dist/stable-ids/normalize.d.ts.map +1 -0
  268. package/dist/stable-ids/normalize.js +174 -0
  269. package/dist/stable-ids/normalize.js.map +1 -0
  270. package/dist/stable-ids/parse-errors.d.ts +24 -0
  271. package/dist/stable-ids/parse-errors.d.ts.map +1 -0
  272. package/dist/stable-ids/parse-errors.js +46 -0
  273. package/dist/stable-ids/parse-errors.js.map +1 -0
  274. package/dist/stable-ids/python.d.ts +27 -0
  275. package/dist/stable-ids/python.d.ts.map +1 -0
  276. package/dist/stable-ids/python.js +212 -0
  277. package/dist/stable-ids/python.js.map +1 -0
  278. package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
  279. package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
  280. package/dist/stable-ids/ts-parse-errors.js +75 -0
  281. package/dist/stable-ids/ts-parse-errors.js.map +1 -0
  282. package/dist/stable-ids/types.d.ts +106 -0
  283. package/dist/stable-ids/types.d.ts.map +1 -0
  284. package/dist/stable-ids/types.js +16 -0
  285. package/dist/stable-ids/types.js.map +1 -0
  286. package/dist/stable-ids/typescript.d.ts +30 -0
  287. package/dist/stable-ids/typescript.d.ts.map +1 -0
  288. package/dist/stable-ids/typescript.js +362 -0
  289. package/dist/stable-ids/typescript.js.map +1 -0
  290. package/dist/types/config.d.ts +9 -0
  291. package/dist/types/config.d.ts.map +1 -1
  292. package/dist/types/index.d.ts +53 -0
  293. package/dist/types/index.d.ts.map +1 -1
  294. package/dist/types/index.js.map +1 -1
  295. package/package.json +3 -3
@@ -4709,14 +4709,43 @@ function extractJavaTypes(tree, cache, options) {
4709
4709
  );
4710
4710
  const path = node ? enclosingTypePath(node) : [];
4711
4711
  if (path.length > 0) t.enclosing_type = path.join(".");
4712
- const params = node?.childForFieldName("type_parameters");
4713
- if (params) {
4714
- const names = params.namedChildren.filter((c) => c?.type === "type_parameter").map((c) => c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier")).map((x) => x ? getNodeText(x) : "").filter(Boolean);
4715
- if (names.length > 0) t.type_parameters = names;
4712
+ const tp = typeParameters(node);
4713
+ if (tp.names.length > 0) t.type_parameters = tp.names;
4714
+ if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
4715
+ }
4716
+ const allMethods = /* @__PURE__ */ new Map();
4717
+ for (const t of types) {
4718
+ for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
4719
+ }
4720
+ for (const kind of ["method_declaration", "constructor_declaration"]) {
4721
+ for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
4722
+ const name2 = getIdentifier(node, "name");
4723
+ const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
4724
+ if (!info2) continue;
4725
+ const tp = typeParameters(node);
4726
+ if (tp.names.length > 0) info2.type_parameters = tp.names;
4727
+ if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
4716
4728
  }
4717
4729
  }
4718
4730
  return types;
4719
4731
  }
4732
+ function typeParameters(node) {
4733
+ const names = [];
4734
+ const bounds = {};
4735
+ const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
4736
+ if (!list) return { names, bounds };
4737
+ for (const c of list.namedChildren) {
4738
+ if (c?.type !== "type_parameter") continue;
4739
+ const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
4740
+ const name2 = id ? getNodeText(id) : "";
4741
+ if (!name2) continue;
4742
+ names.push(name2);
4743
+ const bound = c.namedChildren.find((x) => x?.type === "type_bound");
4744
+ const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
4745
+ if (first) bounds[name2] = getNodeText(first);
4746
+ }
4747
+ return { names, bounds };
4748
+ }
4720
4749
  function recordComponents(node) {
4721
4750
  const out2 = [];
4722
4751
  const params = node.childForFieldName("parameters");
@@ -10427,6 +10456,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10427
10456
  carryBranchDefsOut(popped, scopeStack, alwaysRuns);
10428
10457
  break;
10429
10458
  }
10459
+ case "try_statement":
10460
+ case "try_with_resources_statement": {
10461
+ if (isJavaScript) {
10462
+ const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
10463
+ uses.push(...stmtUses.uses);
10464
+ useId = stmtUses.nextId;
10465
+ break;
10466
+ }
10467
+ scopeStack.push(/* @__PURE__ */ new Map());
10468
+ const resources = stmt.childForFieldName("resources");
10469
+ for (const res of resources?.namedChildren ?? []) {
10470
+ if (!res || res.type !== "resource") continue;
10471
+ const nameNode = res.childForFieldName("name");
10472
+ const value = res.childForFieldName("value");
10473
+ if (value) {
10474
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
10475
+ uses.push(...valueUses.uses);
10476
+ useId = valueUses.nextId;
10477
+ }
10478
+ if (nameNode && nameNode.type === "identifier") {
10479
+ const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
10480
+ defs.push(def);
10481
+ currentScope(scopeStack).set(def.variable, def.id);
10482
+ markDeclared(currentScope(scopeStack), def.variable);
10483
+ } else if (!value) {
10484
+ const resUses = extractUses(res, useId, scopeStack, isJavaScript);
10485
+ uses.push(...resUses.uses);
10486
+ useId = resUses.nextId;
10487
+ }
10488
+ }
10489
+ for (const part of stmt.namedChildren) {
10490
+ if (!part) continue;
10491
+ if (part.type === "block") {
10492
+ const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
10493
+ defs.push(...r.defs);
10494
+ uses.push(...r.uses);
10495
+ defId = r.nextDefId;
10496
+ useId = r.nextUseId;
10497
+ } else if (part.type === "catch_clause") {
10498
+ scopeStack.push(/* @__PURE__ */ new Map());
10499
+ for (const param of part.namedChildren) {
10500
+ if (!param || param.type !== "catch_formal_parameter") continue;
10501
+ const pname = param.childForFieldName("name");
10502
+ if (pname) {
10503
+ const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
10504
+ defs.push(def);
10505
+ currentScope(scopeStack).set(def.variable, def.id);
10506
+ markDeclared(currentScope(scopeStack), def.variable);
10507
+ }
10508
+ }
10509
+ const body2 = part.childForFieldName("body");
10510
+ if (body2) {
10511
+ const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
10512
+ defs.push(...r.defs);
10513
+ uses.push(...r.uses);
10514
+ defId = r.nextDefId;
10515
+ useId = r.nextUseId;
10516
+ }
10517
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10518
+ } else if (part.type === "finally_clause") {
10519
+ for (const b of part.namedChildren) {
10520
+ if (!b || b.type !== "block") continue;
10521
+ const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
10522
+ defs.push(...r.defs);
10523
+ uses.push(...r.uses);
10524
+ defId = r.nextDefId;
10525
+ useId = r.nextUseId;
10526
+ }
10527
+ }
10528
+ }
10529
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10530
+ break;
10531
+ }
10430
10532
  case "else_clause": {
10431
10533
  for (let i2 = 0; i2 < stmt.childCount; i2++) {
10432
10534
  const arm = stmt.child(i2);
@@ -11042,6 +11144,42 @@ function buildBashDFG(tree) {
11042
11144
  defs.push(def);
11043
11145
  currentScope(scopeStack).set(p, def.id);
11044
11146
  }
11147
+ const bashFnDefs = /* @__PURE__ */ new Map();
11148
+ const bashFnByNodeId = /* @__PURE__ */ new Map();
11149
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11150
+ const nameNode = fn.childForFieldName("name");
11151
+ if (!nameNode) continue;
11152
+ const fnName = getNodeText(nameNode);
11153
+ if (!fnName || bashFnDefs.has(fnName)) continue;
11154
+ const m = /* @__PURE__ */ new Map();
11155
+ for (const p of positionalParams) {
11156
+ const def = {
11157
+ id: defIdCounter++,
11158
+ variable: p,
11159
+ line: fn.startPosition.row + 1,
11160
+ kind: "param",
11161
+ expression: `bash-fn:${fnName}`
11162
+ };
11163
+ defs.push(def);
11164
+ m.set(p, def.id);
11165
+ }
11166
+ bashFnDefs.set(fnName, m);
11167
+ bashFnByNodeId.set(fn.id, fnName);
11168
+ }
11169
+ const enclosingBashFn = (node) => {
11170
+ let cur = node.parent;
11171
+ while (cur) {
11172
+ if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
11173
+ cur = cur.parent;
11174
+ }
11175
+ return null;
11176
+ };
11177
+ const positionalDef = (varName, node, fallback) => {
11178
+ if (!positionalParams.includes(varName)) return fallback;
11179
+ const fnName = enclosingBashFn(node);
11180
+ if (!fnName) return fallback;
11181
+ return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
11182
+ };
11045
11183
  walkTree(tree.rootNode, (node) => {
11046
11184
  if (node.type === "variable_assignment") {
11047
11185
  const nameNode = node.childForFieldName("name");
@@ -11094,7 +11232,7 @@ function buildBashDFG(tree) {
11094
11232
  if (varNameNode) {
11095
11233
  const varName = getNodeText(varNameNode);
11096
11234
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
11097
- let reachingDef = findReachingDef(varName, scopeStack);
11235
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11098
11236
  if (reachingDef === null && !positionalParams.includes(varName)) {
11099
11237
  const def = {
11100
11238
  id: defIdCounter++,
@@ -11118,7 +11256,7 @@ function buildBashDFG(tree) {
11118
11256
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
11119
11257
  if (varNameNode && varNameNode.type === "variable_name") {
11120
11258
  const varName = getNodeText(varNameNode);
11121
- let reachingDef = findReachingDef(varName, scopeStack);
11259
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11122
11260
  if (reachingDef === null && !positionalParams.includes(varName)) {
11123
11261
  const def = {
11124
11262
  id: defIdCounter++,
@@ -11140,8 +11278,56 @@ function buildBashDFG(tree) {
11140
11278
  }
11141
11279
  });
11142
11280
  const chains = computeChains(defs, uses);
11281
+ chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
11143
11282
  return { defs, uses, chains };
11144
11283
  }
11284
+ function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
11285
+ const chains = [];
11286
+ if (bashFnDefs.size === 0) return chains;
11287
+ const renumbers = /* @__PURE__ */ new Set();
11288
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11289
+ const nameNode = fn.childForFieldName("name");
11290
+ if (!nameNode) continue;
11291
+ if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
11292
+ }
11293
+ const usesByLine = /* @__PURE__ */ new Map();
11294
+ for (const u of uses) {
11295
+ const l = usesByLine.get(u.line) ?? [];
11296
+ l.push(u);
11297
+ usesByLine.set(u.line, l);
11298
+ }
11299
+ for (const cmd of findNodes(tree.rootNode, "command")) {
11300
+ const nameNode = cmd.childForFieldName("name");
11301
+ if (!nameNode) continue;
11302
+ const fnDefs = bashFnDefs.get(getNodeText(nameNode));
11303
+ if (!fnDefs) continue;
11304
+ const fnName = getNodeText(nameNode);
11305
+ let position = 0;
11306
+ for (let i2 = 0; i2 < cmd.childCount; i2++) {
11307
+ const arg = cmd.child(i2);
11308
+ if (!arg || arg.id === nameNode.id) continue;
11309
+ if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
11310
+ const text = getNodeText(arg);
11311
+ if (!text.trim()) continue;
11312
+ position++;
11313
+ const names = /* @__PURE__ */ new Set();
11314
+ const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
11315
+ let m;
11316
+ while ((m = re.exec(text)) !== null) names.add(m[1]);
11317
+ if (names.size === 0) continue;
11318
+ const spreads = /\$\{?[@*]/.test(text);
11319
+ const targets = spreads || renumbers.has(fnName) ? positionalParams.map((p) => fnDefs.get(p)).filter((d) => d !== void 0) : [fnDefs.get(String(position))].filter((d) => d !== void 0);
11320
+ const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
11321
+ for (const name2 of names) {
11322
+ for (const u of lineUses) {
11323
+ if (u.variable !== name2 || u.def_id === null) continue;
11324
+ for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
11325
+ }
11326
+ }
11327
+ }
11328
+ }
11329
+ return chains;
11330
+ }
11145
11331
  function buildRustDFG(tree, cache) {
11146
11332
  const defs = [];
11147
11333
  const uses = [];
@@ -11851,6 +12037,198 @@ function javaReplaceCrLfCoverage(method, rawFrom) {
11851
12037
  return { cr, lf };
11852
12038
  }
11853
12039
 
12040
+ // src/analysis/js-crlf-strip.ts
12041
+ var MAX_PATTERN_LENGTH = 200;
12042
+ var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
12043
+ "replace",
12044
+ "replaceAll",
12045
+ "trim",
12046
+ "trimStart",
12047
+ "trimEnd",
12048
+ "toString",
12049
+ "toLowerCase",
12050
+ "toUpperCase",
12051
+ "slice",
12052
+ "substring"
12053
+ ]);
12054
+ var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
12055
+ function readQuoted(s, i2) {
12056
+ const q = s[i2];
12057
+ if (q !== '"' && q !== "'" && q !== "`") return null;
12058
+ let out2 = "";
12059
+ for (let j = i2 + 1; j < s.length; j++) {
12060
+ const c = s[j];
12061
+ if (c === q) return { value: out2, end: j + 1 };
12062
+ if (q === "`" && c === "$" && s[j + 1] === "{") return null;
12063
+ if (c === "\\") {
12064
+ const n = s[j + 1];
12065
+ if (n === void 0) return null;
12066
+ switch (n) {
12067
+ case "n":
12068
+ out2 += "\n";
12069
+ j++;
12070
+ continue;
12071
+ case "r":
12072
+ out2 += "\r";
12073
+ j++;
12074
+ continue;
12075
+ case "t":
12076
+ out2 += " ";
12077
+ j++;
12078
+ continue;
12079
+ case "0":
12080
+ out2 += "\0";
12081
+ j++;
12082
+ continue;
12083
+ case "x": {
12084
+ const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
12085
+ if (!h) return null;
12086
+ out2 += String.fromCharCode(parseInt(h[0], 16));
12087
+ j += 3;
12088
+ continue;
12089
+ }
12090
+ case "u": {
12091
+ const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
12092
+ if (!h) return null;
12093
+ out2 += String.fromCharCode(parseInt(h[0], 16));
12094
+ j += 5;
12095
+ continue;
12096
+ }
12097
+ default:
12098
+ out2 += n;
12099
+ j++;
12100
+ continue;
12101
+ }
12102
+ }
12103
+ if (c === "\n" && q !== "`") return null;
12104
+ out2 += c;
12105
+ }
12106
+ return null;
12107
+ }
12108
+ function readRegexLiteral(s, i2) {
12109
+ if (s[i2] !== "/") return null;
12110
+ let inClass = false;
12111
+ for (let j = i2 + 1; j < s.length; j++) {
12112
+ const c = s[j];
12113
+ if (c === "\n") return null;
12114
+ if (c === "\\") {
12115
+ j++;
12116
+ continue;
12117
+ }
12118
+ if (inClass) {
12119
+ if (c === "]") inClass = false;
12120
+ continue;
12121
+ }
12122
+ if (c === "[") {
12123
+ inClass = true;
12124
+ continue;
12125
+ }
12126
+ if (c === "/") {
12127
+ const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
12128
+ return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
12129
+ }
12130
+ }
12131
+ return null;
12132
+ }
12133
+ var skipWs = (s, i2) => {
12134
+ while (i2 < s.length && /\s/.test(s[i2])) i2++;
12135
+ return i2;
12136
+ };
12137
+ function parseSteps(chainText) {
12138
+ const steps = [];
12139
+ const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
12140
+ let m;
12141
+ while ((m = callRe.exec(chainText)) !== null) {
12142
+ const kind = m[1];
12143
+ let i2 = skipWs(chainText, m.index + m[0].length);
12144
+ let pattern;
12145
+ const rx = readRegexLiteral(chainText, i2);
12146
+ if (rx) {
12147
+ if (rx.source.length > MAX_PATTERN_LENGTH) return null;
12148
+ try {
12149
+ pattern = new RegExp(rx.source, rx.flags);
12150
+ } catch {
12151
+ return null;
12152
+ }
12153
+ i2 = rx.end;
12154
+ } else {
12155
+ const str = readQuoted(chainText, i2);
12156
+ if (!str) return null;
12157
+ pattern = str.value;
12158
+ i2 = str.end;
12159
+ }
12160
+ i2 = skipWs(chainText, i2);
12161
+ if (chainText[i2] !== ",") return null;
12162
+ i2 = skipWs(chainText, i2 + 1);
12163
+ const rep = readQuoted(chainText, i2);
12164
+ if (!rep) return null;
12165
+ i2 = skipWs(chainText, rep.end);
12166
+ if (chainText[i2] !== ")") return null;
12167
+ steps.push({ kind, pattern, replacement: rep.value });
12168
+ callRe.lastIndex = i2 + 1;
12169
+ }
12170
+ return steps;
12171
+ }
12172
+ function run2(steps, input) {
12173
+ let s = input;
12174
+ try {
12175
+ for (const st of steps) {
12176
+ if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
12177
+ s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
12178
+ }
12179
+ } catch {
12180
+ return null;
12181
+ }
12182
+ return s;
12183
+ }
12184
+ function jsReplaceChainVerdict(chainText) {
12185
+ const steps = parseSteps(chainText);
12186
+ if (!steps || steps.length === 0) return "undecided";
12187
+ const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
12188
+ for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
12189
+ if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
12190
+ }
12191
+ const out2 = run2(steps, PROBE);
12192
+ if (out2 === null) return "undecided";
12193
+ return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
12194
+ }
12195
+ function jsChainTextAt(sourceLines, line, column) {
12196
+ const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
12197
+ let depth = 0;
12198
+ let prevSig = "";
12199
+ let i2 = 0;
12200
+ for (; i2 < text.length; i2++) {
12201
+ const c = text[i2];
12202
+ if (c === '"' || c === "'" || c === "`") {
12203
+ for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
12204
+ prevSig = c;
12205
+ continue;
12206
+ }
12207
+ if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
12208
+ const rx = readRegexLiteral(text, i2);
12209
+ if (rx) {
12210
+ i2 = rx.end - 1;
12211
+ prevSig = "/";
12212
+ continue;
12213
+ }
12214
+ }
12215
+ if (c === "(" || c === "[" || c === "{") {
12216
+ depth++;
12217
+ prevSig = c;
12218
+ continue;
12219
+ }
12220
+ if (c === ")" || c === "]" || c === "}") {
12221
+ if (depth === 0) break;
12222
+ depth--;
12223
+ prevSig = c;
12224
+ continue;
12225
+ }
12226
+ if (depth === 0 && !/[\w$.\s]/.test(c)) break;
12227
+ if (!/\s/.test(c)) prevSig = c;
12228
+ }
12229
+ return text.slice(0, i2);
12230
+ }
12231
+
11854
12232
  // src/analysis/passes/language-sources-pass.ts
11855
12233
  var JINJA_LITERAL_ARG_RE_FRAG = `(?:"[^"\\\\]*"|'[^'\\\\]*')`;
11856
12234
  var JINJA_SAFE_RTS_RE = new RegExp(
@@ -12634,6 +13012,10 @@ var DEFAULT_SOURCES = [
12634
13012
  { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12635
13013
  { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12636
13014
  { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13015
+ // Node redis clients are often bound to `cache` from `createClient()`; class
13016
+ // `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
13017
+ // filtered in sink-filter-pass (#818 src14).
13018
+ { method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12637
13019
  { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
12638
13020
  { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
12639
13021
  { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
@@ -12813,7 +13195,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
12813
13195
  // on arg[1] (the *Cookie) catches struct-literal composite tainting.
12814
13196
  { method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
12815
13197
  // gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
12816
- { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
13198
+ { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
13199
+ // --- Rust: actix-session / tower-sessions write -------------------------
13200
+ { method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
12817
13201
  ];
12818
13202
  var DEFAULT_SINKS = [
12819
13203
  // SQL Injection (CWE-89)
@@ -13474,7 +13858,9 @@ var DEFAULT_SINKS = [
13474
13858
  { method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13475
13859
  { method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13476
13860
  // Cron expression parsing (DoS/ReDoS)
13477
- { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13861
+ // cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
13862
+ // `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
13863
+ { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
13478
13864
  { method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13479
13865
  // ff4j feature flags
13480
13866
  { method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
@@ -13571,9 +13957,13 @@ var DEFAULT_SINKS = [
13571
13957
  { method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13572
13958
  { method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13573
13959
  // XXE - XML External Entity (CWE-611)
13574
- { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13575
- { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13576
- { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13960
+ // cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
13961
+ // receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
13962
+ // `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
13963
+ // libxmljs, DOMParser) have their own entries.
13964
+ { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13965
+ { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13966
+ { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13577
13967
  { method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13578
13968
  { method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13579
13969
  { method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
@@ -13828,6 +14218,46 @@ var DEFAULT_SINKS = [
13828
14218
  { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
13829
14219
  { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
13830
14220
  { method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14221
+ // DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
14222
+ // `duckdb` / `duckdb-async` bindings (Database / Connection) and
14223
+ // `react-native-duckdb` (Database / Transaction). The method names are
14224
+ // generic and the receivers come from factories (`await instance.connect()`,
14225
+ // `open({...})`), so the receiver cannot be typed: the import is the gate
14226
+ // (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
14227
+ // as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
14228
+ // `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
14229
+ // not reach it.
14230
+ { method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
14231
+ { method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async", "react-native-duckdb"] },
14232
+ { method: "stream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async", "react-native-duckdb"] },
14233
+ { method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14234
+ { method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14235
+ { method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14236
+ { method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14237
+ { method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14238
+ { method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14239
+ { method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14240
+ { method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14241
+ { method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14242
+ { method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14243
+ { method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14244
+ { method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14245
+ { method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14246
+ { method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14247
+ { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14248
+ { method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14249
+ { method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14250
+ { method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14251
+ { method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14252
+ // RxDB (Mango queries, any storage incl. SQLite): a selector or query
14253
+ // object, a field name passed to the query builder, or a `$regex` pattern
14254
+ // under caller control is NoSQL operator injection (CWE-943). Gated on the
14255
+ // `rxdb` import for the same reason as DuckDB above. `find` is already a
14256
+ // classless JS NoSQL sink in the JavaScript plugin.
14257
+ { method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14258
+ { method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14259
+ { method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14260
+ { method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
13831
14261
  // TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
13832
14262
  // escape hatch is already covered by the classless `query` sink in the JS
13833
14263
  // plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
@@ -13938,6 +14368,8 @@ var DEFAULT_SINKS = [
13938
14368
  { method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13939
14369
  { method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13940
14370
  { method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14371
+ // Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
14372
+ { method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
13941
14373
  // needle library (used in NodeGoat)
13942
14374
  { method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13943
14375
  { method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -14432,7 +14864,7 @@ var DEFAULT_SINKS = [
14432
14864
  // receiver-taint or operator-LHS-taint tracking — the format string is the
14433
14865
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
14434
14866
  // C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
14435
- { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
14867
+ { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
14436
14868
  { method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
14437
14869
  // Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
14438
14870
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
@@ -15501,198 +15933,6 @@ function getDefaultConfig() {
15501
15933
  };
15502
15934
  }
15503
15935
 
15504
- // src/analysis/js-crlf-strip.ts
15505
- var MAX_PATTERN_LENGTH = 200;
15506
- var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
15507
- "replace",
15508
- "replaceAll",
15509
- "trim",
15510
- "trimStart",
15511
- "trimEnd",
15512
- "toString",
15513
- "toLowerCase",
15514
- "toUpperCase",
15515
- "slice",
15516
- "substring"
15517
- ]);
15518
- var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
15519
- function readQuoted(s, i2) {
15520
- const q = s[i2];
15521
- if (q !== '"' && q !== "'" && q !== "`") return null;
15522
- let out2 = "";
15523
- for (let j = i2 + 1; j < s.length; j++) {
15524
- const c = s[j];
15525
- if (c === q) return { value: out2, end: j + 1 };
15526
- if (q === "`" && c === "$" && s[j + 1] === "{") return null;
15527
- if (c === "\\") {
15528
- const n = s[j + 1];
15529
- if (n === void 0) return null;
15530
- switch (n) {
15531
- case "n":
15532
- out2 += "\n";
15533
- j++;
15534
- continue;
15535
- case "r":
15536
- out2 += "\r";
15537
- j++;
15538
- continue;
15539
- case "t":
15540
- out2 += " ";
15541
- j++;
15542
- continue;
15543
- case "0":
15544
- out2 += "\0";
15545
- j++;
15546
- continue;
15547
- case "x": {
15548
- const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
15549
- if (!h) return null;
15550
- out2 += String.fromCharCode(parseInt(h[0], 16));
15551
- j += 3;
15552
- continue;
15553
- }
15554
- case "u": {
15555
- const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
15556
- if (!h) return null;
15557
- out2 += String.fromCharCode(parseInt(h[0], 16));
15558
- j += 5;
15559
- continue;
15560
- }
15561
- default:
15562
- out2 += n;
15563
- j++;
15564
- continue;
15565
- }
15566
- }
15567
- if (c === "\n" && q !== "`") return null;
15568
- out2 += c;
15569
- }
15570
- return null;
15571
- }
15572
- function readRegexLiteral(s, i2) {
15573
- if (s[i2] !== "/") return null;
15574
- let inClass = false;
15575
- for (let j = i2 + 1; j < s.length; j++) {
15576
- const c = s[j];
15577
- if (c === "\n") return null;
15578
- if (c === "\\") {
15579
- j++;
15580
- continue;
15581
- }
15582
- if (inClass) {
15583
- if (c === "]") inClass = false;
15584
- continue;
15585
- }
15586
- if (c === "[") {
15587
- inClass = true;
15588
- continue;
15589
- }
15590
- if (c === "/") {
15591
- const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
15592
- return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
15593
- }
15594
- }
15595
- return null;
15596
- }
15597
- var skipWs = (s, i2) => {
15598
- while (i2 < s.length && /\s/.test(s[i2])) i2++;
15599
- return i2;
15600
- };
15601
- function parseSteps(chainText) {
15602
- const steps = [];
15603
- const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
15604
- let m;
15605
- while ((m = callRe.exec(chainText)) !== null) {
15606
- const kind = m[1];
15607
- let i2 = skipWs(chainText, m.index + m[0].length);
15608
- let pattern;
15609
- const rx = readRegexLiteral(chainText, i2);
15610
- if (rx) {
15611
- if (rx.source.length > MAX_PATTERN_LENGTH) return null;
15612
- try {
15613
- pattern = new RegExp(rx.source, rx.flags);
15614
- } catch {
15615
- return null;
15616
- }
15617
- i2 = rx.end;
15618
- } else {
15619
- const str = readQuoted(chainText, i2);
15620
- if (!str) return null;
15621
- pattern = str.value;
15622
- i2 = str.end;
15623
- }
15624
- i2 = skipWs(chainText, i2);
15625
- if (chainText[i2] !== ",") return null;
15626
- i2 = skipWs(chainText, i2 + 1);
15627
- const rep = readQuoted(chainText, i2);
15628
- if (!rep) return null;
15629
- i2 = skipWs(chainText, rep.end);
15630
- if (chainText[i2] !== ")") return null;
15631
- steps.push({ kind, pattern, replacement: rep.value });
15632
- callRe.lastIndex = i2 + 1;
15633
- }
15634
- return steps;
15635
- }
15636
- function run2(steps, input) {
15637
- let s = input;
15638
- try {
15639
- for (const st of steps) {
15640
- if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
15641
- s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
15642
- }
15643
- } catch {
15644
- return null;
15645
- }
15646
- return s;
15647
- }
15648
- function jsReplaceChainVerdict(chainText) {
15649
- const steps = parseSteps(chainText);
15650
- if (!steps || steps.length === 0) return "undecided";
15651
- const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
15652
- for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
15653
- if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
15654
- }
15655
- const out2 = run2(steps, PROBE);
15656
- if (out2 === null) return "undecided";
15657
- return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
15658
- }
15659
- function jsChainTextAt(sourceLines, line, column) {
15660
- const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
15661
- let depth = 0;
15662
- let prevSig = "";
15663
- let i2 = 0;
15664
- for (; i2 < text.length; i2++) {
15665
- const c = text[i2];
15666
- if (c === '"' || c === "'" || c === "`") {
15667
- for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
15668
- prevSig = c;
15669
- continue;
15670
- }
15671
- if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
15672
- const rx = readRegexLiteral(text, i2);
15673
- if (rx) {
15674
- i2 = rx.end - 1;
15675
- prevSig = "/";
15676
- continue;
15677
- }
15678
- }
15679
- if (c === "(" || c === "[" || c === "{") {
15680
- depth++;
15681
- prevSig = c;
15682
- continue;
15683
- }
15684
- if (c === ")" || c === "]" || c === "}") {
15685
- if (depth === 0) break;
15686
- depth--;
15687
- prevSig = c;
15688
- continue;
15689
- }
15690
- if (depth === 0 && !/[\w$.\s]/.test(c)) break;
15691
- if (!/\s/.test(c)) prevSig = c;
15692
- }
15693
- return text.slice(0, i2);
15694
- }
15695
-
15696
15936
  // src/analysis/constant-propagation/patterns.ts
15697
15937
  var TAINT_PATTERNS = [
15698
15938
  // HTTP Sources (Servlet API)
@@ -16082,13 +16322,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
16082
16322
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
16083
16323
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
16084
16324
  const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
16085
- let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
16325
+ let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
16326
+ sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
16086
16327
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
16087
16328
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
16088
16329
  const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
16089
16330
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
16090
16331
  return { sources, sinks: gatedSinks, sanitizers };
16091
16332
  }
16333
+ function isBoundParameterCall(call, pattern, sourceLines) {
16334
+ if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
16335
+ const first = call.arguments[0]?.expression?.trim() ?? "";
16336
+ if (first.startsWith("[") || first.startsWith("{")) return true;
16337
+ const receiver = call.receiver;
16338
+ if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
16339
+ const assignedFromPrepare = new RegExp(
16340
+ `\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
16341
+ );
16342
+ return sourceLines.some((line) => assignedFromPrepare.test(line));
16343
+ }
16344
+ function sinkPatternImportApplies(pattern, imports) {
16345
+ const required = pattern.requires_import;
16346
+ if (!required || required.length === 0) return true;
16347
+ if (!imports || imports.length === 0) return false;
16348
+ return imports.some((imp) => {
16349
+ const from = imp.from_package;
16350
+ if (!from) return false;
16351
+ return required.some((m) => from === m || from.startsWith(m + "/"));
16352
+ });
16353
+ }
16092
16354
  function sinkPatternAppliesTo(pattern, language) {
16093
16355
  if (language === void 0) return true;
16094
16356
  if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
@@ -16490,6 +16752,9 @@ function isInterproceduralTaintableType(typeName, language) {
16490
16752
  "IEnumerable",
16491
16753
  "ICollection",
16492
16754
  "IList",
16755
+ "List",
16756
+ "Hashtable",
16757
+ "ArrayList",
16493
16758
  "Dictionary",
16494
16759
  "IDictionary",
16495
16760
  "IReadOnlyList",
@@ -16503,6 +16768,46 @@ function isInterproceduralTaintableType(typeName, language) {
16503
16768
  const leaf = baseType.split(".").pop() ?? baseType;
16504
16769
  if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
16505
16770
  }
16771
+ if (language === "rust") {
16772
+ const trimmed = typeName.trim();
16773
+ const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
16774
+ const rustBase = unref.split("<")[0].trim();
16775
+ const rustScalars = [
16776
+ "String",
16777
+ "str",
16778
+ "bool",
16779
+ "char",
16780
+ "i8",
16781
+ "i16",
16782
+ "i32",
16783
+ "i64",
16784
+ "i128",
16785
+ "isize",
16786
+ "u8",
16787
+ "u16",
16788
+ "u32",
16789
+ "u64",
16790
+ "u128",
16791
+ "usize",
16792
+ "f32",
16793
+ "f64"
16794
+ ];
16795
+ if (rustScalars.includes(rustBase)) return true;
16796
+ if (rustBase === "()" || rustBase === "!") return false;
16797
+ if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
16798
+ return true;
16799
+ }
16800
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
16801
+ }
16802
+ if (language === "javascript" || language === "typescript") {
16803
+ const trimmed = typeName.trim();
16804
+ const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
16805
+ if (jsScalars.includes(baseType)) return true;
16806
+ if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
16807
+ if (/\bRecord\s*</.test(trimmed)) return true;
16808
+ if (["void", "undefined", "null", "never"].includes(baseType)) return false;
16809
+ if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
16810
+ }
16506
16811
  if (typeName.endsWith("[]")) {
16507
16812
  const elementType = typeName.slice(0, -2);
16508
16813
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -16617,6 +16922,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
16617
16922
  return false;
16618
16923
  }
16619
16924
  }
16925
+ if (program === "git") {
16926
+ const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
16927
+ const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
16928
+ let subcommand;
16929
+ if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
16930
+ subcommand = String(subcommandArg.literal);
16931
+ } else {
16932
+ const expr = (subcommandArg?.expression ?? "").trim();
16933
+ if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
16934
+ subcommand = expr.slice(1, -1);
16935
+ }
16936
+ }
16937
+ if (subcommand === "ls-remote") {
16938
+ const hasEndOfOptions = call.arguments.some((a) => {
16939
+ if (a.literal !== null && a.literal !== void 0) {
16940
+ return String(a.literal) === "--";
16941
+ }
16942
+ const expr = (a.expression ?? "").trim();
16943
+ return expr === '"--"' || expr === "'--'" || expr === "`--`";
16944
+ });
16945
+ if (!hasEndOfOptions) {
16946
+ return false;
16947
+ }
16948
+ }
16949
+ }
16620
16950
  return true;
16621
16951
  }
16622
16952
  function isSafeJSChildProcessCall(call, pattern, language) {
@@ -17020,6 +17350,29 @@ function isRegexReceiver(receiver, sourceLines) {
17020
17350
  const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
17021
17351
  return sourceLines.some((l) => decl.test(l));
17022
17352
  }
17353
+ function isNavigationReceiverName(receiver) {
17354
+ const r = (receiver ?? "").trim();
17355
+ if (!r) return false;
17356
+ const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
17357
+ return /router|history|navigat|^nav$/i.test(last);
17358
+ }
17359
+ function isInMemoryMapGetReceiver(receiver, sourceLines) {
17360
+ const raw = (receiver ?? "").trim();
17361
+ if (!raw || !sourceLines) return false;
17362
+ if (/\.properties$/i.test(raw)) return true;
17363
+ const lastDot = raw.lastIndexOf(".");
17364
+ const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
17365
+ if (/^(properties|memoryCache)$/i.test(r)) return true;
17366
+ if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
17367
+ const n = escapeRe(r);
17368
+ const mapCtor = new RegExp(
17369
+ `\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
17370
+ );
17371
+ const mapType = new RegExp(
17372
+ `\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
17373
+ );
17374
+ return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
17375
+ }
17023
17376
  function isDeclaredFunctionReference(arg, sourceLines) {
17024
17377
  if (arg.literal !== null && arg.literal !== void 0) return false;
17025
17378
  if (!sourceLines) return false;
@@ -17224,6 +17577,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17224
17577
  if (isParameterizedQueryCall(call, pattern)) {
17225
17578
  continue;
17226
17579
  }
17580
+ if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
17581
+ continue;
17582
+ }
17583
+ if (isBoundParameterCall(call, pattern, sourceLines)) {
17584
+ continue;
17585
+ }
17227
17586
  if (isSafePythonSubprocessCall(call, pattern, language)) {
17228
17587
  continue;
17229
17588
  }
@@ -17281,9 +17640,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17281
17640
  if (isArgumentlessJsResponseWriter(call, pattern, language)) {
17282
17641
  continue;
17283
17642
  }
17643
+ if (pattern.type === "open_redirect" && (language === "javascript" || language === "typescript") && (call.method_name === "push" || call.method_name === "replace") && (pattern.class ?? "").toLowerCase() === "router" && !call.receiver_type && !call.receiver_type_fqn && !isNavigationReceiverName(call.receiver)) {
17644
+ continue;
17645
+ }
17284
17646
  if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
17285
17647
  continue;
17286
17648
  }
17649
+ if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
17650
+ continue;
17651
+ }
17287
17652
  if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
17288
17653
  continue;
17289
17654
  }
@@ -17300,7 +17665,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17300
17665
  continue;
17301
17666
  }
17302
17667
  const location = formatCallLocation(call);
17303
- const key = `${location}:${call.location.line}:${pattern.cwe}`;
17668
+ const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
17304
17669
  const confidence = calculateSinkConfidence(call, pattern);
17305
17670
  const existing = sinkMap.get(key);
17306
17671
  if (!existing || confidence > existing.confidence) {
@@ -17311,6 +17676,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17311
17676
  cwe: pattern.cwe,
17312
17677
  location,
17313
17678
  line: call.location.line,
17679
+ callColumn: call.location.column,
17314
17680
  confidence,
17315
17681
  method: call.method_name,
17316
17682
  argPositions: pattern.arg_positions,
@@ -17348,6 +17714,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
17348
17714
  return false;
17349
17715
  }
17350
17716
  }
17717
+ if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
17718
+ const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
17719
+ if (expressRequestAccessors.has(pattern.method)) {
17720
+ const recv = call.receiver?.toLowerCase() ?? "";
17721
+ if (recv !== "req" && recv !== "request" && recv !== "ctx") {
17722
+ return false;
17723
+ }
17724
+ }
17725
+ }
17351
17726
  return pattern.return_tainted === true;
17352
17727
  }
17353
17728
  return false;
@@ -17441,6 +17816,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
17441
17816
  "database",
17442
17817
  "sqlite",
17443
17818
  "conn",
17819
+ "con",
17444
17820
  "connection",
17445
17821
  "client",
17446
17822
  "pool",
@@ -17717,7 +18093,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
17717
18093
  }
17718
18094
  }
17719
18095
  }
17720
- if (!pattern.class && call.receiver) {
18096
+ if (!pattern.class && call.receiver && !pattern.requires_import) {
17721
18097
  if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
17722
18098
  return false;
17723
18099
  }
@@ -18450,6 +18826,8 @@ function javaReplaceChainCredit(chainText) {
18450
18826
  let lf = false;
18451
18827
  let lt = false;
18452
18828
  let gt = false;
18829
+ let openBrace = false;
18830
+ let closeBrace = false;
18453
18831
  for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
18454
18832
  const kind = m[1];
18455
18833
  const rawFrom = m[2] ?? m[3] ?? "";
@@ -18460,10 +18838,13 @@ function javaReplaceChainCredit(chainText) {
18460
18838
  if (cov.lf) lf = true;
18461
18839
  if (from === "<" && to.startsWith("&")) lt = true;
18462
18840
  if (from === ">" && to.startsWith("&")) gt = true;
18841
+ if (from === "{") openBrace = true;
18842
+ if (from === "}") closeBrace = true;
18463
18843
  }
18464
18844
  const out2 = [];
18465
18845
  if (cr && lf) out2.push("log_injection");
18466
18846
  if (lt && gt) out2.push("xss");
18847
+ if (openBrace && closeBrace) out2.push("code_injection");
18467
18848
  return out2;
18468
18849
  }
18469
18850
  function matchesSanitizerPattern(call, pattern) {
@@ -19793,6 +20174,11 @@ var ConstantPropagator = class _ConstantPropagator {
19793
20174
  taintedArrayElements = /* @__PURE__ */ new Map();
19794
20175
  // Track current method name for scoping local variables
19795
20176
  currentMethod = null;
20177
+ /** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
20178
+ blockScopeStack = [];
20179
+ lineBlockScopes = /* @__PURE__ */ new Map();
20180
+ /** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
20181
+ skipMethodBodyBlockScope = false;
19796
20182
  // Track conditional taints: which variables were tainted under which conditions
19797
20183
  // Maps condition expression string → set of variables tainted under that condition
19798
20184
  conditionalTaints = /* @__PURE__ */ new Map();
@@ -19806,6 +20192,8 @@ var ConstantPropagator = class _ConstantPropagator {
19806
20192
  inSynchronizedBlock = false;
19807
20193
  // Track iterator sources: iterator variable name → collection name it was created from
19808
20194
  iteratorSources = /* @__PURE__ */ new Map();
20195
+ /** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
20196
+ taintedFromIteratorNext = /* @__PURE__ */ new Set();
19809
20197
  // Track class field names (declared at class level, not local variables)
19810
20198
  classFields = /* @__PURE__ */ new Set();
19811
20199
  // Track tainted method parameters for inter-procedural analysis
@@ -19863,12 +20251,16 @@ var ConstantPropagator = class _ConstantPropagator {
19863
20251
  this.sanitizedVars.clear();
19864
20252
  this.sanitizedVarUncovered.clear();
19865
20253
  this.currentMethod = null;
20254
+ this.blockScopeStack = [];
20255
+ this.lineBlockScopes.clear();
20256
+ this.skipMethodBodyBlockScope = false;
19866
20257
  this.conditionalTaints.clear();
19867
20258
  this.conditionStack = [];
19868
20259
  this.lineConditions.clear();
19869
20260
  this.synchronizedLines.clear();
19870
20261
  this.inSynchronizedBlock = false;
19871
20262
  this.iteratorSources.clear();
20263
+ this.taintedFromIteratorNext.clear();
19872
20264
  this.classFields.clear();
19873
20265
  this.instanceFieldTaint.clear();
19874
20266
  this.currentClassName = null;
@@ -19893,6 +20285,9 @@ var ConstantPropagator = class _ConstantPropagator {
19893
20285
  const resultSymbols = new Map(this.symbols);
19894
20286
  for (const name2 of this.tainted) {
19895
20287
  if (name2.includes(":")) {
20288
+ if (/^[^:]+:b\d+:/.test(name2)) {
20289
+ continue;
20290
+ }
19896
20291
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19897
20292
  resultTainted.add(unscoped);
19898
20293
  }
@@ -19907,6 +20302,9 @@ var ConstantPropagator = class _ConstantPropagator {
19907
20302
  for (const [name2, value] of this.symbols) {
19908
20303
  if (this.goScopedNames.has(name2)) continue;
19909
20304
  if (name2.includes(":")) {
20305
+ if (/^[^:]+:b\d+:/.test(name2)) {
20306
+ continue;
20307
+ }
19910
20308
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19911
20309
  if (!resultSymbols.has(unscoped)) {
19912
20310
  resultSymbols.set(unscoped, value);
@@ -19916,6 +20314,7 @@ var ConstantPropagator = class _ConstantPropagator {
19916
20314
  return {
19917
20315
  symbols: resultSymbols,
19918
20316
  tainted: resultTainted,
20317
+ lineBlockScopes: new Map(this.lineBlockScopes),
19919
20318
  unreachableLines: new Set(this.unreachableLines),
19920
20319
  taintedCollections: new Map(this.taintedCollections),
19921
20320
  taintedArrayElements: new Map(this.taintedArrayElements),
@@ -20459,6 +20858,18 @@ var ConstantPropagator = class _ConstantPropagator {
20459
20858
  // ===========================================================================
20460
20859
  // Taint Refinement
20461
20860
  // ===========================================================================
20861
+ /** True when `node` is `iter.next()` (possibly under a cast). */
20862
+ isIteratorNextExpression(node) {
20863
+ const text = getNodeText2(node, this.source);
20864
+ if (/\.\s*next\s*\(/.test(text)) return true;
20865
+ let current = node;
20866
+ if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
20867
+ current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
20868
+ }
20869
+ if (!current || current.type !== "method_invocation") return false;
20870
+ const nameNode = current.childForFieldName("name");
20871
+ return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
20872
+ }
20462
20873
  refineTaintFromConstants() {
20463
20874
  let changed = true;
20464
20875
  let iterations = 0;
@@ -20468,6 +20879,9 @@ var ConstantPropagator = class _ConstantPropagator {
20468
20879
  iterations++;
20469
20880
  const toRemove = [];
20470
20881
  for (const varName of this.tainted) {
20882
+ if (this.taintedFromIteratorNext.has(varName)) {
20883
+ continue;
20884
+ }
20471
20885
  const symbol = this.symbols.get(varName);
20472
20886
  if (symbol && symbol.type !== "unknown") {
20473
20887
  toRemove.push(varName);
@@ -20516,6 +20930,9 @@ var ConstantPropagator = class _ConstantPropagator {
20516
20930
  if (this.unreachableLines.has(line)) {
20517
20931
  return true;
20518
20932
  }
20933
+ if (this.blockScopeStack.length > 0) {
20934
+ this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
20935
+ }
20519
20936
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
20520
20937
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
20521
20938
  }
@@ -20541,12 +20958,24 @@ var ConstantPropagator = class _ConstantPropagator {
20541
20958
  case "assignment_statement":
20542
20959
  this.handleGoAssignment(node);
20543
20960
  return false;
20961
+ case "var_declaration":
20962
+ this.handleGoVarDeclaration(node);
20963
+ return false;
20544
20964
  case "update_expression":
20545
20965
  this.handleUpdateExpression(node);
20546
20966
  return false;
20547
20967
  case "if_statement":
20548
20968
  this.handleIfStatement(node);
20549
20969
  return true;
20970
+ case "block": {
20971
+ if (this.skipMethodBodyBlockScope) {
20972
+ this.skipMethodBodyBlockScope = false;
20973
+ }
20974
+ for (const child of node.children) {
20975
+ this.visit(child);
20976
+ }
20977
+ return true;
20978
+ }
20550
20979
  case "switch_expression":
20551
20980
  case "switch_statement":
20552
20981
  this.handleSwitch(node);
@@ -20632,7 +21061,9 @@ var ConstantPropagator = class _ConstantPropagator {
20632
21061
  }
20633
21062
  const body2 = node.childForFieldName("body");
20634
21063
  if (body2) {
21064
+ this.skipMethodBodyBlockScope = true;
20635
21065
  this.visit(body2);
21066
+ this.skipMethodBodyBlockScope = false;
20636
21067
  }
20637
21068
  this.currentMethod = prevMethod;
20638
21069
  this.inConstructor = prevInConstructor;
@@ -20646,10 +21077,31 @@ var ConstantPropagator = class _ConstantPropagator {
20646
21077
  if (varName.includes(".") || varName.includes(":")) {
20647
21078
  return varName;
20648
21079
  }
21080
+ const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
20649
21081
  if (this.currentMethod) {
20650
- return `${this.currentMethod}:${varName}`;
21082
+ return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
20651
21083
  }
20652
- return varName;
21084
+ return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
21085
+ }
21086
+ /** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
21087
+ getScopedNameCandidates(varName) {
21088
+ if (varName.includes(".") || varName.includes(":")) {
21089
+ return [varName];
21090
+ }
21091
+ const out2 = [];
21092
+ for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
21093
+ const blockId = this.blockScopeStack[i2];
21094
+ if (this.currentMethod) {
21095
+ out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
21096
+ } else {
21097
+ out2.push(`b${blockId}:${varName}`);
21098
+ }
21099
+ }
21100
+ if (this.currentMethod) {
21101
+ out2.push(`${this.currentMethod}:${varName}`);
21102
+ }
21103
+ out2.push(varName);
21104
+ return out2;
20653
21105
  }
20654
21106
  /**
20655
21107
  * Look up a variable value, checking both scoped and unscoped names.
@@ -20657,11 +21109,12 @@ var ConstantPropagator = class _ConstantPropagator {
20657
21109
  * either local (scoped) or global (unscoped, like class fields).
20658
21110
  */
20659
21111
  lookupSymbol(varName) {
20660
- if (this.currentMethod && !varName.includes(".") && !varName.includes(":")) {
20661
- const scopedName = `${this.currentMethod}:${varName}`;
20662
- const scopedValue = this.symbols.get(scopedName);
20663
- if (scopedValue) {
20664
- return scopedValue;
21112
+ if (!varName.includes(".") && !varName.includes(":")) {
21113
+ for (const scopedName of this.getScopedNameCandidates(varName)) {
21114
+ const scopedValue = this.symbols.get(scopedName);
21115
+ if (scopedValue) {
21116
+ return scopedValue;
21117
+ }
20665
21118
  }
20666
21119
  }
20667
21120
  return this.symbols.get(varName);
@@ -20713,8 +21166,22 @@ var ConstantPropagator = class _ConstantPropagator {
20713
21166
  this.trackIteratorsInNode(initNode);
20714
21167
  }
20715
21168
  }
20716
- for (const child of node.children) {
20717
- this.visit(child);
21169
+ if (node.type === "while_statement" || node.type === "do_statement") {
21170
+ const body2 = node.childForFieldName("body");
21171
+ for (const child of node.children) {
21172
+ if (child !== body2) this.visit(child);
21173
+ }
21174
+ if (body2?.type === "block") {
21175
+ this.blockScopeStack.push(getNodeLine(body2));
21176
+ this.visit(body2);
21177
+ this.blockScopeStack.pop();
21178
+ } else if (body2) {
21179
+ this.visit(body2);
21180
+ }
21181
+ } else {
21182
+ for (const child of node.children) {
21183
+ this.visit(child);
21184
+ }
20718
21185
  }
20719
21186
  for (const varName of loopVarNames) {
20720
21187
  this.symbols.set(varName, createUnknown(getNodeLine(node)));
@@ -20856,6 +21323,10 @@ var ConstantPropagator = class _ConstantPropagator {
20856
21323
  this.sanitizedVars.delete(scopedName);
20857
21324
  this.sanitizedVarUncovered.delete(scopedName);
20858
21325
  this.definitionNodes.set(scopedName, valueNode);
21326
+ if (this.isIteratorNextExpression(valueNode)) {
21327
+ this.taintedFromIteratorNext.add(scopedName);
21328
+ this.taintedFromIteratorNext.add(varName);
21329
+ }
20859
21330
  this.symbols.set(scopedName, createUnknown(line));
20860
21331
  } else {
20861
21332
  if (this.inConditionalBranch) {
@@ -20897,6 +21368,64 @@ var ConstantPropagator = class _ConstantPropagator {
20897
21368
  * helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
20898
21369
  * assignments got before this handler existed: the DFG decides.
20899
21370
  */
21371
+ /** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
21372
+ handleGoVarDeclaration(node) {
21373
+ for (let i2 = 0; i2 < node.childCount; i2++) {
21374
+ const spec = node.child(i2);
21375
+ if (!spec || spec.type !== "var_spec") continue;
21376
+ const nameNode = spec.childForFieldName("name");
21377
+ let valueNode = spec.childForFieldName("value");
21378
+ if (!nameNode || !valueNode) continue;
21379
+ const names = [];
21380
+ if (nameNode.type === "identifier") {
21381
+ names.push(getNodeText2(nameNode, this.source));
21382
+ } else if (nameNode.type === "identifier_list") {
21383
+ for (const c of nameNode.children) {
21384
+ if (c.type === "identifier") names.push(getNodeText2(c, this.source));
21385
+ }
21386
+ }
21387
+ if (valueNode.type === "expression_list") {
21388
+ valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
21389
+ }
21390
+ if (names.length === 0) continue;
21391
+ const line = getNodeLine(node);
21392
+ for (const varName of names) {
21393
+ if (varName === "_") continue;
21394
+ const scopedName = this.getScopedName(varName);
21395
+ this.goScopedNames.add(scopedName);
21396
+ if (this.isTaintedExpression(valueNode)) {
21397
+ this.goUnknownVars.add(scopedName);
21398
+ this.tainted.add(scopedName);
21399
+ this.sanitizedVars.delete(scopedName);
21400
+ this.sanitizedVarUncovered.delete(scopedName);
21401
+ this.definitionNodes.set(scopedName, valueNode);
21402
+ this.symbols.set(scopedName, createUnknown(line));
21403
+ continue;
21404
+ }
21405
+ if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
21406
+ this.goUnknownVars.add(scopedName);
21407
+ this.symbols.delete(scopedName);
21408
+ this.sanitizedVars.delete(scopedName);
21409
+ this.sanitizedVarUncovered.delete(scopedName);
21410
+ if (!this.inConditionalBranch) {
21411
+ this.tainted.delete(scopedName);
21412
+ this.definitionNodes.delete(scopedName);
21413
+ }
21414
+ continue;
21415
+ }
21416
+ if (this.inConditionalBranch) {
21417
+ this.goUnknownVars.add(scopedName);
21418
+ this.symbols.delete(scopedName);
21419
+ continue;
21420
+ }
21421
+ this.tainted.delete(scopedName);
21422
+ this.definitionNodes.delete(scopedName);
21423
+ this.symbols.set(scopedName, this.evaluateExpression(valueNode));
21424
+ this.sanitizedVars.delete(scopedName);
21425
+ this.sanitizedVarUncovered.delete(scopedName);
21426
+ }
21427
+ }
21428
+ }
20900
21429
  handleGoAssignment(node) {
20901
21430
  const left = node.childForFieldName("left");
20902
21431
  const right = node.childForFieldName("right");
@@ -20983,6 +21512,12 @@ var ConstantPropagator = class _ConstantPropagator {
20983
21512
  this.sanitizedVars.delete(scopedName);
20984
21513
  this.sanitizedVarUncovered.delete(scopedName);
20985
21514
  this.definitionNodes.set(scopedName, right);
21515
+ if (this.isIteratorNextExpression(right)) {
21516
+ this.taintedFromIteratorNext.add(scopedName);
21517
+ if (!varName.includes(".")) {
21518
+ this.taintedFromIteratorNext.add(varName);
21519
+ }
21520
+ }
20986
21521
  this.symbols.set(scopedName, createUnknown(line));
20987
21522
  if (this.inConstructor && varName.startsWith("this.")) {
20988
21523
  const fieldName = varName.substring(5);
@@ -21707,6 +22242,24 @@ var ConstantPropagator = class _ConstantPropagator {
21707
22242
  cache.set(rootId, false);
21708
22243
  return false;
21709
22244
  }
22245
+ /** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
22246
+ argIsSanitizedTaint(call) {
22247
+ const argsNode = call.childForFieldName("arguments");
22248
+ if (!argsNode) return false;
22249
+ for (const arg of argsNode.children) {
22250
+ if (arg.type !== "method_invocation") continue;
22251
+ const name2 = arg.childForFieldName("name");
22252
+ if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
22253
+ const inner = arg.childForFieldName("arguments");
22254
+ if (!inner) continue;
22255
+ for (const a of inner.children) {
22256
+ if (a.type === "(" || a.type === ")" || a.type === ",") continue;
22257
+ if (this.isTaintedExpression(a)) return true;
22258
+ }
22259
+ if (this.argIsSanitizedTaint(arg)) return true;
22260
+ }
22261
+ return false;
22262
+ }
21710
22263
  isTaintedExpressionStep(node) {
21711
22264
  const text = getNodeText2(node, this.source);
21712
22265
  if (node.type === "method_invocation") {
@@ -21714,6 +22267,9 @@ var ConstantPropagator = class _ConstantPropagator {
21714
22267
  const objectNode = node.childForFieldName("object");
21715
22268
  if (nameNode) {
21716
22269
  const methodName = getNodeText2(nameNode, this.source);
22270
+ if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
22271
+ return true;
22272
+ }
21717
22273
  if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
21718
22274
  return false;
21719
22275
  }
@@ -21973,10 +22529,17 @@ var ConstantPropagator = class _ConstantPropagator {
21973
22529
  }
21974
22530
  }
21975
22531
  if (node.type === "identifier") {
21976
- const scopedName = this.currentMethod ? `${this.currentMethod}:${text}` : text;
21977
- const isTainted = this.tainted.has(scopedName) || this.tainted.has(text);
21978
- if (isTainted) {
21979
- if (this.isExcludedByCondition(scopedName) || this.isExcludedByCondition(text)) {
22532
+ let isTainted = false;
22533
+ let boundName = null;
22534
+ for (const candidate of this.getScopedNameCandidates(text)) {
22535
+ if (this.tainted.has(candidate)) {
22536
+ isTainted = true;
22537
+ boundName = candidate;
22538
+ break;
22539
+ }
22540
+ }
22541
+ if (isTainted && boundName) {
22542
+ if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
21980
22543
  return false;
21981
22544
  }
21982
22545
  }