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
@@ -4643,14 +4643,43 @@ function extractJavaTypes(tree, cache, options) {
4643
4643
  );
4644
4644
  const path = node ? enclosingTypePath(node) : [];
4645
4645
  if (path.length > 0) t.enclosing_type = path.join(".");
4646
- const params = node?.childForFieldName("type_parameters");
4647
- if (params) {
4648
- 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);
4649
- if (names.length > 0) t.type_parameters = names;
4646
+ const tp = typeParameters(node);
4647
+ if (tp.names.length > 0) t.type_parameters = tp.names;
4648
+ if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
4649
+ }
4650
+ const allMethods = /* @__PURE__ */ new Map();
4651
+ for (const t of types) {
4652
+ for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
4653
+ }
4654
+ for (const kind of ["method_declaration", "constructor_declaration"]) {
4655
+ for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
4656
+ const name2 = getIdentifier(node, "name");
4657
+ const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
4658
+ if (!info2) continue;
4659
+ const tp = typeParameters(node);
4660
+ if (tp.names.length > 0) info2.type_parameters = tp.names;
4661
+ if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
4650
4662
  }
4651
4663
  }
4652
4664
  return types;
4653
4665
  }
4666
+ function typeParameters(node) {
4667
+ const names = [];
4668
+ const bounds = {};
4669
+ const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
4670
+ if (!list) return { names, bounds };
4671
+ for (const c of list.namedChildren) {
4672
+ if (c?.type !== "type_parameter") continue;
4673
+ const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
4674
+ const name2 = id ? getNodeText(id) : "";
4675
+ if (!name2) continue;
4676
+ names.push(name2);
4677
+ const bound = c.namedChildren.find((x) => x?.type === "type_bound");
4678
+ const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
4679
+ if (first) bounds[name2] = getNodeText(first);
4680
+ }
4681
+ return { names, bounds };
4682
+ }
4654
4683
  function recordComponents(node) {
4655
4684
  const out2 = [];
4656
4685
  const params = node.childForFieldName("parameters");
@@ -10361,6 +10390,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10361
10390
  carryBranchDefsOut(popped, scopeStack, alwaysRuns);
10362
10391
  break;
10363
10392
  }
10393
+ case "try_statement":
10394
+ case "try_with_resources_statement": {
10395
+ if (isJavaScript) {
10396
+ const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
10397
+ uses.push(...stmtUses.uses);
10398
+ useId = stmtUses.nextId;
10399
+ break;
10400
+ }
10401
+ scopeStack.push(/* @__PURE__ */ new Map());
10402
+ const resources = stmt.childForFieldName("resources");
10403
+ for (const res of resources?.namedChildren ?? []) {
10404
+ if (!res || res.type !== "resource") continue;
10405
+ const nameNode = res.childForFieldName("name");
10406
+ const value = res.childForFieldName("value");
10407
+ if (value) {
10408
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
10409
+ uses.push(...valueUses.uses);
10410
+ useId = valueUses.nextId;
10411
+ }
10412
+ if (nameNode && nameNode.type === "identifier") {
10413
+ const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
10414
+ defs.push(def);
10415
+ currentScope(scopeStack).set(def.variable, def.id);
10416
+ markDeclared(currentScope(scopeStack), def.variable);
10417
+ } else if (!value) {
10418
+ const resUses = extractUses(res, useId, scopeStack, isJavaScript);
10419
+ uses.push(...resUses.uses);
10420
+ useId = resUses.nextId;
10421
+ }
10422
+ }
10423
+ for (const part of stmt.namedChildren) {
10424
+ if (!part) continue;
10425
+ if (part.type === "block") {
10426
+ const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
10427
+ defs.push(...r.defs);
10428
+ uses.push(...r.uses);
10429
+ defId = r.nextDefId;
10430
+ useId = r.nextUseId;
10431
+ } else if (part.type === "catch_clause") {
10432
+ scopeStack.push(/* @__PURE__ */ new Map());
10433
+ for (const param of part.namedChildren) {
10434
+ if (!param || param.type !== "catch_formal_parameter") continue;
10435
+ const pname = param.childForFieldName("name");
10436
+ if (pname) {
10437
+ const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
10438
+ defs.push(def);
10439
+ currentScope(scopeStack).set(def.variable, def.id);
10440
+ markDeclared(currentScope(scopeStack), def.variable);
10441
+ }
10442
+ }
10443
+ const body2 = part.childForFieldName("body");
10444
+ if (body2) {
10445
+ const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
10446
+ defs.push(...r.defs);
10447
+ uses.push(...r.uses);
10448
+ defId = r.nextDefId;
10449
+ useId = r.nextUseId;
10450
+ }
10451
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10452
+ } else if (part.type === "finally_clause") {
10453
+ for (const b of part.namedChildren) {
10454
+ if (!b || b.type !== "block") continue;
10455
+ const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
10456
+ defs.push(...r.defs);
10457
+ uses.push(...r.uses);
10458
+ defId = r.nextDefId;
10459
+ useId = r.nextUseId;
10460
+ }
10461
+ }
10462
+ }
10463
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10464
+ break;
10465
+ }
10364
10466
  case "else_clause": {
10365
10467
  for (let i2 = 0; i2 < stmt.childCount; i2++) {
10366
10468
  const arm = stmt.child(i2);
@@ -10976,6 +11078,42 @@ function buildBashDFG(tree) {
10976
11078
  defs.push(def);
10977
11079
  currentScope(scopeStack).set(p, def.id);
10978
11080
  }
11081
+ const bashFnDefs = /* @__PURE__ */ new Map();
11082
+ const bashFnByNodeId = /* @__PURE__ */ new Map();
11083
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11084
+ const nameNode = fn.childForFieldName("name");
11085
+ if (!nameNode) continue;
11086
+ const fnName = getNodeText(nameNode);
11087
+ if (!fnName || bashFnDefs.has(fnName)) continue;
11088
+ const m = /* @__PURE__ */ new Map();
11089
+ for (const p of positionalParams) {
11090
+ const def = {
11091
+ id: defIdCounter++,
11092
+ variable: p,
11093
+ line: fn.startPosition.row + 1,
11094
+ kind: "param",
11095
+ expression: `bash-fn:${fnName}`
11096
+ };
11097
+ defs.push(def);
11098
+ m.set(p, def.id);
11099
+ }
11100
+ bashFnDefs.set(fnName, m);
11101
+ bashFnByNodeId.set(fn.id, fnName);
11102
+ }
11103
+ const enclosingBashFn = (node) => {
11104
+ let cur = node.parent;
11105
+ while (cur) {
11106
+ if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
11107
+ cur = cur.parent;
11108
+ }
11109
+ return null;
11110
+ };
11111
+ const positionalDef = (varName, node, fallback) => {
11112
+ if (!positionalParams.includes(varName)) return fallback;
11113
+ const fnName = enclosingBashFn(node);
11114
+ if (!fnName) return fallback;
11115
+ return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
11116
+ };
10979
11117
  walkTree(tree.rootNode, (node) => {
10980
11118
  if (node.type === "variable_assignment") {
10981
11119
  const nameNode = node.childForFieldName("name");
@@ -11028,7 +11166,7 @@ function buildBashDFG(tree) {
11028
11166
  if (varNameNode) {
11029
11167
  const varName = getNodeText(varNameNode);
11030
11168
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
11031
- let reachingDef = findReachingDef(varName, scopeStack);
11169
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11032
11170
  if (reachingDef === null && !positionalParams.includes(varName)) {
11033
11171
  const def = {
11034
11172
  id: defIdCounter++,
@@ -11052,7 +11190,7 @@ function buildBashDFG(tree) {
11052
11190
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
11053
11191
  if (varNameNode && varNameNode.type === "variable_name") {
11054
11192
  const varName = getNodeText(varNameNode);
11055
- let reachingDef = findReachingDef(varName, scopeStack);
11193
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11056
11194
  if (reachingDef === null && !positionalParams.includes(varName)) {
11057
11195
  const def = {
11058
11196
  id: defIdCounter++,
@@ -11074,8 +11212,56 @@ function buildBashDFG(tree) {
11074
11212
  }
11075
11213
  });
11076
11214
  const chains = computeChains(defs, uses);
11215
+ chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
11077
11216
  return { defs, uses, chains };
11078
11217
  }
11218
+ function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
11219
+ const chains = [];
11220
+ if (bashFnDefs.size === 0) return chains;
11221
+ const renumbers = /* @__PURE__ */ new Set();
11222
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11223
+ const nameNode = fn.childForFieldName("name");
11224
+ if (!nameNode) continue;
11225
+ if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
11226
+ }
11227
+ const usesByLine = /* @__PURE__ */ new Map();
11228
+ for (const u of uses) {
11229
+ const l = usesByLine.get(u.line) ?? [];
11230
+ l.push(u);
11231
+ usesByLine.set(u.line, l);
11232
+ }
11233
+ for (const cmd of findNodes(tree.rootNode, "command")) {
11234
+ const nameNode = cmd.childForFieldName("name");
11235
+ if (!nameNode) continue;
11236
+ const fnDefs = bashFnDefs.get(getNodeText(nameNode));
11237
+ if (!fnDefs) continue;
11238
+ const fnName = getNodeText(nameNode);
11239
+ let position = 0;
11240
+ for (let i2 = 0; i2 < cmd.childCount; i2++) {
11241
+ const arg = cmd.child(i2);
11242
+ if (!arg || arg.id === nameNode.id) continue;
11243
+ if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
11244
+ const text = getNodeText(arg);
11245
+ if (!text.trim()) continue;
11246
+ position++;
11247
+ const names = /* @__PURE__ */ new Set();
11248
+ const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
11249
+ let m;
11250
+ while ((m = re.exec(text)) !== null) names.add(m[1]);
11251
+ if (names.size === 0) continue;
11252
+ const spreads = /\$\{?[@*]/.test(text);
11253
+ 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);
11254
+ const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
11255
+ for (const name2 of names) {
11256
+ for (const u of lineUses) {
11257
+ if (u.variable !== name2 || u.def_id === null) continue;
11258
+ for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
11259
+ }
11260
+ }
11261
+ }
11262
+ }
11263
+ return chains;
11264
+ }
11079
11265
  function buildRustDFG(tree, cache) {
11080
11266
  const defs = [];
11081
11267
  const uses = [];
@@ -11785,6 +11971,198 @@ function javaReplaceCrLfCoverage(method, rawFrom) {
11785
11971
  return { cr, lf };
11786
11972
  }
11787
11973
 
11974
+ // src/analysis/js-crlf-strip.ts
11975
+ var MAX_PATTERN_LENGTH = 200;
11976
+ var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
11977
+ "replace",
11978
+ "replaceAll",
11979
+ "trim",
11980
+ "trimStart",
11981
+ "trimEnd",
11982
+ "toString",
11983
+ "toLowerCase",
11984
+ "toUpperCase",
11985
+ "slice",
11986
+ "substring"
11987
+ ]);
11988
+ var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
11989
+ function readQuoted(s, i2) {
11990
+ const q = s[i2];
11991
+ if (q !== '"' && q !== "'" && q !== "`") return null;
11992
+ let out2 = "";
11993
+ for (let j = i2 + 1; j < s.length; j++) {
11994
+ const c = s[j];
11995
+ if (c === q) return { value: out2, end: j + 1 };
11996
+ if (q === "`" && c === "$" && s[j + 1] === "{") return null;
11997
+ if (c === "\\") {
11998
+ const n = s[j + 1];
11999
+ if (n === void 0) return null;
12000
+ switch (n) {
12001
+ case "n":
12002
+ out2 += "\n";
12003
+ j++;
12004
+ continue;
12005
+ case "r":
12006
+ out2 += "\r";
12007
+ j++;
12008
+ continue;
12009
+ case "t":
12010
+ out2 += " ";
12011
+ j++;
12012
+ continue;
12013
+ case "0":
12014
+ out2 += "\0";
12015
+ j++;
12016
+ continue;
12017
+ case "x": {
12018
+ const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
12019
+ if (!h) return null;
12020
+ out2 += String.fromCharCode(parseInt(h[0], 16));
12021
+ j += 3;
12022
+ continue;
12023
+ }
12024
+ case "u": {
12025
+ const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
12026
+ if (!h) return null;
12027
+ out2 += String.fromCharCode(parseInt(h[0], 16));
12028
+ j += 5;
12029
+ continue;
12030
+ }
12031
+ default:
12032
+ out2 += n;
12033
+ j++;
12034
+ continue;
12035
+ }
12036
+ }
12037
+ if (c === "\n" && q !== "`") return null;
12038
+ out2 += c;
12039
+ }
12040
+ return null;
12041
+ }
12042
+ function readRegexLiteral(s, i2) {
12043
+ if (s[i2] !== "/") return null;
12044
+ let inClass = false;
12045
+ for (let j = i2 + 1; j < s.length; j++) {
12046
+ const c = s[j];
12047
+ if (c === "\n") return null;
12048
+ if (c === "\\") {
12049
+ j++;
12050
+ continue;
12051
+ }
12052
+ if (inClass) {
12053
+ if (c === "]") inClass = false;
12054
+ continue;
12055
+ }
12056
+ if (c === "[") {
12057
+ inClass = true;
12058
+ continue;
12059
+ }
12060
+ if (c === "/") {
12061
+ const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
12062
+ return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
12063
+ }
12064
+ }
12065
+ return null;
12066
+ }
12067
+ var skipWs = (s, i2) => {
12068
+ while (i2 < s.length && /\s/.test(s[i2])) i2++;
12069
+ return i2;
12070
+ };
12071
+ function parseSteps(chainText) {
12072
+ const steps = [];
12073
+ const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
12074
+ let m;
12075
+ while ((m = callRe.exec(chainText)) !== null) {
12076
+ const kind = m[1];
12077
+ let i2 = skipWs(chainText, m.index + m[0].length);
12078
+ let pattern;
12079
+ const rx = readRegexLiteral(chainText, i2);
12080
+ if (rx) {
12081
+ if (rx.source.length > MAX_PATTERN_LENGTH) return null;
12082
+ try {
12083
+ pattern = new RegExp(rx.source, rx.flags);
12084
+ } catch {
12085
+ return null;
12086
+ }
12087
+ i2 = rx.end;
12088
+ } else {
12089
+ const str = readQuoted(chainText, i2);
12090
+ if (!str) return null;
12091
+ pattern = str.value;
12092
+ i2 = str.end;
12093
+ }
12094
+ i2 = skipWs(chainText, i2);
12095
+ if (chainText[i2] !== ",") return null;
12096
+ i2 = skipWs(chainText, i2 + 1);
12097
+ const rep = readQuoted(chainText, i2);
12098
+ if (!rep) return null;
12099
+ i2 = skipWs(chainText, rep.end);
12100
+ if (chainText[i2] !== ")") return null;
12101
+ steps.push({ kind, pattern, replacement: rep.value });
12102
+ callRe.lastIndex = i2 + 1;
12103
+ }
12104
+ return steps;
12105
+ }
12106
+ function run2(steps, input) {
12107
+ let s = input;
12108
+ try {
12109
+ for (const st of steps) {
12110
+ if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
12111
+ s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
12112
+ }
12113
+ } catch {
12114
+ return null;
12115
+ }
12116
+ return s;
12117
+ }
12118
+ function jsReplaceChainVerdict(chainText) {
12119
+ const steps = parseSteps(chainText);
12120
+ if (!steps || steps.length === 0) return "undecided";
12121
+ const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
12122
+ for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
12123
+ if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
12124
+ }
12125
+ const out2 = run2(steps, PROBE);
12126
+ if (out2 === null) return "undecided";
12127
+ return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
12128
+ }
12129
+ function jsChainTextAt(sourceLines, line, column) {
12130
+ const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
12131
+ let depth = 0;
12132
+ let prevSig = "";
12133
+ let i2 = 0;
12134
+ for (; i2 < text.length; i2++) {
12135
+ const c = text[i2];
12136
+ if (c === '"' || c === "'" || c === "`") {
12137
+ for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
12138
+ prevSig = c;
12139
+ continue;
12140
+ }
12141
+ if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
12142
+ const rx = readRegexLiteral(text, i2);
12143
+ if (rx) {
12144
+ i2 = rx.end - 1;
12145
+ prevSig = "/";
12146
+ continue;
12147
+ }
12148
+ }
12149
+ if (c === "(" || c === "[" || c === "{") {
12150
+ depth++;
12151
+ prevSig = c;
12152
+ continue;
12153
+ }
12154
+ if (c === ")" || c === "]" || c === "}") {
12155
+ if (depth === 0) break;
12156
+ depth--;
12157
+ prevSig = c;
12158
+ continue;
12159
+ }
12160
+ if (depth === 0 && !/[\w$.\s]/.test(c)) break;
12161
+ if (!/\s/.test(c)) prevSig = c;
12162
+ }
12163
+ return text.slice(0, i2);
12164
+ }
12165
+
11788
12166
  // src/analysis/passes/language-sources-pass.ts
11789
12167
  var JINJA_LITERAL_ARG_RE_FRAG = `(?:"[^"\\\\]*"|'[^'\\\\]*')`;
11790
12168
  var JINJA_SAFE_RTS_RE = new RegExp(
@@ -12568,6 +12946,10 @@ var DEFAULT_SOURCES = [
12568
12946
  { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12569
12947
  { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12570
12948
  { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12949
+ // Node redis clients are often bound to `cache` from `createClient()`; class
12950
+ // `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
12951
+ // filtered in sink-filter-pass (#818 src14).
12952
+ { method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12571
12953
  { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
12572
12954
  { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
12573
12955
  { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
@@ -12747,7 +13129,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
12747
13129
  // on arg[1] (the *Cookie) catches struct-literal composite tainting.
12748
13130
  { method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
12749
13131
  // gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
12750
- { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
13132
+ { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
13133
+ // --- Rust: actix-session / tower-sessions write -------------------------
13134
+ { method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
12751
13135
  ];
12752
13136
  var DEFAULT_SINKS = [
12753
13137
  // SQL Injection (CWE-89)
@@ -13408,7 +13792,9 @@ var DEFAULT_SINKS = [
13408
13792
  { method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13409
13793
  { method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13410
13794
  // Cron expression parsing (DoS/ReDoS)
13411
- { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13795
+ // cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
13796
+ // `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
13797
+ { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
13412
13798
  { method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13413
13799
  // ff4j feature flags
13414
13800
  { method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
@@ -13505,9 +13891,13 @@ var DEFAULT_SINKS = [
13505
13891
  { method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13506
13892
  { method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13507
13893
  // XXE - XML External Entity (CWE-611)
13508
- { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13509
- { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13510
- { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13894
+ // cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
13895
+ // receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
13896
+ // `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
13897
+ // libxmljs, DOMParser) have their own entries.
13898
+ { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13899
+ { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13900
+ { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13511
13901
  { method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13512
13902
  { method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13513
13903
  { method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
@@ -13762,6 +14152,46 @@ var DEFAULT_SINKS = [
13762
14152
  { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
13763
14153
  { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
13764
14154
  { method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14155
+ // DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
14156
+ // `duckdb` / `duckdb-async` bindings (Database / Connection) and
14157
+ // `react-native-duckdb` (Database / Transaction). The method names are
14158
+ // generic and the receivers come from factories (`await instance.connect()`,
14159
+ // `open({...})`), so the receiver cannot be typed: the import is the gate
14160
+ // (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
14161
+ // as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
14162
+ // `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
14163
+ // not reach it.
14164
+ { method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
14165
+ { 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"] },
14166
+ { 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"] },
14167
+ { method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14168
+ { method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14169
+ { method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14170
+ { method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14171
+ { method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14172
+ { method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14173
+ { method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14174
+ { method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14175
+ { method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14176
+ { method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14177
+ { method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14178
+ { method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14179
+ { method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14180
+ { method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14181
+ { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14182
+ { method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14183
+ { method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14184
+ { method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14185
+ { method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14186
+ // RxDB (Mango queries, any storage incl. SQLite): a selector or query
14187
+ // object, a field name passed to the query builder, or a `$regex` pattern
14188
+ // under caller control is NoSQL operator injection (CWE-943). Gated on the
14189
+ // `rxdb` import for the same reason as DuckDB above. `find` is already a
14190
+ // classless JS NoSQL sink in the JavaScript plugin.
14191
+ { method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14192
+ { method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14193
+ { method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14194
+ { method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
13765
14195
  // TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
13766
14196
  // escape hatch is already covered by the classless `query` sink in the JS
13767
14197
  // plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
@@ -13872,6 +14302,8 @@ var DEFAULT_SINKS = [
13872
14302
  { method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13873
14303
  { method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13874
14304
  { method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14305
+ // Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
14306
+ { method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
13875
14307
  // needle library (used in NodeGoat)
13876
14308
  { method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13877
14309
  { method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -14366,7 +14798,7 @@ var DEFAULT_SINKS = [
14366
14798
  // receiver-taint or operator-LHS-taint tracking — the format string is the
14367
14799
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
14368
14800
  // C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
14369
- { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
14801
+ { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
14370
14802
  { method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
14371
14803
  // Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
14372
14804
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
@@ -15435,198 +15867,6 @@ function getDefaultConfig() {
15435
15867
  };
15436
15868
  }
15437
15869
 
15438
- // src/analysis/js-crlf-strip.ts
15439
- var MAX_PATTERN_LENGTH = 200;
15440
- var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
15441
- "replace",
15442
- "replaceAll",
15443
- "trim",
15444
- "trimStart",
15445
- "trimEnd",
15446
- "toString",
15447
- "toLowerCase",
15448
- "toUpperCase",
15449
- "slice",
15450
- "substring"
15451
- ]);
15452
- var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
15453
- function readQuoted(s, i2) {
15454
- const q = s[i2];
15455
- if (q !== '"' && q !== "'" && q !== "`") return null;
15456
- let out2 = "";
15457
- for (let j = i2 + 1; j < s.length; j++) {
15458
- const c = s[j];
15459
- if (c === q) return { value: out2, end: j + 1 };
15460
- if (q === "`" && c === "$" && s[j + 1] === "{") return null;
15461
- if (c === "\\") {
15462
- const n = s[j + 1];
15463
- if (n === void 0) return null;
15464
- switch (n) {
15465
- case "n":
15466
- out2 += "\n";
15467
- j++;
15468
- continue;
15469
- case "r":
15470
- out2 += "\r";
15471
- j++;
15472
- continue;
15473
- case "t":
15474
- out2 += " ";
15475
- j++;
15476
- continue;
15477
- case "0":
15478
- out2 += "\0";
15479
- j++;
15480
- continue;
15481
- case "x": {
15482
- const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
15483
- if (!h) return null;
15484
- out2 += String.fromCharCode(parseInt(h[0], 16));
15485
- j += 3;
15486
- continue;
15487
- }
15488
- case "u": {
15489
- const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
15490
- if (!h) return null;
15491
- out2 += String.fromCharCode(parseInt(h[0], 16));
15492
- j += 5;
15493
- continue;
15494
- }
15495
- default:
15496
- out2 += n;
15497
- j++;
15498
- continue;
15499
- }
15500
- }
15501
- if (c === "\n" && q !== "`") return null;
15502
- out2 += c;
15503
- }
15504
- return null;
15505
- }
15506
- function readRegexLiteral(s, i2) {
15507
- if (s[i2] !== "/") return null;
15508
- let inClass = false;
15509
- for (let j = i2 + 1; j < s.length; j++) {
15510
- const c = s[j];
15511
- if (c === "\n") return null;
15512
- if (c === "\\") {
15513
- j++;
15514
- continue;
15515
- }
15516
- if (inClass) {
15517
- if (c === "]") inClass = false;
15518
- continue;
15519
- }
15520
- if (c === "[") {
15521
- inClass = true;
15522
- continue;
15523
- }
15524
- if (c === "/") {
15525
- const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
15526
- return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
15527
- }
15528
- }
15529
- return null;
15530
- }
15531
- var skipWs = (s, i2) => {
15532
- while (i2 < s.length && /\s/.test(s[i2])) i2++;
15533
- return i2;
15534
- };
15535
- function parseSteps(chainText) {
15536
- const steps = [];
15537
- const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
15538
- let m;
15539
- while ((m = callRe.exec(chainText)) !== null) {
15540
- const kind = m[1];
15541
- let i2 = skipWs(chainText, m.index + m[0].length);
15542
- let pattern;
15543
- const rx = readRegexLiteral(chainText, i2);
15544
- if (rx) {
15545
- if (rx.source.length > MAX_PATTERN_LENGTH) return null;
15546
- try {
15547
- pattern = new RegExp(rx.source, rx.flags);
15548
- } catch {
15549
- return null;
15550
- }
15551
- i2 = rx.end;
15552
- } else {
15553
- const str = readQuoted(chainText, i2);
15554
- if (!str) return null;
15555
- pattern = str.value;
15556
- i2 = str.end;
15557
- }
15558
- i2 = skipWs(chainText, i2);
15559
- if (chainText[i2] !== ",") return null;
15560
- i2 = skipWs(chainText, i2 + 1);
15561
- const rep = readQuoted(chainText, i2);
15562
- if (!rep) return null;
15563
- i2 = skipWs(chainText, rep.end);
15564
- if (chainText[i2] !== ")") return null;
15565
- steps.push({ kind, pattern, replacement: rep.value });
15566
- callRe.lastIndex = i2 + 1;
15567
- }
15568
- return steps;
15569
- }
15570
- function run2(steps, input) {
15571
- let s = input;
15572
- try {
15573
- for (const st of steps) {
15574
- if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
15575
- s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
15576
- }
15577
- } catch {
15578
- return null;
15579
- }
15580
- return s;
15581
- }
15582
- function jsReplaceChainVerdict(chainText) {
15583
- const steps = parseSteps(chainText);
15584
- if (!steps || steps.length === 0) return "undecided";
15585
- const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
15586
- for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
15587
- if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
15588
- }
15589
- const out2 = run2(steps, PROBE);
15590
- if (out2 === null) return "undecided";
15591
- return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
15592
- }
15593
- function jsChainTextAt(sourceLines, line, column) {
15594
- const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
15595
- let depth = 0;
15596
- let prevSig = "";
15597
- let i2 = 0;
15598
- for (; i2 < text.length; i2++) {
15599
- const c = text[i2];
15600
- if (c === '"' || c === "'" || c === "`") {
15601
- for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
15602
- prevSig = c;
15603
- continue;
15604
- }
15605
- if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
15606
- const rx = readRegexLiteral(text, i2);
15607
- if (rx) {
15608
- i2 = rx.end - 1;
15609
- prevSig = "/";
15610
- continue;
15611
- }
15612
- }
15613
- if (c === "(" || c === "[" || c === "{") {
15614
- depth++;
15615
- prevSig = c;
15616
- continue;
15617
- }
15618
- if (c === ")" || c === "]" || c === "}") {
15619
- if (depth === 0) break;
15620
- depth--;
15621
- prevSig = c;
15622
- continue;
15623
- }
15624
- if (depth === 0 && !/[\w$.\s]/.test(c)) break;
15625
- if (!/\s/.test(c)) prevSig = c;
15626
- }
15627
- return text.slice(0, i2);
15628
- }
15629
-
15630
15870
  // src/analysis/constant-propagation/patterns.ts
15631
15871
  var TAINT_PATTERNS = [
15632
15872
  // HTTP Sources (Servlet API)
@@ -16016,13 +16256,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
16016
16256
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
16017
16257
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
16018
16258
  const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
16019
- let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
16259
+ let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
16260
+ sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
16020
16261
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
16021
16262
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
16022
16263
  const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
16023
16264
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
16024
16265
  return { sources, sinks: gatedSinks, sanitizers };
16025
16266
  }
16267
+ function isBoundParameterCall(call, pattern, sourceLines) {
16268
+ if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
16269
+ const first = call.arguments[0]?.expression?.trim() ?? "";
16270
+ if (first.startsWith("[") || first.startsWith("{")) return true;
16271
+ const receiver = call.receiver;
16272
+ if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
16273
+ const assignedFromPrepare = new RegExp(
16274
+ `\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
16275
+ );
16276
+ return sourceLines.some((line) => assignedFromPrepare.test(line));
16277
+ }
16278
+ function sinkPatternImportApplies(pattern, imports) {
16279
+ const required = pattern.requires_import;
16280
+ if (!required || required.length === 0) return true;
16281
+ if (!imports || imports.length === 0) return false;
16282
+ return imports.some((imp) => {
16283
+ const from = imp.from_package;
16284
+ if (!from) return false;
16285
+ return required.some((m) => from === m || from.startsWith(m + "/"));
16286
+ });
16287
+ }
16026
16288
  function sinkPatternAppliesTo(pattern, language) {
16027
16289
  if (language === void 0) return true;
16028
16290
  if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
@@ -16424,6 +16686,9 @@ function isInterproceduralTaintableType(typeName, language) {
16424
16686
  "IEnumerable",
16425
16687
  "ICollection",
16426
16688
  "IList",
16689
+ "List",
16690
+ "Hashtable",
16691
+ "ArrayList",
16427
16692
  "Dictionary",
16428
16693
  "IDictionary",
16429
16694
  "IReadOnlyList",
@@ -16437,6 +16702,46 @@ function isInterproceduralTaintableType(typeName, language) {
16437
16702
  const leaf = baseType.split(".").pop() ?? baseType;
16438
16703
  if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
16439
16704
  }
16705
+ if (language === "rust") {
16706
+ const trimmed = typeName.trim();
16707
+ const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
16708
+ const rustBase = unref.split("<")[0].trim();
16709
+ const rustScalars = [
16710
+ "String",
16711
+ "str",
16712
+ "bool",
16713
+ "char",
16714
+ "i8",
16715
+ "i16",
16716
+ "i32",
16717
+ "i64",
16718
+ "i128",
16719
+ "isize",
16720
+ "u8",
16721
+ "u16",
16722
+ "u32",
16723
+ "u64",
16724
+ "u128",
16725
+ "usize",
16726
+ "f32",
16727
+ "f64"
16728
+ ];
16729
+ if (rustScalars.includes(rustBase)) return true;
16730
+ if (rustBase === "()" || rustBase === "!") return false;
16731
+ if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
16732
+ return true;
16733
+ }
16734
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
16735
+ }
16736
+ if (language === "javascript" || language === "typescript") {
16737
+ const trimmed = typeName.trim();
16738
+ const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
16739
+ if (jsScalars.includes(baseType)) return true;
16740
+ if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
16741
+ if (/\bRecord\s*</.test(trimmed)) return true;
16742
+ if (["void", "undefined", "null", "never"].includes(baseType)) return false;
16743
+ if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
16744
+ }
16440
16745
  if (typeName.endsWith("[]")) {
16441
16746
  const elementType = typeName.slice(0, -2);
16442
16747
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -16551,6 +16856,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
16551
16856
  return false;
16552
16857
  }
16553
16858
  }
16859
+ if (program === "git") {
16860
+ const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
16861
+ const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
16862
+ let subcommand;
16863
+ if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
16864
+ subcommand = String(subcommandArg.literal);
16865
+ } else {
16866
+ const expr = (subcommandArg?.expression ?? "").trim();
16867
+ if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
16868
+ subcommand = expr.slice(1, -1);
16869
+ }
16870
+ }
16871
+ if (subcommand === "ls-remote") {
16872
+ const hasEndOfOptions = call.arguments.some((a) => {
16873
+ if (a.literal !== null && a.literal !== void 0) {
16874
+ return String(a.literal) === "--";
16875
+ }
16876
+ const expr = (a.expression ?? "").trim();
16877
+ return expr === '"--"' || expr === "'--'" || expr === "`--`";
16878
+ });
16879
+ if (!hasEndOfOptions) {
16880
+ return false;
16881
+ }
16882
+ }
16883
+ }
16554
16884
  return true;
16555
16885
  }
16556
16886
  function isSafeJSChildProcessCall(call, pattern, language) {
@@ -16954,6 +17284,29 @@ function isRegexReceiver(receiver, sourceLines) {
16954
17284
  const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
16955
17285
  return sourceLines.some((l) => decl.test(l));
16956
17286
  }
17287
+ function isNavigationReceiverName(receiver) {
17288
+ const r = (receiver ?? "").trim();
17289
+ if (!r) return false;
17290
+ const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
17291
+ return /router|history|navigat|^nav$/i.test(last);
17292
+ }
17293
+ function isInMemoryMapGetReceiver(receiver, sourceLines) {
17294
+ const raw = (receiver ?? "").trim();
17295
+ if (!raw || !sourceLines) return false;
17296
+ if (/\.properties$/i.test(raw)) return true;
17297
+ const lastDot = raw.lastIndexOf(".");
17298
+ const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
17299
+ if (/^(properties|memoryCache)$/i.test(r)) return true;
17300
+ if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
17301
+ const n = escapeRe(r);
17302
+ const mapCtor = new RegExp(
17303
+ `\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
17304
+ );
17305
+ const mapType = new RegExp(
17306
+ `\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
17307
+ );
17308
+ return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
17309
+ }
16957
17310
  function isDeclaredFunctionReference(arg, sourceLines) {
16958
17311
  if (arg.literal !== null && arg.literal !== void 0) return false;
16959
17312
  if (!sourceLines) return false;
@@ -17158,6 +17511,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17158
17511
  if (isParameterizedQueryCall(call, pattern)) {
17159
17512
  continue;
17160
17513
  }
17514
+ if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
17515
+ continue;
17516
+ }
17517
+ if (isBoundParameterCall(call, pattern, sourceLines)) {
17518
+ continue;
17519
+ }
17161
17520
  if (isSafePythonSubprocessCall(call, pattern, language)) {
17162
17521
  continue;
17163
17522
  }
@@ -17215,9 +17574,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17215
17574
  if (isArgumentlessJsResponseWriter(call, pattern, language)) {
17216
17575
  continue;
17217
17576
  }
17577
+ 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)) {
17578
+ continue;
17579
+ }
17218
17580
  if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
17219
17581
  continue;
17220
17582
  }
17583
+ if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
17584
+ continue;
17585
+ }
17221
17586
  if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
17222
17587
  continue;
17223
17588
  }
@@ -17234,7 +17599,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17234
17599
  continue;
17235
17600
  }
17236
17601
  const location = formatCallLocation(call);
17237
- const key = `${location}:${call.location.line}:${pattern.cwe}`;
17602
+ const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
17238
17603
  const confidence = calculateSinkConfidence(call, pattern);
17239
17604
  const existing = sinkMap.get(key);
17240
17605
  if (!existing || confidence > existing.confidence) {
@@ -17245,6 +17610,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17245
17610
  cwe: pattern.cwe,
17246
17611
  location,
17247
17612
  line: call.location.line,
17613
+ callColumn: call.location.column,
17248
17614
  confidence,
17249
17615
  method: call.method_name,
17250
17616
  argPositions: pattern.arg_positions,
@@ -17282,6 +17648,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
17282
17648
  return false;
17283
17649
  }
17284
17650
  }
17651
+ if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
17652
+ const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
17653
+ if (expressRequestAccessors.has(pattern.method)) {
17654
+ const recv = call.receiver?.toLowerCase() ?? "";
17655
+ if (recv !== "req" && recv !== "request" && recv !== "ctx") {
17656
+ return false;
17657
+ }
17658
+ }
17659
+ }
17285
17660
  return pattern.return_tainted === true;
17286
17661
  }
17287
17662
  return false;
@@ -17375,6 +17750,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
17375
17750
  "database",
17376
17751
  "sqlite",
17377
17752
  "conn",
17753
+ "con",
17378
17754
  "connection",
17379
17755
  "client",
17380
17756
  "pool",
@@ -17651,7 +18027,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
17651
18027
  }
17652
18028
  }
17653
18029
  }
17654
- if (!pattern.class && call.receiver) {
18030
+ if (!pattern.class && call.receiver && !pattern.requires_import) {
17655
18031
  if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
17656
18032
  return false;
17657
18033
  }
@@ -18384,6 +18760,8 @@ function javaReplaceChainCredit(chainText) {
18384
18760
  let lf = false;
18385
18761
  let lt = false;
18386
18762
  let gt = false;
18763
+ let openBrace = false;
18764
+ let closeBrace = false;
18387
18765
  for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
18388
18766
  const kind = m[1];
18389
18767
  const rawFrom = m[2] ?? m[3] ?? "";
@@ -18394,10 +18772,13 @@ function javaReplaceChainCredit(chainText) {
18394
18772
  if (cov.lf) lf = true;
18395
18773
  if (from === "<" && to.startsWith("&")) lt = true;
18396
18774
  if (from === ">" && to.startsWith("&")) gt = true;
18775
+ if (from === "{") openBrace = true;
18776
+ if (from === "}") closeBrace = true;
18397
18777
  }
18398
18778
  const out2 = [];
18399
18779
  if (cr && lf) out2.push("log_injection");
18400
18780
  if (lt && gt) out2.push("xss");
18781
+ if (openBrace && closeBrace) out2.push("code_injection");
18401
18782
  return out2;
18402
18783
  }
18403
18784
  function matchesSanitizerPattern(call, pattern) {
@@ -19727,6 +20108,11 @@ var ConstantPropagator = class _ConstantPropagator {
19727
20108
  taintedArrayElements = /* @__PURE__ */ new Map();
19728
20109
  // Track current method name for scoping local variables
19729
20110
  currentMethod = null;
20111
+ /** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
20112
+ blockScopeStack = [];
20113
+ lineBlockScopes = /* @__PURE__ */ new Map();
20114
+ /** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
20115
+ skipMethodBodyBlockScope = false;
19730
20116
  // Track conditional taints: which variables were tainted under which conditions
19731
20117
  // Maps condition expression string → set of variables tainted under that condition
19732
20118
  conditionalTaints = /* @__PURE__ */ new Map();
@@ -19740,6 +20126,8 @@ var ConstantPropagator = class _ConstantPropagator {
19740
20126
  inSynchronizedBlock = false;
19741
20127
  // Track iterator sources: iterator variable name → collection name it was created from
19742
20128
  iteratorSources = /* @__PURE__ */ new Map();
20129
+ /** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
20130
+ taintedFromIteratorNext = /* @__PURE__ */ new Set();
19743
20131
  // Track class field names (declared at class level, not local variables)
19744
20132
  classFields = /* @__PURE__ */ new Set();
19745
20133
  // Track tainted method parameters for inter-procedural analysis
@@ -19797,12 +20185,16 @@ var ConstantPropagator = class _ConstantPropagator {
19797
20185
  this.sanitizedVars.clear();
19798
20186
  this.sanitizedVarUncovered.clear();
19799
20187
  this.currentMethod = null;
20188
+ this.blockScopeStack = [];
20189
+ this.lineBlockScopes.clear();
20190
+ this.skipMethodBodyBlockScope = false;
19800
20191
  this.conditionalTaints.clear();
19801
20192
  this.conditionStack = [];
19802
20193
  this.lineConditions.clear();
19803
20194
  this.synchronizedLines.clear();
19804
20195
  this.inSynchronizedBlock = false;
19805
20196
  this.iteratorSources.clear();
20197
+ this.taintedFromIteratorNext.clear();
19806
20198
  this.classFields.clear();
19807
20199
  this.instanceFieldTaint.clear();
19808
20200
  this.currentClassName = null;
@@ -19827,6 +20219,9 @@ var ConstantPropagator = class _ConstantPropagator {
19827
20219
  const resultSymbols = new Map(this.symbols);
19828
20220
  for (const name2 of this.tainted) {
19829
20221
  if (name2.includes(":")) {
20222
+ if (/^[^:]+:b\d+:/.test(name2)) {
20223
+ continue;
20224
+ }
19830
20225
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19831
20226
  resultTainted.add(unscoped);
19832
20227
  }
@@ -19841,6 +20236,9 @@ var ConstantPropagator = class _ConstantPropagator {
19841
20236
  for (const [name2, value] of this.symbols) {
19842
20237
  if (this.goScopedNames.has(name2)) continue;
19843
20238
  if (name2.includes(":")) {
20239
+ if (/^[^:]+:b\d+:/.test(name2)) {
20240
+ continue;
20241
+ }
19844
20242
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19845
20243
  if (!resultSymbols.has(unscoped)) {
19846
20244
  resultSymbols.set(unscoped, value);
@@ -19850,6 +20248,7 @@ var ConstantPropagator = class _ConstantPropagator {
19850
20248
  return {
19851
20249
  symbols: resultSymbols,
19852
20250
  tainted: resultTainted,
20251
+ lineBlockScopes: new Map(this.lineBlockScopes),
19853
20252
  unreachableLines: new Set(this.unreachableLines),
19854
20253
  taintedCollections: new Map(this.taintedCollections),
19855
20254
  taintedArrayElements: new Map(this.taintedArrayElements),
@@ -20393,6 +20792,18 @@ var ConstantPropagator = class _ConstantPropagator {
20393
20792
  // ===========================================================================
20394
20793
  // Taint Refinement
20395
20794
  // ===========================================================================
20795
+ /** True when `node` is `iter.next()` (possibly under a cast). */
20796
+ isIteratorNextExpression(node) {
20797
+ const text = getNodeText2(node, this.source);
20798
+ if (/\.\s*next\s*\(/.test(text)) return true;
20799
+ let current = node;
20800
+ if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
20801
+ current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
20802
+ }
20803
+ if (!current || current.type !== "method_invocation") return false;
20804
+ const nameNode = current.childForFieldName("name");
20805
+ return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
20806
+ }
20396
20807
  refineTaintFromConstants() {
20397
20808
  let changed = true;
20398
20809
  let iterations = 0;
@@ -20402,6 +20813,9 @@ var ConstantPropagator = class _ConstantPropagator {
20402
20813
  iterations++;
20403
20814
  const toRemove = [];
20404
20815
  for (const varName of this.tainted) {
20816
+ if (this.taintedFromIteratorNext.has(varName)) {
20817
+ continue;
20818
+ }
20405
20819
  const symbol = this.symbols.get(varName);
20406
20820
  if (symbol && symbol.type !== "unknown") {
20407
20821
  toRemove.push(varName);
@@ -20450,6 +20864,9 @@ var ConstantPropagator = class _ConstantPropagator {
20450
20864
  if (this.unreachableLines.has(line)) {
20451
20865
  return true;
20452
20866
  }
20867
+ if (this.blockScopeStack.length > 0) {
20868
+ this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
20869
+ }
20453
20870
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
20454
20871
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
20455
20872
  }
@@ -20475,12 +20892,24 @@ var ConstantPropagator = class _ConstantPropagator {
20475
20892
  case "assignment_statement":
20476
20893
  this.handleGoAssignment(node);
20477
20894
  return false;
20895
+ case "var_declaration":
20896
+ this.handleGoVarDeclaration(node);
20897
+ return false;
20478
20898
  case "update_expression":
20479
20899
  this.handleUpdateExpression(node);
20480
20900
  return false;
20481
20901
  case "if_statement":
20482
20902
  this.handleIfStatement(node);
20483
20903
  return true;
20904
+ case "block": {
20905
+ if (this.skipMethodBodyBlockScope) {
20906
+ this.skipMethodBodyBlockScope = false;
20907
+ }
20908
+ for (const child of node.children) {
20909
+ this.visit(child);
20910
+ }
20911
+ return true;
20912
+ }
20484
20913
  case "switch_expression":
20485
20914
  case "switch_statement":
20486
20915
  this.handleSwitch(node);
@@ -20566,7 +20995,9 @@ var ConstantPropagator = class _ConstantPropagator {
20566
20995
  }
20567
20996
  const body2 = node.childForFieldName("body");
20568
20997
  if (body2) {
20998
+ this.skipMethodBodyBlockScope = true;
20569
20999
  this.visit(body2);
21000
+ this.skipMethodBodyBlockScope = false;
20570
21001
  }
20571
21002
  this.currentMethod = prevMethod;
20572
21003
  this.inConstructor = prevInConstructor;
@@ -20580,10 +21011,31 @@ var ConstantPropagator = class _ConstantPropagator {
20580
21011
  if (varName.includes(".") || varName.includes(":")) {
20581
21012
  return varName;
20582
21013
  }
21014
+ const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
20583
21015
  if (this.currentMethod) {
20584
- return `${this.currentMethod}:${varName}`;
21016
+ return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
20585
21017
  }
20586
- return varName;
21018
+ return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
21019
+ }
21020
+ /** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
21021
+ getScopedNameCandidates(varName) {
21022
+ if (varName.includes(".") || varName.includes(":")) {
21023
+ return [varName];
21024
+ }
21025
+ const out2 = [];
21026
+ for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
21027
+ const blockId = this.blockScopeStack[i2];
21028
+ if (this.currentMethod) {
21029
+ out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
21030
+ } else {
21031
+ out2.push(`b${blockId}:${varName}`);
21032
+ }
21033
+ }
21034
+ if (this.currentMethod) {
21035
+ out2.push(`${this.currentMethod}:${varName}`);
21036
+ }
21037
+ out2.push(varName);
21038
+ return out2;
20587
21039
  }
20588
21040
  /**
20589
21041
  * Look up a variable value, checking both scoped and unscoped names.
@@ -20591,11 +21043,12 @@ var ConstantPropagator = class _ConstantPropagator {
20591
21043
  * either local (scoped) or global (unscoped, like class fields).
20592
21044
  */
20593
21045
  lookupSymbol(varName) {
20594
- if (this.currentMethod && !varName.includes(".") && !varName.includes(":")) {
20595
- const scopedName = `${this.currentMethod}:${varName}`;
20596
- const scopedValue = this.symbols.get(scopedName);
20597
- if (scopedValue) {
20598
- return scopedValue;
21046
+ if (!varName.includes(".") && !varName.includes(":")) {
21047
+ for (const scopedName of this.getScopedNameCandidates(varName)) {
21048
+ const scopedValue = this.symbols.get(scopedName);
21049
+ if (scopedValue) {
21050
+ return scopedValue;
21051
+ }
20599
21052
  }
20600
21053
  }
20601
21054
  return this.symbols.get(varName);
@@ -20647,8 +21100,22 @@ var ConstantPropagator = class _ConstantPropagator {
20647
21100
  this.trackIteratorsInNode(initNode);
20648
21101
  }
20649
21102
  }
20650
- for (const child of node.children) {
20651
- this.visit(child);
21103
+ if (node.type === "while_statement" || node.type === "do_statement") {
21104
+ const body2 = node.childForFieldName("body");
21105
+ for (const child of node.children) {
21106
+ if (child !== body2) this.visit(child);
21107
+ }
21108
+ if (body2?.type === "block") {
21109
+ this.blockScopeStack.push(getNodeLine(body2));
21110
+ this.visit(body2);
21111
+ this.blockScopeStack.pop();
21112
+ } else if (body2) {
21113
+ this.visit(body2);
21114
+ }
21115
+ } else {
21116
+ for (const child of node.children) {
21117
+ this.visit(child);
21118
+ }
20652
21119
  }
20653
21120
  for (const varName of loopVarNames) {
20654
21121
  this.symbols.set(varName, createUnknown(getNodeLine(node)));
@@ -20790,6 +21257,10 @@ var ConstantPropagator = class _ConstantPropagator {
20790
21257
  this.sanitizedVars.delete(scopedName);
20791
21258
  this.sanitizedVarUncovered.delete(scopedName);
20792
21259
  this.definitionNodes.set(scopedName, valueNode);
21260
+ if (this.isIteratorNextExpression(valueNode)) {
21261
+ this.taintedFromIteratorNext.add(scopedName);
21262
+ this.taintedFromIteratorNext.add(varName);
21263
+ }
20793
21264
  this.symbols.set(scopedName, createUnknown(line));
20794
21265
  } else {
20795
21266
  if (this.inConditionalBranch) {
@@ -20831,6 +21302,64 @@ var ConstantPropagator = class _ConstantPropagator {
20831
21302
  * helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
20832
21303
  * assignments got before this handler existed: the DFG decides.
20833
21304
  */
21305
+ /** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
21306
+ handleGoVarDeclaration(node) {
21307
+ for (let i2 = 0; i2 < node.childCount; i2++) {
21308
+ const spec = node.child(i2);
21309
+ if (!spec || spec.type !== "var_spec") continue;
21310
+ const nameNode = spec.childForFieldName("name");
21311
+ let valueNode = spec.childForFieldName("value");
21312
+ if (!nameNode || !valueNode) continue;
21313
+ const names = [];
21314
+ if (nameNode.type === "identifier") {
21315
+ names.push(getNodeText2(nameNode, this.source));
21316
+ } else if (nameNode.type === "identifier_list") {
21317
+ for (const c of nameNode.children) {
21318
+ if (c.type === "identifier") names.push(getNodeText2(c, this.source));
21319
+ }
21320
+ }
21321
+ if (valueNode.type === "expression_list") {
21322
+ valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
21323
+ }
21324
+ if (names.length === 0) continue;
21325
+ const line = getNodeLine(node);
21326
+ for (const varName of names) {
21327
+ if (varName === "_") continue;
21328
+ const scopedName = this.getScopedName(varName);
21329
+ this.goScopedNames.add(scopedName);
21330
+ if (this.isTaintedExpression(valueNode)) {
21331
+ this.goUnknownVars.add(scopedName);
21332
+ this.tainted.add(scopedName);
21333
+ this.sanitizedVars.delete(scopedName);
21334
+ this.sanitizedVarUncovered.delete(scopedName);
21335
+ this.definitionNodes.set(scopedName, valueNode);
21336
+ this.symbols.set(scopedName, createUnknown(line));
21337
+ continue;
21338
+ }
21339
+ if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
21340
+ this.goUnknownVars.add(scopedName);
21341
+ this.symbols.delete(scopedName);
21342
+ this.sanitizedVars.delete(scopedName);
21343
+ this.sanitizedVarUncovered.delete(scopedName);
21344
+ if (!this.inConditionalBranch) {
21345
+ this.tainted.delete(scopedName);
21346
+ this.definitionNodes.delete(scopedName);
21347
+ }
21348
+ continue;
21349
+ }
21350
+ if (this.inConditionalBranch) {
21351
+ this.goUnknownVars.add(scopedName);
21352
+ this.symbols.delete(scopedName);
21353
+ continue;
21354
+ }
21355
+ this.tainted.delete(scopedName);
21356
+ this.definitionNodes.delete(scopedName);
21357
+ this.symbols.set(scopedName, this.evaluateExpression(valueNode));
21358
+ this.sanitizedVars.delete(scopedName);
21359
+ this.sanitizedVarUncovered.delete(scopedName);
21360
+ }
21361
+ }
21362
+ }
20834
21363
  handleGoAssignment(node) {
20835
21364
  const left = node.childForFieldName("left");
20836
21365
  const right = node.childForFieldName("right");
@@ -20917,6 +21446,12 @@ var ConstantPropagator = class _ConstantPropagator {
20917
21446
  this.sanitizedVars.delete(scopedName);
20918
21447
  this.sanitizedVarUncovered.delete(scopedName);
20919
21448
  this.definitionNodes.set(scopedName, right);
21449
+ if (this.isIteratorNextExpression(right)) {
21450
+ this.taintedFromIteratorNext.add(scopedName);
21451
+ if (!varName.includes(".")) {
21452
+ this.taintedFromIteratorNext.add(varName);
21453
+ }
21454
+ }
20920
21455
  this.symbols.set(scopedName, createUnknown(line));
20921
21456
  if (this.inConstructor && varName.startsWith("this.")) {
20922
21457
  const fieldName = varName.substring(5);
@@ -21641,6 +22176,24 @@ var ConstantPropagator = class _ConstantPropagator {
21641
22176
  cache.set(rootId, false);
21642
22177
  return false;
21643
22178
  }
22179
+ /** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
22180
+ argIsSanitizedTaint(call) {
22181
+ const argsNode = call.childForFieldName("arguments");
22182
+ if (!argsNode) return false;
22183
+ for (const arg of argsNode.children) {
22184
+ if (arg.type !== "method_invocation") continue;
22185
+ const name2 = arg.childForFieldName("name");
22186
+ if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
22187
+ const inner = arg.childForFieldName("arguments");
22188
+ if (!inner) continue;
22189
+ for (const a of inner.children) {
22190
+ if (a.type === "(" || a.type === ")" || a.type === ",") continue;
22191
+ if (this.isTaintedExpression(a)) return true;
22192
+ }
22193
+ if (this.argIsSanitizedTaint(arg)) return true;
22194
+ }
22195
+ return false;
22196
+ }
21644
22197
  isTaintedExpressionStep(node) {
21645
22198
  const text = getNodeText2(node, this.source);
21646
22199
  if (node.type === "method_invocation") {
@@ -21648,6 +22201,9 @@ var ConstantPropagator = class _ConstantPropagator {
21648
22201
  const objectNode = node.childForFieldName("object");
21649
22202
  if (nameNode) {
21650
22203
  const methodName = getNodeText2(nameNode, this.source);
22204
+ if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
22205
+ return true;
22206
+ }
21651
22207
  if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
21652
22208
  return false;
21653
22209
  }
@@ -21907,10 +22463,17 @@ var ConstantPropagator = class _ConstantPropagator {
21907
22463
  }
21908
22464
  }
21909
22465
  if (node.type === "identifier") {
21910
- const scopedName = this.currentMethod ? `${this.currentMethod}:${text}` : text;
21911
- const isTainted = this.tainted.has(scopedName) || this.tainted.has(text);
21912
- if (isTainted) {
21913
- if (this.isExcludedByCondition(scopedName) || this.isExcludedByCondition(text)) {
22466
+ let isTainted = false;
22467
+ let boundName = null;
22468
+ for (const candidate of this.getScopedNameCandidates(text)) {
22469
+ if (this.tainted.has(candidate)) {
22470
+ isTainted = true;
22471
+ boundName = candidate;
22472
+ break;
22473
+ }
22474
+ }
22475
+ if (isTainted && boundName) {
22476
+ if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
21914
22477
  return false;
21915
22478
  }
21916
22479
  }