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
@@ -1405,11 +1405,11 @@ var Language = class _Language {
1405
1405
  /**
1406
1406
  * Get the node type id for a node type name.
1407
1407
  */
1408
- idForNodeType(type, named) {
1408
+ idForNodeType(type, named2) {
1409
1409
  const typeLength = C.lengthBytesUTF8(type);
1410
1410
  const typeAddress = C._malloc(typeLength + 1);
1411
1411
  C.stringToUTF8(type, typeAddress, typeLength + 1);
1412
- const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named ? 1 : 0);
1412
+ const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named2 ? 1 : 0);
1413
1413
  C._free(typeAddress);
1414
1414
  return result || null;
1415
1415
  }
@@ -4663,14 +4663,43 @@ function extractJavaTypes(tree, cache, options) {
4663
4663
  );
4664
4664
  const path = node ? enclosingTypePath(node) : [];
4665
4665
  if (path.length > 0) t.enclosing_type = path.join(".");
4666
- const params = node?.childForFieldName("type_parameters");
4667
- if (params) {
4668
- 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);
4669
- if (names.length > 0) t.type_parameters = names;
4666
+ const tp = typeParameters(node);
4667
+ if (tp.names.length > 0) t.type_parameters = tp.names;
4668
+ if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
4669
+ }
4670
+ const allMethods = /* @__PURE__ */ new Map();
4671
+ for (const t of types) {
4672
+ for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
4673
+ }
4674
+ for (const kind of ["method_declaration", "constructor_declaration"]) {
4675
+ for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
4676
+ const name2 = getIdentifier(node, "name");
4677
+ const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
4678
+ if (!info2) continue;
4679
+ const tp = typeParameters(node);
4680
+ if (tp.names.length > 0) info2.type_parameters = tp.names;
4681
+ if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
4670
4682
  }
4671
4683
  }
4672
4684
  return types;
4673
4685
  }
4686
+ function typeParameters(node) {
4687
+ const names = [];
4688
+ const bounds = {};
4689
+ const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
4690
+ if (!list) return { names, bounds };
4691
+ for (const c of list.namedChildren) {
4692
+ if (c?.type !== "type_parameter") continue;
4693
+ const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
4694
+ const name2 = id ? getNodeText(id) : "";
4695
+ if (!name2) continue;
4696
+ names.push(name2);
4697
+ const bound = c.namedChildren.find((x) => x?.type === "type_bound");
4698
+ const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
4699
+ if (first) bounds[name2] = getNodeText(first);
4700
+ }
4701
+ return { names, bounds };
4702
+ }
4674
4703
  function recordComponents(node) {
4675
4704
  const out2 = [];
4676
4705
  const params = node.childForFieldName("parameters");
@@ -10381,6 +10410,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10381
10410
  carryBranchDefsOut(popped, scopeStack, alwaysRuns);
10382
10411
  break;
10383
10412
  }
10413
+ case "try_statement":
10414
+ case "try_with_resources_statement": {
10415
+ if (isJavaScript) {
10416
+ const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
10417
+ uses.push(...stmtUses.uses);
10418
+ useId = stmtUses.nextId;
10419
+ break;
10420
+ }
10421
+ scopeStack.push(/* @__PURE__ */ new Map());
10422
+ const resources = stmt.childForFieldName("resources");
10423
+ for (const res of resources?.namedChildren ?? []) {
10424
+ if (!res || res.type !== "resource") continue;
10425
+ const nameNode = res.childForFieldName("name");
10426
+ const value = res.childForFieldName("value");
10427
+ if (value) {
10428
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
10429
+ uses.push(...valueUses.uses);
10430
+ useId = valueUses.nextId;
10431
+ }
10432
+ if (nameNode && nameNode.type === "identifier") {
10433
+ const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
10434
+ defs.push(def);
10435
+ currentScope(scopeStack).set(def.variable, def.id);
10436
+ markDeclared(currentScope(scopeStack), def.variable);
10437
+ } else if (!value) {
10438
+ const resUses = extractUses(res, useId, scopeStack, isJavaScript);
10439
+ uses.push(...resUses.uses);
10440
+ useId = resUses.nextId;
10441
+ }
10442
+ }
10443
+ for (const part of stmt.namedChildren) {
10444
+ if (!part) continue;
10445
+ if (part.type === "block") {
10446
+ const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
10447
+ defs.push(...r.defs);
10448
+ uses.push(...r.uses);
10449
+ defId = r.nextDefId;
10450
+ useId = r.nextUseId;
10451
+ } else if (part.type === "catch_clause") {
10452
+ scopeStack.push(/* @__PURE__ */ new Map());
10453
+ for (const param of part.namedChildren) {
10454
+ if (!param || param.type !== "catch_formal_parameter") continue;
10455
+ const pname = param.childForFieldName("name");
10456
+ if (pname) {
10457
+ const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
10458
+ defs.push(def);
10459
+ currentScope(scopeStack).set(def.variable, def.id);
10460
+ markDeclared(currentScope(scopeStack), def.variable);
10461
+ }
10462
+ }
10463
+ const body2 = part.childForFieldName("body");
10464
+ if (body2) {
10465
+ const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
10466
+ defs.push(...r.defs);
10467
+ uses.push(...r.uses);
10468
+ defId = r.nextDefId;
10469
+ useId = r.nextUseId;
10470
+ }
10471
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10472
+ } else if (part.type === "finally_clause") {
10473
+ for (const b of part.namedChildren) {
10474
+ if (!b || b.type !== "block") continue;
10475
+ const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
10476
+ defs.push(...r.defs);
10477
+ uses.push(...r.uses);
10478
+ defId = r.nextDefId;
10479
+ useId = r.nextUseId;
10480
+ }
10481
+ }
10482
+ }
10483
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10484
+ break;
10485
+ }
10384
10486
  case "else_clause": {
10385
10487
  for (let i2 = 0; i2 < stmt.childCount; i2++) {
10386
10488
  const arm = stmt.child(i2);
@@ -10996,6 +11098,42 @@ function buildBashDFG(tree) {
10996
11098
  defs.push(def);
10997
11099
  currentScope(scopeStack).set(p, def.id);
10998
11100
  }
11101
+ const bashFnDefs = /* @__PURE__ */ new Map();
11102
+ const bashFnByNodeId = /* @__PURE__ */ new Map();
11103
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11104
+ const nameNode = fn.childForFieldName("name");
11105
+ if (!nameNode) continue;
11106
+ const fnName = getNodeText(nameNode);
11107
+ if (!fnName || bashFnDefs.has(fnName)) continue;
11108
+ const m = /* @__PURE__ */ new Map();
11109
+ for (const p of positionalParams) {
11110
+ const def = {
11111
+ id: defIdCounter++,
11112
+ variable: p,
11113
+ line: fn.startPosition.row + 1,
11114
+ kind: "param",
11115
+ expression: `bash-fn:${fnName}`
11116
+ };
11117
+ defs.push(def);
11118
+ m.set(p, def.id);
11119
+ }
11120
+ bashFnDefs.set(fnName, m);
11121
+ bashFnByNodeId.set(fn.id, fnName);
11122
+ }
11123
+ const enclosingBashFn = (node) => {
11124
+ let cur = node.parent;
11125
+ while (cur) {
11126
+ if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
11127
+ cur = cur.parent;
11128
+ }
11129
+ return null;
11130
+ };
11131
+ const positionalDef = (varName, node, fallback) => {
11132
+ if (!positionalParams.includes(varName)) return fallback;
11133
+ const fnName = enclosingBashFn(node);
11134
+ if (!fnName) return fallback;
11135
+ return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
11136
+ };
10999
11137
  walkTree(tree.rootNode, (node) => {
11000
11138
  if (node.type === "variable_assignment") {
11001
11139
  const nameNode = node.childForFieldName("name");
@@ -11048,7 +11186,7 @@ function buildBashDFG(tree) {
11048
11186
  if (varNameNode) {
11049
11187
  const varName = getNodeText(varNameNode);
11050
11188
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
11051
- let reachingDef = findReachingDef(varName, scopeStack);
11189
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11052
11190
  if (reachingDef === null && !positionalParams.includes(varName)) {
11053
11191
  const def = {
11054
11192
  id: defIdCounter++,
@@ -11072,7 +11210,7 @@ function buildBashDFG(tree) {
11072
11210
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
11073
11211
  if (varNameNode && varNameNode.type === "variable_name") {
11074
11212
  const varName = getNodeText(varNameNode);
11075
- let reachingDef = findReachingDef(varName, scopeStack);
11213
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11076
11214
  if (reachingDef === null && !positionalParams.includes(varName)) {
11077
11215
  const def = {
11078
11216
  id: defIdCounter++,
@@ -11094,8 +11232,56 @@ function buildBashDFG(tree) {
11094
11232
  }
11095
11233
  });
11096
11234
  const chains = computeChains(defs, uses);
11235
+ chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
11097
11236
  return { defs, uses, chains };
11098
11237
  }
11238
+ function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
11239
+ const chains = [];
11240
+ if (bashFnDefs.size === 0) return chains;
11241
+ const renumbers = /* @__PURE__ */ new Set();
11242
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11243
+ const nameNode = fn.childForFieldName("name");
11244
+ if (!nameNode) continue;
11245
+ if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
11246
+ }
11247
+ const usesByLine = /* @__PURE__ */ new Map();
11248
+ for (const u of uses) {
11249
+ const l = usesByLine.get(u.line) ?? [];
11250
+ l.push(u);
11251
+ usesByLine.set(u.line, l);
11252
+ }
11253
+ for (const cmd of findNodes(tree.rootNode, "command")) {
11254
+ const nameNode = cmd.childForFieldName("name");
11255
+ if (!nameNode) continue;
11256
+ const fnDefs = bashFnDefs.get(getNodeText(nameNode));
11257
+ if (!fnDefs) continue;
11258
+ const fnName = getNodeText(nameNode);
11259
+ let position = 0;
11260
+ for (let i2 = 0; i2 < cmd.childCount; i2++) {
11261
+ const arg = cmd.child(i2);
11262
+ if (!arg || arg.id === nameNode.id) continue;
11263
+ if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
11264
+ const text = getNodeText(arg);
11265
+ if (!text.trim()) continue;
11266
+ position++;
11267
+ const names = /* @__PURE__ */ new Set();
11268
+ const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
11269
+ let m;
11270
+ while ((m = re.exec(text)) !== null) names.add(m[1]);
11271
+ if (names.size === 0) continue;
11272
+ const spreads = /\$\{?[@*]/.test(text);
11273
+ 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);
11274
+ const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
11275
+ for (const name2 of names) {
11276
+ for (const u of lineUses) {
11277
+ if (u.variable !== name2 || u.def_id === null) continue;
11278
+ for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
11279
+ }
11280
+ }
11281
+ }
11282
+ }
11283
+ return chains;
11284
+ }
11099
11285
  function buildRustDFG(tree, cache) {
11100
11286
  const defs = [];
11101
11287
  const uses = [];
@@ -12331,6 +12517,195 @@ function parseDistributedSliceAttribute(attrItem) {
12331
12517
  };
12332
12518
  }
12333
12519
 
12520
+ // src/core/extractors/ts-declaration-detail.ts
12521
+ function named(node) {
12522
+ const out2 = [];
12523
+ for (let i2 = 0; i2 < node.namedChildCount; i2++) {
12524
+ const c = node.namedChild(i2);
12525
+ if (c && c.type !== "comment") out2.push(c);
12526
+ }
12527
+ return out2;
12528
+ }
12529
+ function tokens(node) {
12530
+ const out2 = [];
12531
+ for (let i2 = 0; i2 < node.childCount; i2++) {
12532
+ const c = node.child(i2);
12533
+ if (c && !c.isNamed) out2.push(c.text);
12534
+ }
12535
+ return out2;
12536
+ }
12537
+ function stringLiteral(node) {
12538
+ if (node.type !== "string") return void 0;
12539
+ return named(node).map((c) => c.text).join("");
12540
+ }
12541
+ function decoratorInfo(node) {
12542
+ const expr = named(node)[0];
12543
+ if (!expr) return null;
12544
+ let callee = expr;
12545
+ let args2 = [];
12546
+ if (expr.type === "call_expression") {
12547
+ callee = expr.childForFieldName("function");
12548
+ const a = expr.childForFieldName("arguments");
12549
+ args2 = a ? named(a) : [];
12550
+ }
12551
+ if (!callee) return null;
12552
+ const name2 = callee.type === "member_expression" ? callee.childForFieldName("property")?.text : callee.text;
12553
+ if (!name2) return null;
12554
+ return {
12555
+ name: name2,
12556
+ arguments: args2.map((a) => {
12557
+ const value = stringLiteral(a);
12558
+ return value === void 0 ? { text: a.text } : { text: a.text, value };
12559
+ })
12560
+ };
12561
+ }
12562
+ function decoratorsOf(nodes) {
12563
+ return nodes.filter((n) => n.type === "decorator").map(decoratorInfo).filter((d) => d !== null);
12564
+ }
12565
+ function exportModifiers(holder) {
12566
+ const stmt = holder.parent?.type === "export_statement" ? holder.parent : null;
12567
+ if (!stmt) return [];
12568
+ return tokens(stmt).includes("default") ? ["export", "default"] : ["export"];
12569
+ }
12570
+ var MEMBER_WORDS = ["readonly", "abstract", "override", "static", "declare"];
12571
+ function memberModifiers(member) {
12572
+ const out2 = [];
12573
+ for (let i2 = 0; i2 < member.childCount; i2++) {
12574
+ const c = member.child(i2);
12575
+ if (!c) continue;
12576
+ if (c.type === "accessibility_modifier") out2.push(c.text);
12577
+ else if (c.type === "override_modifier") out2.push("override");
12578
+ else if (!c.isNamed && MEMBER_WORDS.includes(c.text)) out2.push(c.text);
12579
+ }
12580
+ const name2 = member.childForFieldName("name") ?? member.childForFieldName("property");
12581
+ if (name2?.type === "private_property_identifier" && !out2.includes("private")) out2.push("private");
12582
+ return out2;
12583
+ }
12584
+ function annotationText(node) {
12585
+ return node ? node.text.replace(/^:\s*/, "") : null;
12586
+ }
12587
+ function addModifiers(target, extra) {
12588
+ for (const m of extra) if (!target.includes(m)) target.push(m);
12589
+ }
12590
+ function clauseExports(root) {
12591
+ const names = /* @__PURE__ */ new Set();
12592
+ let defaultName = null;
12593
+ for (const stmt of named(root)) {
12594
+ if (stmt.type !== "export_statement" || stmt.childForFieldName("source")) continue;
12595
+ const clause = named(stmt).find((c) => c.type === "export_clause");
12596
+ for (const s of clause ? named(clause) : []) {
12597
+ const name2 = s.childForFieldName("name")?.text;
12598
+ const alias = s.childForFieldName("alias")?.text;
12599
+ if (!name2) continue;
12600
+ if (alias === "default") defaultName = name2;
12601
+ else names.add(name2);
12602
+ }
12603
+ const value = stmt.childForFieldName("value");
12604
+ if (value?.type === "identifier") defaultName = value.text;
12605
+ }
12606
+ return { names, defaultName };
12607
+ }
12608
+ function enrichMethod(m, fn, decorators, extra) {
12609
+ addModifiers(m.modifiers, extra);
12610
+ const ret = fn.childForFieldName("return_type");
12611
+ if (ret && m.return_type === null) m.return_type = annotationText(ret);
12612
+ const decs = decoratorsOf(decorators);
12613
+ if (decs.length > 0) m.decorators = decs;
12614
+ const params = fn.childForFieldName("parameters");
12615
+ for (const p of params ? named(params) : []) {
12616
+ const pd = decoratorsOf(named(p));
12617
+ if (pd.length === 0) continue;
12618
+ const line = p.startPosition.row + 1;
12619
+ const info2 = m.parameters.find((x) => x.line === line && !x.decorators && p.text.includes(x.name.replace(/^\.\.\./, "")));
12620
+ if (info2) info2.decorators = pd;
12621
+ }
12622
+ }
12623
+ function enrichClassBody(type, body2) {
12624
+ let pending = [];
12625
+ for (const member of named(body2)) {
12626
+ if (member.type === "decorator") {
12627
+ pending.push(member);
12628
+ continue;
12629
+ }
12630
+ const line = member.startPosition.row + 1;
12631
+ const nameNode = member.childForFieldName("name") ?? member.childForFieldName("property");
12632
+ const name2 = nameNode?.text;
12633
+ if (member.type === "method_definition" && name2) {
12634
+ const m = type.methods.find((x) => x.name === name2 && x.start_line === line);
12635
+ if (m) enrichMethod(m, member, pending, memberModifiers(member));
12636
+ } else if ((member.type === "public_field_definition" || member.type === "field_definition") && name2) {
12637
+ const f = type.fields.find((x) => x.name === name2);
12638
+ if (f) {
12639
+ addModifiers(f.modifiers, memberModifiers(member));
12640
+ if (f.type === null) f.type = annotationText(member.childForFieldName("type"));
12641
+ const decs = decoratorsOf(pending);
12642
+ if (decs.length > 0) f.decorators = decs;
12643
+ }
12644
+ }
12645
+ pending = [];
12646
+ }
12647
+ }
12648
+ function enrichTsDeclarations(tree, types) {
12649
+ const root = tree.rootNode;
12650
+ const { names, defaultName } = clauseExports(root);
12651
+ const clauseMods = (name2) => name2 === defaultName ? ["export", "default"] : names.has(name2) ? ["export"] : [];
12652
+ const visit = (node) => {
12653
+ const t = node.type;
12654
+ if (t === "class_declaration" || t === "abstract_class_declaration" || t === "interface_declaration") {
12655
+ const line = node.startPosition.row + 1;
12656
+ const name2 = node.childForFieldName("name")?.text;
12657
+ const type = types.find((x) => x.name === name2 && x.start_line === line);
12658
+ if (type && name2) {
12659
+ const mods = [...exportModifiers(node), ...clauseMods(name2)];
12660
+ if (t === "abstract_class_declaration") mods.push("abstract");
12661
+ if (node.parent?.type === "ambient_declaration") mods.push("declare");
12662
+ type.modifiers = [...new Set(mods)];
12663
+ const decs = decoratorsOf([...node.parent?.type === "export_statement" ? named(node.parent) : [], ...named(node)]);
12664
+ if (decs.length > 0) type.decorators = decs;
12665
+ const body2 = node.childForFieldName("body");
12666
+ if (body2 && t !== "interface_declaration") enrichClassBody(type, body2);
12667
+ }
12668
+ } else if (t === "function_declaration" || t === "generator_function_declaration") {
12669
+ const module2 = types.find((x) => x.name === "<module>");
12670
+ const name2 = node.childForFieldName("name")?.text;
12671
+ const m = module2?.methods.find((x) => x.name === name2 && x.start_line === node.startPosition.row + 1);
12672
+ if (m && name2) enrichMethod(m, node, [], [...exportModifiers(node), ...clauseMods(name2)]);
12673
+ } else if (t === "variable_declarator") {
12674
+ const value = node.childForFieldName("value");
12675
+ const name2 = node.childForFieldName("name")?.text;
12676
+ const decl = node.parent;
12677
+ const module2 = types.find((x) => x.name === "<module>");
12678
+ const m = module2?.methods.find((x) => x.name === name2 && x.start_line === node.startPosition.row + 1);
12679
+ if (m && name2 && value?.type === "arrow_function" && decl) enrichMethod(m, value, [], [...exportModifiers(decl), ...clauseMods(name2)]);
12680
+ }
12681
+ for (const c of named(node)) visit(c);
12682
+ };
12683
+ visit(root);
12684
+ }
12685
+ function extractTsExports(types) {
12686
+ const esm = types.some((t) => t.modifiers?.includes("export") || t.methods.some((m) => m.modifiers.includes("export")));
12687
+ if (!esm) return null;
12688
+ const out2 = [];
12689
+ const visibility = (mods) => mods.includes("private") ? null : mods.includes("protected") ? "protected" : "public";
12690
+ for (const t of types) {
12691
+ if (t.name === "<module>") {
12692
+ for (const m of t.methods) if (m.modifiers.includes("export")) out2.push({ symbol: m.name, kind: "method", visibility: "public" });
12693
+ continue;
12694
+ }
12695
+ if (!t.modifiers?.includes("export")) continue;
12696
+ out2.push({ symbol: t.name, kind: t.kind === "enum" ? "class" : t.kind, visibility: "public" });
12697
+ for (const m of t.methods) {
12698
+ const v = visibility(m.modifiers);
12699
+ if (v) out2.push({ symbol: `${t.name}.${m.name}`, kind: "method", visibility: v });
12700
+ }
12701
+ for (const f of t.fields) {
12702
+ const v = visibility(f.modifiers);
12703
+ if (v) out2.push({ symbol: `${t.name}.${f.name}`, kind: "field", visibility: v });
12704
+ }
12705
+ }
12706
+ return out2;
12707
+ }
12708
+
12334
12709
  // src/analysis/config-loader.ts
12335
12710
  var DEFAULT_SOURCES = [
12336
12711
  // HTTP Sources (Servlet API)
@@ -13032,6 +13407,10 @@ var DEFAULT_SOURCES = [
13032
13407
  { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13033
13408
  { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13034
13409
  { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13410
+ // Node redis clients are often bound to `cache` from `createClient()`; class
13411
+ // `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
13412
+ // filtered in sink-filter-pass (#818 src14).
13413
+ { method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13035
13414
  { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
13036
13415
  { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
13037
13416
  { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
@@ -13211,7 +13590,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
13211
13590
  // on arg[1] (the *Cookie) catches struct-literal composite tainting.
13212
13591
  { method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
13213
13592
  // gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
13214
- { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
13593
+ { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
13594
+ // --- Rust: actix-session / tower-sessions write -------------------------
13595
+ { method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
13215
13596
  ];
13216
13597
  var DEFAULT_SINKS = [
13217
13598
  // SQL Injection (CWE-89)
@@ -13872,7 +14253,9 @@ var DEFAULT_SINKS = [
13872
14253
  { method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13873
14254
  { method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13874
14255
  // Cron expression parsing (DoS/ReDoS)
13875
- { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
14256
+ // cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
14257
+ // `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
14258
+ { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
13876
14259
  { method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13877
14260
  // ff4j feature flags
13878
14261
  { method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
@@ -13969,9 +14352,13 @@ var DEFAULT_SINKS = [
13969
14352
  { method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13970
14353
  { method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13971
14354
  // XXE - XML External Entity (CWE-611)
13972
- { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13973
- { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13974
- { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
14355
+ // cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
14356
+ // receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
14357
+ // `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
14358
+ // libxmljs, DOMParser) have their own entries.
14359
+ { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
14360
+ { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
14361
+ { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13975
14362
  { method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13976
14363
  { method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13977
14364
  { method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
@@ -14226,6 +14613,46 @@ var DEFAULT_SINKS = [
14226
14613
  { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14227
14614
  { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14228
14615
  { method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14616
+ // DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
14617
+ // `duckdb` / `duckdb-async` bindings (Database / Connection) and
14618
+ // `react-native-duckdb` (Database / Transaction). The method names are
14619
+ // generic and the receivers come from factories (`await instance.connect()`,
14620
+ // `open({...})`), so the receiver cannot be typed: the import is the gate
14621
+ // (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
14622
+ // as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
14623
+ // `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
14624
+ // not reach it.
14625
+ { method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
14626
+ { 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"] },
14627
+ { 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"] },
14628
+ { method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14629
+ { method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14630
+ { method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14631
+ { method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14632
+ { method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14633
+ { method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14634
+ { method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14635
+ { method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14636
+ { method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14637
+ { method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14638
+ { method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14639
+ { method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14640
+ { method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14641
+ { method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14642
+ { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14643
+ { method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14644
+ { method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14645
+ { method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14646
+ { method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14647
+ // RxDB (Mango queries, any storage incl. SQLite): a selector or query
14648
+ // object, a field name passed to the query builder, or a `$regex` pattern
14649
+ // under caller control is NoSQL operator injection (CWE-943). Gated on the
14650
+ // `rxdb` import for the same reason as DuckDB above. `find` is already a
14651
+ // classless JS NoSQL sink in the JavaScript plugin.
14652
+ { method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14653
+ { method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14654
+ { method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14655
+ { method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14229
14656
  // TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
14230
14657
  // escape hatch is already covered by the classless `query` sink in the JS
14231
14658
  // plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
@@ -14336,6 +14763,8 @@ var DEFAULT_SINKS = [
14336
14763
  { method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14337
14764
  { method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14338
14765
  { method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14766
+ // Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
14767
+ { method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
14339
14768
  // needle library (used in NodeGoat)
14340
14769
  { method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14341
14770
  { method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -14830,7 +15259,7 @@ var DEFAULT_SINKS = [
14830
15259
  // receiver-taint or operator-LHS-taint tracking — the format string is the
14831
15260
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
14832
15261
  // C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
14833
- { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
15262
+ { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
14834
15263
  { method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
14835
15264
  // Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
14836
15265
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
@@ -16735,6 +17164,85 @@ function passesMutParam(line, n) {
16735
17164
  if (!new RegExp(String.raw`(?<![\w.])(?:[A-Za-z_]\w*(?:\s*::\s*[A-Za-z_]\w*)*)\s*\((?:[^)]*,\s*)?${n}\b`).test(line)) return false;
16736
17165
  return !new RegExp(String.raw`\b${n}\s*\.\s*\w+\s*\(`).test(line) || new RegExp(String.raw`(?<![\w.])\w+\s*\(\s*${n}\b`).test(line);
16737
17166
  }
17167
+ function findRustAllowlistTernarySanitizers(code) {
17168
+ const masked = maskRust(code);
17169
+ const origLines = code.split("\n");
17170
+ const maskedLines = masked.split("\n");
17171
+ const out2 = [];
17172
+ const allowlistVar = (cond) => {
17173
+ const c = cond.trim();
17174
+ let m = new RegExp(String.raw`^!?(\w+)\s*\.\s*contains\s*\(\s*&\s*(\w+)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)$`).exec(c);
17175
+ if (m) return m[2];
17176
+ if (m = new RegExp(String.raw`^!\s*\[\s*${STR}(?:\s*,\s*${STR})*\s*,?\s*\]\s*\.\s*contains\s*\(\s*&\s*${VAR}\s*\)$`).exec(c)) {
17177
+ return m[1];
17178
+ }
17179
+ return guardedVariable(c);
17180
+ };
17181
+ const LITERAL_CHAIN = /* @__PURE__ */ new Set(["to_string", "into", "to_owned", "clone"]);
17182
+ const literalOnly = (block) => {
17183
+ const maskedB = maskRust(block.trim());
17184
+ if (!maskedB.trim()) return false;
17185
+ const re = /[A-Za-z_]\w*/g;
17186
+ let m;
17187
+ while ((m = re.exec(maskedB)) !== null) {
17188
+ const id = m[0];
17189
+ if (KEYWORDS.has(id) || /[A-Z]/.test(id[0]) || LITERAL_CHAIN.has(id)) continue;
17190
+ return false;
17191
+ }
17192
+ return true;
17193
+ };
17194
+ const usesOnlyVar = (block, name2) => {
17195
+ const b = block.trim();
17196
+ if (!b) return false;
17197
+ const maskedB = maskRust(b);
17198
+ const re = new RegExp(String.raw`(?<![\w.])${name2}\b`);
17199
+ if (!re.test(maskedB)) return false;
17200
+ return !readsOtherValue(b, maskedB, name2);
17201
+ };
17202
+ for (const m of masked.matchAll(/\blet\s+(\w+)\s*=\s*if\b/g)) {
17203
+ const bound = m[1];
17204
+ const at = m.index + m[0].length - 2;
17205
+ let depth = 0;
17206
+ let open = -1;
17207
+ for (let i2 = at + 2; i2 < masked.length; i2++) {
17208
+ const ch = masked[i2];
17209
+ if (ch === "(" || ch === "[") depth++;
17210
+ else if (ch === ")" || ch === "]") depth--;
17211
+ else if (ch === "{" && depth === 0) {
17212
+ open = i2;
17213
+ break;
17214
+ } else if (ch === ";" || ch === "}") break;
17215
+ }
17216
+ if (open < 0) continue;
17217
+ const cond = code.slice(at + 2, open).trim();
17218
+ const guarded = allowlistVar(cond);
17219
+ if (!guarded) continue;
17220
+ const thenClose = matchBrace(masked, open);
17221
+ if (thenClose < 0) continue;
17222
+ const thenBody = code.slice(open + 1, thenClose);
17223
+ if (!/^\s*else\b/.test(masked.slice(thenClose + 1, thenClose + 10))) continue;
17224
+ const elseOpen = masked.indexOf("{", thenClose + 1);
17225
+ if (elseOpen < 0) continue;
17226
+ const elseClose = matchBrace(masked, elseOpen);
17227
+ if (elseClose < 0) continue;
17228
+ const elseBody = code.slice(elseOpen + 1, elseClose);
17229
+ const inverted = /^\s*!/.test(cond);
17230
+ const ok = inverted ? literalOnly(thenBody) && usesOnlyVar(elseBody, guarded) : usesOnlyVar(thenBody, guarded) && literalOnly(elseBody);
17231
+ if (!ok) continue;
17232
+ const assignLine = masked.slice(0, at).split("\n").length;
17233
+ const mention = new RegExp(String.raw`(?<![\w.])${bound}\b`);
17234
+ for (let l = assignLine; l < origLines.length; l++) {
17235
+ if (!mention.test(maskedLines[l])) continue;
17236
+ out2.push({
17237
+ type: "rust_allowlist_ternary",
17238
+ method: "guard",
17239
+ line: l + 1,
17240
+ sanitizes: CREDITED_TYPES
17241
+ });
17242
+ }
17243
+ }
17244
+ return out2;
17245
+ }
16738
17246
  function findRustLiteralEqualityGuardSanitizers(code) {
16739
17247
  const masked = maskRust(code);
16740
17248
  const origLines = code.split("\n");
@@ -16855,7 +17363,7 @@ function findRustLiteralEqualityGuardSanitizers(code) {
16855
17363
  });
16856
17364
  }
16857
17365
  }
16858
- return out2;
17366
+ return [...out2, ...findRustAllowlistTernarySanitizers(code)];
16859
17367
  }
16860
17368
  function statementSpan(code, masked, lineStart, endIdx) {
16861
17369
  let start2 = lineStart;
@@ -17009,6 +17517,198 @@ function constTableLookupDefIds(defs, code, language) {
17009
17517
  return out2;
17010
17518
  }
17011
17519
 
17520
+ // src/analysis/js-crlf-strip.ts
17521
+ var MAX_PATTERN_LENGTH = 200;
17522
+ var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
17523
+ "replace",
17524
+ "replaceAll",
17525
+ "trim",
17526
+ "trimStart",
17527
+ "trimEnd",
17528
+ "toString",
17529
+ "toLowerCase",
17530
+ "toUpperCase",
17531
+ "slice",
17532
+ "substring"
17533
+ ]);
17534
+ var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
17535
+ function readQuoted(s, i2) {
17536
+ const q = s[i2];
17537
+ if (q !== '"' && q !== "'" && q !== "`") return null;
17538
+ let out2 = "";
17539
+ for (let j = i2 + 1; j < s.length; j++) {
17540
+ const c = s[j];
17541
+ if (c === q) return { value: out2, end: j + 1 };
17542
+ if (q === "`" && c === "$" && s[j + 1] === "{") return null;
17543
+ if (c === "\\") {
17544
+ const n = s[j + 1];
17545
+ if (n === void 0) return null;
17546
+ switch (n) {
17547
+ case "n":
17548
+ out2 += "\n";
17549
+ j++;
17550
+ continue;
17551
+ case "r":
17552
+ out2 += "\r";
17553
+ j++;
17554
+ continue;
17555
+ case "t":
17556
+ out2 += " ";
17557
+ j++;
17558
+ continue;
17559
+ case "0":
17560
+ out2 += "\0";
17561
+ j++;
17562
+ continue;
17563
+ case "x": {
17564
+ const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
17565
+ if (!h) return null;
17566
+ out2 += String.fromCharCode(parseInt(h[0], 16));
17567
+ j += 3;
17568
+ continue;
17569
+ }
17570
+ case "u": {
17571
+ const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
17572
+ if (!h) return null;
17573
+ out2 += String.fromCharCode(parseInt(h[0], 16));
17574
+ j += 5;
17575
+ continue;
17576
+ }
17577
+ default:
17578
+ out2 += n;
17579
+ j++;
17580
+ continue;
17581
+ }
17582
+ }
17583
+ if (c === "\n" && q !== "`") return null;
17584
+ out2 += c;
17585
+ }
17586
+ return null;
17587
+ }
17588
+ function readRegexLiteral(s, i2) {
17589
+ if (s[i2] !== "/") return null;
17590
+ let inClass = false;
17591
+ for (let j = i2 + 1; j < s.length; j++) {
17592
+ const c = s[j];
17593
+ if (c === "\n") return null;
17594
+ if (c === "\\") {
17595
+ j++;
17596
+ continue;
17597
+ }
17598
+ if (inClass) {
17599
+ if (c === "]") inClass = false;
17600
+ continue;
17601
+ }
17602
+ if (c === "[") {
17603
+ inClass = true;
17604
+ continue;
17605
+ }
17606
+ if (c === "/") {
17607
+ const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
17608
+ return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
17609
+ }
17610
+ }
17611
+ return null;
17612
+ }
17613
+ var skipWs = (s, i2) => {
17614
+ while (i2 < s.length && /\s/.test(s[i2])) i2++;
17615
+ return i2;
17616
+ };
17617
+ function parseSteps(chainText) {
17618
+ const steps = [];
17619
+ const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
17620
+ let m;
17621
+ while ((m = callRe.exec(chainText)) !== null) {
17622
+ const kind = m[1];
17623
+ let i2 = skipWs(chainText, m.index + m[0].length);
17624
+ let pattern;
17625
+ const rx = readRegexLiteral(chainText, i2);
17626
+ if (rx) {
17627
+ if (rx.source.length > MAX_PATTERN_LENGTH) return null;
17628
+ try {
17629
+ pattern = new RegExp(rx.source, rx.flags);
17630
+ } catch {
17631
+ return null;
17632
+ }
17633
+ i2 = rx.end;
17634
+ } else {
17635
+ const str = readQuoted(chainText, i2);
17636
+ if (!str) return null;
17637
+ pattern = str.value;
17638
+ i2 = str.end;
17639
+ }
17640
+ i2 = skipWs(chainText, i2);
17641
+ if (chainText[i2] !== ",") return null;
17642
+ i2 = skipWs(chainText, i2 + 1);
17643
+ const rep = readQuoted(chainText, i2);
17644
+ if (!rep) return null;
17645
+ i2 = skipWs(chainText, rep.end);
17646
+ if (chainText[i2] !== ")") return null;
17647
+ steps.push({ kind, pattern, replacement: rep.value });
17648
+ callRe.lastIndex = i2 + 1;
17649
+ }
17650
+ return steps;
17651
+ }
17652
+ function run2(steps, input) {
17653
+ let s = input;
17654
+ try {
17655
+ for (const st of steps) {
17656
+ if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
17657
+ s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
17658
+ }
17659
+ } catch {
17660
+ return null;
17661
+ }
17662
+ return s;
17663
+ }
17664
+ function jsReplaceChainVerdict(chainText) {
17665
+ const steps = parseSteps(chainText);
17666
+ if (!steps || steps.length === 0) return "undecided";
17667
+ const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
17668
+ for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
17669
+ if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
17670
+ }
17671
+ const out2 = run2(steps, PROBE);
17672
+ if (out2 === null) return "undecided";
17673
+ return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
17674
+ }
17675
+ function jsChainTextAt(sourceLines, line, column) {
17676
+ const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
17677
+ let depth = 0;
17678
+ let prevSig = "";
17679
+ let i2 = 0;
17680
+ for (; i2 < text.length; i2++) {
17681
+ const c = text[i2];
17682
+ if (c === '"' || c === "'" || c === "`") {
17683
+ for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
17684
+ prevSig = c;
17685
+ continue;
17686
+ }
17687
+ if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
17688
+ const rx = readRegexLiteral(text, i2);
17689
+ if (rx) {
17690
+ i2 = rx.end - 1;
17691
+ prevSig = "/";
17692
+ continue;
17693
+ }
17694
+ }
17695
+ if (c === "(" || c === "[" || c === "{") {
17696
+ depth++;
17697
+ prevSig = c;
17698
+ continue;
17699
+ }
17700
+ if (c === ")" || c === "]" || c === "}") {
17701
+ if (depth === 0) break;
17702
+ depth--;
17703
+ prevSig = c;
17704
+ continue;
17705
+ }
17706
+ if (depth === 0 && !/[\w$.\s]/.test(c)) break;
17707
+ if (!/\s/.test(c)) prevSig = c;
17708
+ }
17709
+ return text.slice(0, i2);
17710
+ }
17711
+
17012
17712
  // src/analysis/passes/language-sources-pass.ts
17013
17713
  var JS_DOM_XSS_SINKS = [
17014
17714
  { pattern: /\.innerHTML\s*=/, type: "xss", cwe: "CWE-79", severity: "critical" },
@@ -17260,6 +17960,7 @@ var LanguageSourcesPass = class {
17260
17960
  }
17261
17961
  additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
17262
17962
  additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
17963
+ additionalSanitizers.push(...findBashVerifiedPackageDownloadSanitizers(code));
17263
17964
  additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
17264
17965
  }
17265
17966
  if (language === "go") {
@@ -17375,8 +18076,13 @@ var LanguageSourcesPass = class {
17375
18076
  }
17376
18077
  }
17377
18078
  if (language === "rust") {
18079
+ additionalSanitizers.push(...findRustConstSliceMembershipGuardSanitizers(code));
17378
18080
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
17379
18081
  additionalSanitizers.push(...findRustCanonicalizeGuardSanitizers(code));
18082
+ additionalSanitizers.push(
18083
+ ...findRustNumericRangeCheckGuardSanitizers(code)
18084
+ );
18085
+ additionalSanitizers.push(...findRustPatInfoHttpOnlyResponseSanitizers(code));
17380
18086
  additionalSanitizers.push(...findRustLiteralEqualityGuardSanitizers(code));
17381
18087
  additionalSanitizers.push(...findRustArgvCommandSanitizers(code));
17382
18088
  const rustMisconfigFindings = findRustPatternFindings(code, graph.ir.meta.file);
@@ -17434,6 +18140,9 @@ var LanguageSourcesPass = class {
17434
18140
  additionalSanitizers.push(
17435
18141
  ...findJsCommandAllowlistGuardSanitizers(code)
17436
18142
  );
18143
+ additionalSanitizers.push(
18144
+ ...findJsNumericRangeCheckGuardSanitizers(code)
18145
+ );
17437
18146
  for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
17438
18147
  ctx.addFinding(finding);
17439
18148
  }
@@ -17467,6 +18176,12 @@ var LanguageSourcesPass = class {
17467
18176
  )) {
17468
18177
  ctx.addFinding(finding);
17469
18178
  }
18179
+ for (const finding of findJsZipHeaderFailopenFindings(
18180
+ code,
18181
+ graph.ir.meta.file
18182
+ )) {
18183
+ ctx.addFinding(finding);
18184
+ }
17470
18185
  for (const finding of findJsJsonParseBodyFindings(
17471
18186
  code,
17472
18187
  graph.ir.meta.file
@@ -17516,6 +18231,7 @@ var LanguageSourcesPass = class {
17516
18231
  }
17517
18232
  additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
17518
18233
  additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, "java"));
18234
+ additionalSanitizers.push(...findJavaTemplateDelimiterWrapperSanitizers(code));
17519
18235
  additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
17520
18236
  for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
17521
18237
  ctx.addFinding(finding);
@@ -18233,6 +18949,7 @@ function findCSharpRequestSources(sourceCode, language) {
18233
18949
  const assignRe = /^\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
18234
18950
  const arrayElementAssignRe = /^\s*([A-Za-z_]\w*)\s*\[[^\]]+\]\s*=\s*(.+?);?\s*$/;
18235
18951
  const memberFieldAssignRe = /^\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
18952
+ const collectionAddRe = /^\s*([A-Za-z_]\w*)\s*\.\s*Add\s*\(\s*(.+?)\s*\)\s*;?\s*$/;
18236
18953
  const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
18237
18954
  const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
18238
18955
  const formFileParams = /* @__PURE__ */ new Set();
@@ -18326,6 +19043,25 @@ function findCSharpRequestSources(sourceCode, language) {
18326
19043
  continue;
18327
19044
  }
18328
19045
  }
19046
+ const addEl = collectionAddRe.exec(stmt);
19047
+ if (addEl) {
19048
+ const [, baseName, rhs2] = addEl;
19049
+ const type2 = classify(rhs2);
19050
+ if (type2 && baseName) {
19051
+ const lineNumber2 = i2 + 1;
19052
+ if (!sources.some((s) => s.line === lineNumber2 && s.variable === baseName)) {
19053
+ sources.push({
19054
+ type: type2,
19055
+ location: `${baseName}.Add(${rhs2.trim().substring(0, 50)}${rhs2.length > 50 ? "..." : ""})`,
19056
+ severity: type2 === "db_input" ? "medium" : "high",
19057
+ line: lineNumber2,
19058
+ confidence: 1,
19059
+ variable: baseName
19060
+ });
19061
+ }
19062
+ continue;
19063
+ }
19064
+ }
18329
19065
  const m = assignRe.exec(stmt);
18330
19066
  const rhsCrossesBlock = m !== null && m[2].includes("{");
18331
19067
  if (!m || rhsCrossesBlock) {
@@ -18705,9 +19441,9 @@ function findGraphqlResolverArgSources(sourceCode) {
18705
19441
  maps.push(open + inline.index + inline[0].length - 1);
18706
19442
  continue;
18707
19443
  }
18708
- const named = opts.match(/\bresolvers\s*(?::\s*(\w+))?\s*[,}]/);
18709
- if (!named) continue;
18710
- const ident = named[1] ?? "resolvers";
19444
+ const named2 = opts.match(/\bresolvers\s*(?::\s*(\w+))?\s*[,}]/);
19445
+ if (!named2) continue;
19446
+ const ident = named2[1] ?? "resolvers";
18711
19447
  const decl = new RegExp(
18712
19448
  `\\b(?:const|let|var)\\s+${ident}\\s*(?::\\s*[A-Za-z_$][\\w$]*(?:\\s*\\.\\s*[A-Za-z_$][\\w$]*)*(?:\\s*<[^<>=]*(?:<[^<>=]*>[^<>=]*)*>)?\\s*)?=\\s*\\{`
18713
19449
  ).exec(sourceCode);
@@ -18870,15 +19606,15 @@ function evaluatePythonConstExpression(expr, consts) {
18870
19606
  if (parts2.length !== 1) return void 0;
18871
19607
  const matched = expr.match(/\d+|[A-Za-z_][A-Za-z0-9_]*|\/\/|[-+*/%()]|\S/g);
18872
19608
  if (!matched) return void 0;
18873
- const tokens = matched;
19609
+ const tokens2 = matched;
18874
19610
  let pos = 0;
18875
- const peek = () => tokens[pos];
19611
+ const peek = () => tokens2[pos];
18876
19612
  const parseFactor = () => {
18877
- const tok = tokens[pos++];
19613
+ const tok = tokens2[pos++];
18878
19614
  if (tok === void 0) return void 0;
18879
19615
  if (tok === "(") {
18880
19616
  const inner = parseSum();
18881
- if (tokens[pos++] !== ")") return void 0;
19617
+ if (tokens2[pos++] !== ")") return void 0;
18882
19618
  return inner;
18883
19619
  }
18884
19620
  if (tok === "-") {
@@ -18893,7 +19629,7 @@ function evaluatePythonConstExpression(expr, consts) {
18893
19629
  let value = parseFactor();
18894
19630
  if (value === void 0) return void 0;
18895
19631
  while (peek() === "*" || peek() === "/" || peek() === "//" || peek() === "%") {
18896
- const op = tokens[pos++];
19632
+ const op = tokens2[pos++];
18897
19633
  const rhs = parseFactor();
18898
19634
  if (rhs === void 0) return void 0;
18899
19635
  if ((op === "/" || op === "//" || op === "%") && rhs === 0) return void 0;
@@ -18908,7 +19644,7 @@ function evaluatePythonConstExpression(expr, consts) {
18908
19644
  let value = parseTerm();
18909
19645
  if (value === void 0) return void 0;
18910
19646
  while (peek() === "+" || peek() === "-") {
18911
- const op = tokens[pos++];
19647
+ const op = tokens2[pos++];
18912
19648
  const rhs = parseTerm();
18913
19649
  if (rhs === void 0) return void 0;
18914
19650
  value = op === "+" ? value + rhs : value - rhs;
@@ -18916,7 +19652,7 @@ function evaluatePythonConstExpression(expr, consts) {
18916
19652
  return value;
18917
19653
  }
18918
19654
  const result = parseSum();
18919
- return pos === tokens.length ? result : void 0;
19655
+ return pos === tokens2.length ? result : void 0;
18920
19656
  }
18921
19657
  function normalizeListIndex(index, length) {
18922
19658
  const resolved = index < 0 ? length + index : index;
@@ -18964,6 +19700,140 @@ function computeDeadPythonBranchLines(lines) {
18964
19700
  }
18965
19701
  return dead;
18966
19702
  }
19703
+ function parseJavaSwitchCaseLabel(label) {
19704
+ const t = label.trim();
19705
+ if (t === "default") return "default";
19706
+ const ch = t.match(/^'(\\.|[^'])'\s*$/);
19707
+ if (ch) {
19708
+ const inner = ch[1];
19709
+ if (inner.startsWith("\\")) {
19710
+ if (inner === "\\'") return 39;
19711
+ if (inner === "\\\\") return 92;
19712
+ if (inner === "\\n") return 10;
19713
+ return void 0;
19714
+ }
19715
+ return inner.charCodeAt(0);
19716
+ }
19717
+ const num = t.match(/^(-?\d+)\s*$/);
19718
+ if (num) return Number(num[1]);
19719
+ return void 0;
19720
+ }
19721
+ function parseJavaSwitchLabelLine(line) {
19722
+ const caseLine = line.match(/^\s*case\s+(.+?)\s*:\s*$/);
19723
+ if (caseLine) return parseJavaSwitchCaseLabel(caseLine[1]);
19724
+ if (/^\s*default\s*:\s*$/.test(line)) return "default";
19725
+ return void 0;
19726
+ }
19727
+ function markDeadJavaSwitchBodies(lines, switchLineIdx, switchValue, dead) {
19728
+ let depth = 0;
19729
+ let started = false;
19730
+ let groupLabels = [];
19731
+ let bodyStart = -1;
19732
+ let explicitCaseMatched = false;
19733
+ const groupIsTaken = () => {
19734
+ if (groupLabels.length === 0) return false;
19735
+ const explicit = groupLabels.some((v) => v !== "default" && v === switchValue);
19736
+ if (explicit) return true;
19737
+ return groupLabels.includes("default") && !explicitCaseMatched;
19738
+ };
19739
+ const flushGroup = (bodyEnd) => {
19740
+ if (bodyStart < 0 || groupLabels.length === 0) return;
19741
+ const taken = groupIsTaken();
19742
+ if (groupLabels.some((v) => v !== "default" && v === switchValue)) {
19743
+ explicitCaseMatched = true;
19744
+ }
19745
+ if (!taken) {
19746
+ for (let k = bodyStart; k < bodyEnd; k++) dead.add(k);
19747
+ }
19748
+ groupLabels = [];
19749
+ bodyStart = -1;
19750
+ };
19751
+ for (let j = switchLineIdx; j < lines.length; j++) {
19752
+ const line = lines[j];
19753
+ for (const ch of line) {
19754
+ if (ch === "{") {
19755
+ depth++;
19756
+ started = true;
19757
+ } else if (ch === "}") depth--;
19758
+ }
19759
+ if (!started) continue;
19760
+ const label = parseJavaSwitchLabelLine(line);
19761
+ if (label !== void 0) {
19762
+ if (bodyStart >= 0 && j === bodyStart) {
19763
+ groupLabels.push(label);
19764
+ bodyStart = j + 1;
19765
+ } else {
19766
+ flushGroup(j);
19767
+ groupLabels = [label];
19768
+ bodyStart = j + 1;
19769
+ }
19770
+ }
19771
+ if (started && depth === 0) {
19772
+ flushGroup(j);
19773
+ break;
19774
+ }
19775
+ }
19776
+ }
19777
+ function computeDeadJavaBranchLines(lines) {
19778
+ const dead = /* @__PURE__ */ new Set();
19779
+ const consts = /* @__PURE__ */ new Map();
19780
+ const stringConsts = /* @__PURE__ */ new Map();
19781
+ const charConsts = /* @__PURE__ */ new Map();
19782
+ for (let i2 = 0; i2 < lines.length; i2++) {
19783
+ const line = lines[i2];
19784
+ const stringAssign = line.match(/^\s*String\s+([A-Za-z_]\w*)\s*=\s*"([^"]*)"\s*;\s*$/);
19785
+ if (stringAssign) {
19786
+ stringConsts.set(stringAssign[1], stringAssign[2]);
19787
+ continue;
19788
+ }
19789
+ const charAtAssign = line.match(
19790
+ /^\s*char\s+([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\.charAt\s*\(\s*(\d+)\s*\)\s*;\s*(?:\/\/.*)?$/
19791
+ );
19792
+ if (charAtAssign) {
19793
+ const str = stringConsts.get(charAtAssign[2]);
19794
+ const idx = Number(charAtAssign[3]);
19795
+ if (str !== void 0 && idx >= 0 && idx < str.length) {
19796
+ charConsts.set(charAtAssign[1], str.charCodeAt(idx));
19797
+ } else {
19798
+ charConsts.delete(charAtAssign[1]);
19799
+ }
19800
+ continue;
19801
+ }
19802
+ const intAssign = line.match(/^\s*(?:int|long|short|byte)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/);
19803
+ if (intAssign) {
19804
+ const v = evaluatePythonConstExpression(intAssign[2].trim(), consts);
19805
+ if (typeof v === "number") consts.set(intAssign[1], v);
19806
+ else consts.delete(intAssign[1]);
19807
+ continue;
19808
+ }
19809
+ const switchMatch = line.match(/^\s*switch\s*\(([^)]+)\)\s*\{?\s*$/);
19810
+ if (switchMatch) {
19811
+ const sel = switchMatch[1].trim();
19812
+ let switchVal = charConsts.get(sel);
19813
+ if (switchVal === void 0) {
19814
+ const n = evaluatePythonConstExpression(sel, consts);
19815
+ if (typeof n === "number") switchVal = n;
19816
+ }
19817
+ if (switchVal !== void 0) {
19818
+ markDeadJavaSwitchBodies(lines, i2, switchVal, dead);
19819
+ }
19820
+ continue;
19821
+ }
19822
+ const ifMatch = line.match(/^(\s*)if\s*\((.+)\)\s+(.+)$/);
19823
+ if (!ifMatch) continue;
19824
+ const decided = evaluatePythonConstExpression(ifMatch[2].trim(), consts);
19825
+ if (decided !== true && decided !== false) continue;
19826
+ const rest = ifMatch[3].trim();
19827
+ if (/\belse\b/.test(rest)) continue;
19828
+ if (i2 + 1 < lines.length && /^\s*else\b/.test(lines[i2 + 1])) {
19829
+ if (decided === true) dead.add(i2 + 1);
19830
+ else dead.add(i2);
19831
+ } else if (decided === false) {
19832
+ dead.add(i2);
19833
+ }
19834
+ }
19835
+ return dead;
19836
+ }
18967
19837
  function buildPythonTaintedVars(sourceCode) {
18968
19838
  const tainted = /* @__PURE__ */ new Map();
18969
19839
  const containerTainted = /* @__PURE__ */ new Map();
@@ -19683,6 +20553,82 @@ function buildRustTaintedVars(sourceCode, seedVars) {
19683
20553
  }
19684
20554
  return derived;
19685
20555
  }
20556
+ function stripJavaTrailingLineComment(line) {
20557
+ let quote = null;
20558
+ for (let i2 = 0; i2 < line.length; i2++) {
20559
+ const c = line[i2];
20560
+ if (quote) {
20561
+ if (c === "\\") i2++;
20562
+ else if (c === quote) quote = null;
20563
+ } else if (c === '"' || c === "'") {
20564
+ quote = c;
20565
+ } else if (c === "/" && line[i2 + 1] === "/") {
20566
+ return line.slice(0, i2).trimEnd();
20567
+ }
20568
+ }
20569
+ return line;
20570
+ }
20571
+ function javaExpressionIdents(expr) {
20572
+ const code = expr.replace(/"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
20573
+ return code.match(/[\p{L}\p{N}_]+/gu) ?? [];
20574
+ }
20575
+ function recordJavaSlotStatement(line, listElems, mapSlots, intConsts) {
20576
+ const t = line.trimEnd().replace(/;\s*\/\/[^"]*$/, ";");
20577
+ const intDecl = /^\s*(?:final\s+)?(?:int|long|short|byte)\s+([A-Za-z_]\w*)\s*=\s*([^;]+);\s*$/.exec(t);
20578
+ if (intDecl) {
20579
+ const v = evaluatePythonConstExpression(intDecl[2].trim(), intConsts);
20580
+ if (typeof v === "number") intConsts.set(intDecl[1], v);
20581
+ else intConsts.delete(intDecl[1]);
20582
+ return false;
20583
+ }
20584
+ const add = /^\s*([A-Za-z_]\w*)\.add\s*\(\s*([A-Za-z_]\w*|"(?:[^"\\]|\\.)*")\s*\)\s*;\s*$/.exec(t);
20585
+ if (add) {
20586
+ const elems = listElems.get(add[1]) ?? [];
20587
+ elems.push(add[2].startsWith('"') ? null : add[2]);
20588
+ listElems.set(add[1], elems);
20589
+ return true;
20590
+ }
20591
+ const remove = /^\s*([A-Za-z_]\w*)\.remove\s*\(\s*(\d+)\s*\)\s*;\s*$/.exec(t);
20592
+ if (remove && listElems.has(remove[1])) {
20593
+ const elems = listElems.get(remove[1]);
20594
+ const idx = Number(remove[2]);
20595
+ if (idx < elems.length) elems.splice(idx, 1);
20596
+ else listElems.delete(remove[1]);
20597
+ return true;
20598
+ }
20599
+ const put = /^\s*([A-Za-z_]\w*)\.put\s*\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*(.+?)\s*\)\s*;\s*$/.exec(t);
20600
+ if (put) {
20601
+ const keys = mapSlots.get(put[1]) ?? /* @__PURE__ */ new Map();
20602
+ keys.set(put[2], javaExpressionIdents(put[3]));
20603
+ mapSlots.set(put[1], keys);
20604
+ return true;
20605
+ }
20606
+ for (const table of [listElems, mapSlots]) {
20607
+ for (const name2 of [...table.keys()]) {
20608
+ const mention = new RegExp(`(?<![\\w.])${name2}(?!\\w)`);
20609
+ if (!mention.test(t)) continue;
20610
+ const reads = new RegExp(`(?<![\\w.])${name2}\\.(?:get|size|isEmpty|contains\\w*)\\s*\\(`, "g");
20611
+ if (mention.test(t.replace(reads, " "))) table.delete(name2);
20612
+ }
20613
+ }
20614
+ return false;
20615
+ }
20616
+ function javaSlotRead(rhs, listElems, mapSlots) {
20617
+ const m = /^(?:\(\s*[A-Za-z_][\w.<>]*\s*\)\s*)?([A-Za-z_]\w*)\.get\s*\(\s*(\d+|"(?:[^"\\]|\\.)*")\s*\)$/.exec(rhs.trim());
20618
+ if (!m) return void 0;
20619
+ const [, recv, arg] = m;
20620
+ if (arg.startsWith('"')) {
20621
+ const keys = mapSlots.get(recv);
20622
+ if (!keys) return void 0;
20623
+ return keys.get(arg.slice(1, -1)) ?? [];
20624
+ }
20625
+ const elems = listElems.get(recv);
20626
+ if (!elems) return void 0;
20627
+ const idx = Number(arg);
20628
+ if (idx >= elems.length) return void 0;
20629
+ const slot = elems[idx];
20630
+ return slot === null ? [] : [slot];
20631
+ }
19686
20632
  function joinWrappedJavaStatement(lines, i2) {
19687
20633
  const strip = (l) => l.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)'/g, '""').replace(/\/\/.*$/, "");
19688
20634
  const first = strip(lines[i2]);
@@ -19704,12 +20650,19 @@ function joinWrappedJavaStatement(lines, i2) {
19704
20650
  }
19705
20651
  return null;
19706
20652
  }
19707
- function buildJavaTaintedVars(sourceCode, seedVars) {
20653
+ function buildJavaTaintedVars(sourceCode, seedVars, deadBranchLines, options) {
19708
20654
  const derived = /* @__PURE__ */ new Map();
19709
20655
  const knownTainted = new Set(seedVars);
19710
- const lines = sourceCode.split("\n");
20656
+ const trackSlots = options?.trackSlots === true;
20657
+ const lines = trackSlots ? sourceCode.split("\n").map(stripJavaTrailingLineComment) : sourceCode.split("\n");
20658
+ const dead = deadBranchLines ?? /* @__PURE__ */ new Set();
20659
+ const listElems = /* @__PURE__ */ new Map();
20660
+ const mapSlots = /* @__PURE__ */ new Map();
20661
+ const intConsts = /* @__PURE__ */ new Map();
19711
20662
  const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
19712
20663
  const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
20664
+ const thisFieldAssignRe = /^\s*this\.([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
20665
+ const beanSetterCallRe = /\bset([A-Z]\w*)\s*\((.*)\)\s*;?\s*$/;
19713
20666
  const JAVA_KEYWORDS = /* @__PURE__ */ new Set([
19714
20667
  "if",
19715
20668
  "else",
@@ -19745,10 +20698,23 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
19745
20698
  while (changed && guard < lines.length + 2) {
19746
20699
  changed = false;
19747
20700
  guard++;
20701
+ listElems.clear();
20702
+ mapSlots.clear();
20703
+ intConsts.clear();
19748
20704
  for (let i2 = 0; i2 < lines.length; i2++) {
20705
+ if (deadBranchLines !== void 0 && dead.has(i2)) continue;
19749
20706
  const line = lines[i2];
19750
20707
  const trimmed = line.trimStart();
19751
20708
  if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
20709
+ if (trackSlots && recordJavaSlotStatement(line, listElems, mapSlots, intConsts)) {
20710
+ const add = /^\s*([A-Za-z_]\w*)\.add\s*\(\s*([A-Za-z_]\w*)\s*\)\s*;/.exec(line);
20711
+ if (add && knownTainted.has(add[2]) && !knownTainted.has(add[1])) {
20712
+ derived.set(add[1], i2 + 1);
20713
+ knownTainted.add(add[1]);
20714
+ changed = true;
20715
+ }
20716
+ continue;
20717
+ }
19752
20718
  const arrayCopyRe = /\bSystem\.arraycopy\s*\(\s*([A-Za-z_]\w*)\s*,\s*[^,]+\s*,\s*([A-Za-z_]\w*)\s*,/;
19753
20719
  const acMatch = arrayCopyRe.exec(line);
19754
20720
  if (acMatch) {
@@ -19778,17 +20744,79 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
19778
20744
  let assignMatch = !declMatch ? assignRe.exec(statement) : null;
19779
20745
  if (!declMatch && !assignMatch) {
19780
20746
  const joined = joinWrappedJavaStatement(lines, i2);
19781
- if (joined === null) continue;
19782
- statement = joined;
19783
- declMatch = declRe.exec(statement);
19784
- assignMatch = !declMatch ? assignRe.exec(statement) : null;
20747
+ if (joined !== null) {
20748
+ statement = joined;
20749
+ declMatch = declRe.exec(statement);
20750
+ assignMatch = !declMatch ? assignRe.exec(statement) : null;
20751
+ }
20752
+ }
20753
+ const thisFieldMatch = thisFieldAssignRe.exec(statement.trim());
20754
+ if (thisFieldMatch) {
20755
+ const fieldName = thisFieldMatch[1];
20756
+ const rhs2 = thisFieldMatch[2];
20757
+ const rhsCode2 = rhs2.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
20758
+ const rhsIdents2 = rhsCode2.match(/[\p{L}\p{N}_]+/gu);
20759
+ const ref2 = rhsIdents2 !== null && rhsIdents2.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode2));
20760
+ if (ref2) {
20761
+ const thisField = `this.${fieldName}`;
20762
+ if (!knownTainted.has(thisField)) {
20763
+ derived.set(thisField, i2 + 1);
20764
+ knownTainted.add(thisField);
20765
+ changed = true;
20766
+ }
20767
+ if (!knownTainted.has(fieldName)) {
20768
+ derived.set(fieldName, i2 + 1);
20769
+ knownTainted.add(fieldName);
20770
+ changed = true;
20771
+ }
20772
+ }
20773
+ continue;
20774
+ }
20775
+ const setterCall = beanSetterCallRe.exec(statement);
20776
+ if (setterCall) {
20777
+ const fieldName = setterCall[1].charAt(0).toLowerCase() + setterCall[1].slice(1);
20778
+ const arg = setterCall[2];
20779
+ const rhsCode2 = arg.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
20780
+ const rhsIdents2 = rhsCode2.match(/[\p{L}\p{N}_]+/gu);
20781
+ const ref2 = rhsIdents2 !== null && rhsIdents2.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode2));
20782
+ if (ref2) {
20783
+ const thisField = `this.${fieldName}`;
20784
+ if (!knownTainted.has(thisField)) {
20785
+ derived.set(thisField, i2 + 1);
20786
+ knownTainted.add(thisField);
20787
+ changed = true;
20788
+ }
20789
+ if (!knownTainted.has(fieldName)) {
20790
+ derived.set(fieldName, i2 + 1);
20791
+ knownTainted.add(fieldName);
20792
+ changed = true;
20793
+ }
20794
+ }
20795
+ continue;
19785
20796
  }
19786
20797
  const m = declMatch ?? assignMatch;
19787
20798
  if (!m) continue;
19788
20799
  const lhs = m[1];
19789
- const rhs = m[2];
20800
+ let rhs = m[2];
19790
20801
  if (JAVA_KEYWORDS.has(lhs)) continue;
19791
20802
  if (knownTainted.has(lhs)) continue;
20803
+ if (trackSlots) {
20804
+ const slot = javaSlotRead(rhs, listElems, mapSlots);
20805
+ if (slot !== void 0) {
20806
+ if (slot.some((t) => knownTainted.has(t))) {
20807
+ derived.set(lhs, i2 + 1);
20808
+ knownTainted.add(lhs);
20809
+ changed = true;
20810
+ }
20811
+ continue;
20812
+ }
20813
+ const tern = /^([^?]+?)\s*\?\s*([^:]+?)\s*:\s*(.+)$/.exec(rhs.trim());
20814
+ if (tern) {
20815
+ const decided = evaluatePythonConstExpression(tern[1].trim().replace(/^\((.*)\)$/, "$1"), intConsts);
20816
+ if (decided === true) rhs = tern[2];
20817
+ else if (decided === false) rhs = tern[3];
20818
+ }
20819
+ }
19792
20820
  const rhsCode = rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
19793
20821
  const rhsIdents = rhsCode.match(/[\p{L}\p{N}_]+/gu);
19794
20822
  const ref = rhsIdents !== null && rhsIdents.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode));
@@ -19840,8 +20868,11 @@ function findBashTaintSources(sourceCode, dfg) {
19840
20868
  const insideFunction = braceDepth > 0;
19841
20869
  if (!insideFunction) {
19842
20870
  const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
20871
+ const header = /^\s*(?:function\s+)?[A-Za-z_][\w-]*\s*(?:\(\s*\))?\s*\{/.exec(line);
20872
+ const isFnHeader = header !== null && (/\(\s*\)/.test(header[0]) || /^\s*function\s/.test(header[0]));
20873
+ const topLevelText = (isFnHeader ? line.slice(0, header[0].length) : line).replace(/'[^']*'/g, "''");
19843
20874
  let m;
19844
- while ((m = positionalRe.exec(line)) !== null) {
20875
+ while ((m = positionalRe.exec(topLevelText)) !== null) {
19845
20876
  const param = m[1] ?? m[2];
19846
20877
  const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
19847
20878
  if (!alreadyExists) {
@@ -19897,10 +20928,10 @@ function findBashTaintSources(sourceCode, dfg) {
19897
20928
  if (readMatch) {
19898
20929
  const argStr = readMatch[1].trim();
19899
20930
  const noRedirect = argStr.replace(/\s*<[^<].*$/, "").trim();
19900
- const tokens = noRedirect.split(/\s+/);
20931
+ const tokens2 = noRedirect.split(/\s+/);
19901
20932
  const varNames = [];
19902
- for (let ti = 0; ti < tokens.length; ti++) {
19903
- const tok = tokens[ti];
20933
+ for (let ti = 0; ti < tokens2.length; ti++) {
20934
+ const tok = tokens2[ti];
19904
20935
  if (!tok) continue;
19905
20936
  if (tok.startsWith("-")) {
19906
20937
  if (/^-[antpidu]$|^-N$/.test(tok)) ti++;
@@ -19925,10 +20956,10 @@ function findBashTaintSources(sourceCode, dfg) {
19925
20956
  const mapfileMatch = /^(?:mapfile|readarray)\s*\(/.test(trimmed) ? null : trimmed.match(/^(?:mapfile|readarray)\b([^#(]*)$/);
19926
20957
  if (mapfileMatch) {
19927
20958
  const argStr = mapfileMatch[1].trim();
19928
- const tokens = argStr.split(/\s+/);
20959
+ const tokens2 = argStr.split(/\s+/);
19929
20960
  const varNames = [];
19930
- for (let ti = 0; ti < tokens.length; ti++) {
19931
- const tok = tokens[ti];
20961
+ for (let ti = 0; ti < tokens2.length; ti++) {
20962
+ const tok = tokens2[ti];
19932
20963
  if (!tok) continue;
19933
20964
  if (tok.startsWith("-")) {
19934
20965
  if (/^-[cCnOsud]$/.test(tok)) ti++;
@@ -20313,6 +21344,30 @@ function findBashRealpathPrefixGuardSanitizers(code) {
20313
21344
  }
20314
21345
  return sanitizers;
20315
21346
  }
21347
+ function findBashVerifiedPackageDownloadSanitizers(code) {
21348
+ const lines = code.split("\n");
21349
+ if (!hasIntegrityVerifierAnywhere(lines)) {
21350
+ return [];
21351
+ }
21352
+ const hasDownload = lines.some((l) => /\b(curl|wget)\b/.test(l));
21353
+ const hasInstall = lines.some((l) => matchUnverifiedPackageInstall(l.trim()) !== null);
21354
+ if (!hasDownload || !hasInstall) {
21355
+ return [];
21356
+ }
21357
+ const sanitizers = [];
21358
+ for (let i2 = 0; i2 < lines.length; i2++) {
21359
+ const line = lines[i2];
21360
+ if (line.trimStart().startsWith("#")) continue;
21361
+ if (!/\b(curl|wget)\b/.test(line)) continue;
21362
+ sanitizers.push({
21363
+ type: "verified_package_download",
21364
+ method: "curl",
21365
+ line: i2 + 1,
21366
+ sanitizes: ["ssrf"]
21367
+ });
21368
+ }
21369
+ return sanitizers;
21370
+ }
20316
21371
  function findGoMapAllowlistGuardSanitizers(code) {
20317
21372
  const sanitizers = [];
20318
21373
  const lines = code.split("\n");
@@ -20468,6 +21523,160 @@ function findPythonNetlocAllowlistGuardSanitizers(code) {
20468
21523
  }
20469
21524
  return sanitizers;
20470
21525
  }
21526
+ function findJsNumericRangeCheckGuardSanitizers(code) {
21527
+ const sanitizers = [];
21528
+ const lines = code.split("\n");
21529
+ const coerceDecl = /^\s*(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:Number|parseInt)\s*\(/;
21530
+ const lowerGuard = /\bif\s*\(\s*!(?:Number\.)?isFinite\s*\(\s*([A-Za-z_]\w*)\s*\)|\bif\s*\([^)]*\b([A-Za-z_]\w*)\s*<=\s*0/;
21531
+ const upperGuard = /\bif\s*\(\s*([A-Za-z_]\w*)\s*>\s*(?:\d+|[A-Z][A-Z0-9_]+)\s*\)/;
21532
+ const terminator = /\b(?:return|throw|res\s*\.\s*status\s*\([^)]*\)\s*\.\s*(?:send|end|json))/;
21533
+ const coerced = /* @__PURE__ */ new Map();
21534
+ for (let i2 = 0; i2 < lines.length; i2++) {
21535
+ const m = coerceDecl.exec(lines[i2]);
21536
+ if (m) coerced.set(m[1], i2);
21537
+ }
21538
+ if (coerced.size === 0) return sanitizers;
21539
+ const state = /* @__PURE__ */ new Map();
21540
+ for (let i2 = 0; i2 < lines.length; i2++) {
21541
+ const line = lines[i2];
21542
+ let varName = null;
21543
+ let kind = null;
21544
+ const low = lowerGuard.exec(line);
21545
+ if (low) {
21546
+ varName = low[1] ?? low[2];
21547
+ kind = "lower";
21548
+ } else {
21549
+ const up = upperGuard.exec(line);
21550
+ if (up) {
21551
+ varName = up[1];
21552
+ kind = "upper";
21553
+ }
21554
+ }
21555
+ if (!varName || !kind || !coerced.has(varName)) continue;
21556
+ if (!terminator.test(line)) {
21557
+ let bodyHasTerminator = false;
21558
+ let blockEnd = i2;
21559
+ let braceDepth = 0;
21560
+ let started = false;
21561
+ const maxScan = Math.min(lines.length, i2 + 26);
21562
+ for (let j = i2; j < maxScan; j++) {
21563
+ const ln = lines[j];
21564
+ for (const ch of ln) {
21565
+ if (ch === "{") {
21566
+ braceDepth++;
21567
+ started = true;
21568
+ } else if (ch === "}") {
21569
+ braceDepth--;
21570
+ if (started && braceDepth === 0) {
21571
+ blockEnd = j;
21572
+ break;
21573
+ }
21574
+ }
21575
+ }
21576
+ if (j > i2 && terminator.test(ln)) bodyHasTerminator = true;
21577
+ if (started && braceDepth === 0 && blockEnd !== i2) break;
21578
+ }
21579
+ if (!bodyHasTerminator && !started) continue;
21580
+ if (!bodyHasTerminator) continue;
21581
+ }
21582
+ const st = state.get(varName) ?? { lower: false, upper: false, lastGuardEnd: i2 };
21583
+ if (kind === "lower") st.lower = true;
21584
+ else st.upper = true;
21585
+ st.lastGuardEnd = i2;
21586
+ state.set(varName, st);
21587
+ }
21588
+ for (const [varName, st] of state) {
21589
+ if (!st.lower || !st.upper) continue;
21590
+ const varRefRe = new RegExp(`\\b${varName}\\b`);
21591
+ for (let l = st.lastGuardEnd + 1; l < lines.length; l++) {
21592
+ if (!varRefRe.test(lines[l])) continue;
21593
+ sanitizers.push({
21594
+ type: "js_numeric_range_check_guard",
21595
+ method: "if",
21596
+ line: l + 1,
21597
+ sanitizes: ["xss", "external_taint_escape"]
21598
+ });
21599
+ }
21600
+ }
21601
+ return sanitizers;
21602
+ }
21603
+ function findRustNumericRangeCheckGuardSanitizers(code) {
21604
+ const sanitizers = [];
21605
+ const lines = code.split("\n");
21606
+ const coerceDecl = /^\s*let\s+([A-Za-z_]\w*)\s*(?::\s*(?:i\d+|u\d+|isize|usize))?\s*=\s*[^;]*\.parse\s*(?:\(\s*\)|::<)/;
21607
+ const lowerGuard = /\bif\s+([A-Za-z_]\w*)\s*<=\s*0\b/;
21608
+ const upperGuard = /\bif\s+([A-Za-z_]\w*)\s*>\s*(?:\d+|[A-Z][A-Z0-9_]+)\b/;
21609
+ const terminator = /\b(?:return\s+HttpResponse::|return\s+StatusCode::|return\s+Err\s*\(|panic!\s*\()/;
21610
+ const coerced = /* @__PURE__ */ new Map();
21611
+ for (let i2 = 0; i2 < lines.length; i2++) {
21612
+ const m = coerceDecl.exec(lines[i2]);
21613
+ if (m) coerced.set(m[1], i2);
21614
+ }
21615
+ if (coerced.size === 0) return sanitizers;
21616
+ const state = /* @__PURE__ */ new Map();
21617
+ for (let i2 = 0; i2 < lines.length; i2++) {
21618
+ const line = lines[i2];
21619
+ let varName = null;
21620
+ let kind = null;
21621
+ const low = lowerGuard.exec(line);
21622
+ if (low) {
21623
+ varName = low[1];
21624
+ kind = "lower";
21625
+ } else {
21626
+ const up = upperGuard.exec(line);
21627
+ if (up) {
21628
+ varName = up[1];
21629
+ kind = "upper";
21630
+ }
21631
+ }
21632
+ if (!varName || !kind || !coerced.has(varName)) continue;
21633
+ if (!terminator.test(line)) {
21634
+ let bodyHasTerminator = false;
21635
+ let blockEnd = i2;
21636
+ let braceDepth = 0;
21637
+ let started = false;
21638
+ const maxScan = Math.min(lines.length, i2 + 26);
21639
+ for (let j = i2; j < maxScan; j++) {
21640
+ const ln = lines[j];
21641
+ for (const ch of ln) {
21642
+ if (ch === "{") {
21643
+ braceDepth++;
21644
+ started = true;
21645
+ } else if (ch === "}") {
21646
+ braceDepth--;
21647
+ if (started && braceDepth === 0) {
21648
+ blockEnd = j;
21649
+ break;
21650
+ }
21651
+ }
21652
+ }
21653
+ if (j > i2 && terminator.test(ln)) bodyHasTerminator = true;
21654
+ if (started && braceDepth === 0 && blockEnd !== i2) break;
21655
+ }
21656
+ if (!bodyHasTerminator && !started) continue;
21657
+ if (!bodyHasTerminator) continue;
21658
+ }
21659
+ const st = state.get(varName) ?? { lower: false, upper: false, lastGuardEnd: i2 };
21660
+ if (kind === "lower") st.lower = true;
21661
+ else st.upper = true;
21662
+ st.lastGuardEnd = i2;
21663
+ state.set(varName, st);
21664
+ }
21665
+ for (const [varName, st] of state) {
21666
+ if (!st.lower || !st.upper) continue;
21667
+ const varRefRe = new RegExp(`\\b${varName}\\b`);
21668
+ for (let l = st.lastGuardEnd + 1; l < lines.length; l++) {
21669
+ if (!varRefRe.test(lines[l])) continue;
21670
+ sanitizers.push({
21671
+ type: "rust_numeric_range_check_guard",
21672
+ method: "if",
21673
+ line: l + 1,
21674
+ sanitizes: ["xss", "external_taint_escape"]
21675
+ });
21676
+ }
21677
+ }
21678
+ return sanitizers;
21679
+ }
20471
21680
  function findPythonRangeCheckGuardSanitizers(code) {
20472
21681
  const sanitizers = [];
20473
21682
  const lines = code.split("\n");
@@ -21054,6 +22263,127 @@ function findPythonDefusedXmlSanitizers(code) {
21054
22263
  }
21055
22264
  return sanitizers;
21056
22265
  }
22266
+ function findRustPatInfoHttpOnlyResponseSanitizers(code) {
22267
+ const sanitizers = [];
22268
+ const lines = code.split("\n");
22269
+ const bodyFormatRe = /HttpResponse::\w+\(\)\s*\.\s*body\s*\(\s*format!\s*\(/;
22270
+ const envBindRe = /(?:^|[^:])env::var\s*\(|std::env::/;
22271
+ const httpGetRe = /\.get\s*\(\s*"[^"]+"\s*\)/;
22272
+ const letVarRe = (v) => new RegExp(String.raw`^\s*let\s+(?:mut\s+)?${v}\s*=`);
22273
+ for (let i2 = 0; i2 < lines.length; i2++) {
22274
+ const sinkLine = lines[i2];
22275
+ if (!bodyFormatRe.test(sinkLine)) continue;
22276
+ if (envBindRe.test(sinkLine)) continue;
22277
+ const varRefs = [
22278
+ ...sinkLine.matchAll(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g)
22279
+ ].map((m) => m[1]);
22280
+ if (varRefs.length === 0) continue;
22281
+ let allHttpOnly = true;
22282
+ for (const v of varRefs) {
22283
+ let sawBinding = false;
22284
+ let boundEnv = false;
22285
+ let boundHttp = false;
22286
+ for (let j = i2 - 1; j >= Math.max(0, i2 - 24); j--) {
22287
+ const line = lines[j];
22288
+ if (!letVarRe(v).test(line)) continue;
22289
+ sawBinding = true;
22290
+ if (envBindRe.test(line)) boundEnv = true;
22291
+ if (httpGetRe.test(line)) boundHttp = true;
22292
+ break;
22293
+ }
22294
+ if (!sawBinding || boundEnv || !boundHttp) {
22295
+ allHttpOnly = false;
22296
+ break;
22297
+ }
22298
+ }
22299
+ if (!allHttpOnly) continue;
22300
+ sanitizers.push({
22301
+ type: "rust_pat_info_http_only_body",
22302
+ method: "format",
22303
+ line: i2 + 1,
22304
+ sanitizes: [
22305
+ "xss",
22306
+ "external_taint_escape",
22307
+ "open_redirect",
22308
+ "log_injection"
22309
+ ]
22310
+ });
22311
+ }
22312
+ return sanitizers;
22313
+ }
22314
+ function collectRustConstStrSliceNames(code) {
22315
+ const names = /* @__PURE__ */ new Set();
22316
+ const re = /^\s*const\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*\[\s*&\s*str\s*\]\s*=\s*&\s*\[([^\]]*)\]\s*;/;
22317
+ for (const line of code.split("\n")) {
22318
+ const m = re.exec(line);
22319
+ if (!m) continue;
22320
+ const inner = m[2].trim();
22321
+ if (!inner) {
22322
+ names.add(m[1]);
22323
+ continue;
22324
+ }
22325
+ const elems = inner.split(",").map((s) => s.trim()).filter(Boolean);
22326
+ if (elems.every((e) => /^"(?:[^"\\]|\\.)*"$/.test(e))) names.add(m[1]);
22327
+ }
22328
+ return names;
22329
+ }
22330
+ function findRustConstSliceMembershipGuardSanitizers(code) {
22331
+ const sanitizers = [];
22332
+ const slices = collectRustConstStrSliceNames(code);
22333
+ if (slices.size === 0) return sanitizers;
22334
+ const lines = code.split("\n");
22335
+ const guardOpen = /^\s*if\s+!\s*([A-Z][A-Z0-9_]*)\s*\.\s*contains\s*\(\s*&\s*([A-Za-z_]\w*)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)\s*\{?\s*$/;
22336
+ const terminator = /\b(?:return\s+HttpResponse::|return\s+StatusCode::|return\s+Err\s*\(|panic!\s*\()/;
22337
+ for (let i2 = 0; i2 < lines.length; i2++) {
22338
+ const m = guardOpen.exec(lines[i2]);
22339
+ if (!m) continue;
22340
+ const sliceName = m[1];
22341
+ const guardedVar = m[2];
22342
+ if (!slices.has(sliceName)) continue;
22343
+ let depth = 0;
22344
+ for (const ch of lines[i2]) {
22345
+ if (ch === "{") depth++;
22346
+ else if (ch === "}") depth--;
22347
+ }
22348
+ let closeLine = i2;
22349
+ let bodyHasTerminator = terminator.test(lines[i2]);
22350
+ if (depth > 0) {
22351
+ for (let j = i2 + 1; j < Math.min(lines.length, i2 + 26); j++) {
22352
+ if (terminator.test(lines[j])) bodyHasTerminator = true;
22353
+ for (const ch of lines[j]) {
22354
+ if (ch === "{") depth++;
22355
+ else if (ch === "}") depth--;
22356
+ }
22357
+ if (depth === 0) {
22358
+ closeLine = j;
22359
+ break;
22360
+ }
22361
+ }
22362
+ }
22363
+ if (!bodyHasTerminator) continue;
22364
+ const varRe = new RegExp(String.raw`\b${guardedVar}\b`);
22365
+ const reassign = new RegExp(String.raw`^\s*(?:let\s+)?(?:mut\s+)?${guardedVar}\s*=(?!=)`);
22366
+ for (let l = closeLine + 2; l <= lines.length; l++) {
22367
+ const lineText = lines[l - 1];
22368
+ if (!lineText || !varRe.test(lineText)) continue;
22369
+ if (reassign.test(lineText)) break;
22370
+ sanitizers.push({
22371
+ type: "rust_const_slice_membership_guard",
22372
+ method: "if",
22373
+ line: l,
22374
+ sanitizes: [
22375
+ "trust_boundary",
22376
+ "external_taint_escape",
22377
+ "xss",
22378
+ "open_redirect",
22379
+ "ssrf",
22380
+ "command_injection"
22381
+ ]
22382
+ });
22383
+ }
22384
+ }
22385
+ return sanitizers;
22386
+ }
21057
22387
  function findRustSetAllowlistGuardSanitizers(code) {
21058
22388
  const sanitizers = [];
21059
22389
  const lines = code.split("\n");
@@ -21210,6 +22540,13 @@ function findJsCsvFormulaPrefixSanitizers(code) {
21210
22540
  }
21211
22541
  return sanitizers;
21212
22542
  }
22543
+ function jsWrapperBodyReplaceChainText(body2) {
22544
+ const returnM = /\breturn\s+([^;}\n]+)/.exec(body2);
22545
+ if (returnM) return returnM[1].trim();
22546
+ const arrowM = /=>\s*([^;}\n]+)/.exec(body2);
22547
+ if (arrowM) return arrowM[1].trim();
22548
+ return null;
22549
+ }
21213
22550
  function findJsWrapperFunctionSanitizers(code) {
21214
22551
  const sanitizers = [];
21215
22552
  const lines = code.split("\n");
@@ -21229,8 +22566,12 @@ function findJsWrapperFunctionSanitizers(code) {
21229
22566
  kinds.add("external_taint_escape");
21230
22567
  }
21231
22568
  if (crlfCharClass.test(body2)) {
21232
- kinds.add("log_injection");
21233
- kinds.add("external_taint_escape");
22569
+ const chainText = jsWrapperBodyReplaceChainText(body2);
22570
+ const verdict = chainText ? jsReplaceChainVerdict(chainText) : "undecided";
22571
+ if (verdict !== "leaves-crlf") {
22572
+ kinds.add("log_injection");
22573
+ kinds.add("external_taint_escape");
22574
+ }
21234
22575
  }
21235
22576
  if (kinds.size > 0) wrappers.set(name2, kinds);
21236
22577
  }
@@ -22032,6 +23373,74 @@ function javaBodyStripsCrAndLf(body2) {
22032
23373
  }
22033
23374
  return cr && lf;
22034
23375
  }
23376
+ function javaBodyStripsTemplateDelimiters(body2) {
23377
+ let open = false;
23378
+ let close = false;
23379
+ for (const m of body2.matchAll(/\.\s*(replace|replaceAll)\s*\(\s*(?:"((?:\\.|[^"\\])*)"|'(\\.|[^'\\])')/g)) {
23380
+ const lit = m[2] ?? m[3] ?? "";
23381
+ if (lit === "{") open = true;
23382
+ if (lit === "}") close = true;
23383
+ }
23384
+ return open && close;
23385
+ }
23386
+ function findJavaTemplateDelimiterWrapperSanitizers(code) {
23387
+ const sanitizers = [];
23388
+ const lines = code.split("\n");
23389
+ const declRe = /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:String|CharSequence)\s+([A-Za-z_]\w*)\s*\(/;
23390
+ const bodyWindow = 10;
23391
+ const wrappers = /* @__PURE__ */ new Set();
23392
+ for (let i2 = 0; i2 < lines.length; i2++) {
23393
+ const m = declRe.exec(lines[i2]);
23394
+ if (!m) continue;
23395
+ let end = Math.min(lines.length, i2 + bodyWindow);
23396
+ for (let j = i2 + 1; j < end; j++) {
23397
+ if (declRe.test(lines[j])) {
23398
+ end = j;
23399
+ break;
23400
+ }
23401
+ }
23402
+ const body2 = lines.slice(i2, end).join("\n");
23403
+ if (javaBodyStripsTemplateDelimiters(body2)) wrappers.add(m[1]);
23404
+ }
23405
+ if (wrappers.size === 0) return sanitizers;
23406
+ const kinds = ["code_injection", "external_taint_escape"];
23407
+ for (const name2 of wrappers) {
23408
+ const assignedCallRe = new RegExp(
23409
+ `\\b([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*${name2}\\s*\\(`
23410
+ );
23411
+ const bareCallRe = new RegExp(`\\b${name2}\\s*\\(`);
23412
+ for (let i2 = 0; i2 < lines.length; i2++) {
23413
+ if (!bareCallRe.test(lines[i2])) continue;
23414
+ if (declRe.test(lines[i2]) && bareCallRe.test(lines[i2])) continue;
23415
+ sanitizers.push({
23416
+ type: "java_wrapper_template_delim_strip",
23417
+ method: name2,
23418
+ line: i2 + 1,
23419
+ sanitizes: [...kinds]
23420
+ });
23421
+ const am = assignedCallRe.exec(lines[i2]);
23422
+ if (!am) continue;
23423
+ const callIndent = lines[i2].length - lines[i2].trimStart().length;
23424
+ const validated = am[1];
23425
+ const validatedRe = new RegExp(`\\b${validated}\\b`);
23426
+ for (let j = i2 + 1; j < lines.length; j++) {
23427
+ const jline = lines[j];
23428
+ if (jline.trim() === "") continue;
23429
+ const jindent = jline.length - jline.trimStart().length;
23430
+ if (jindent < callIndent) break;
23431
+ if (validatedRe.test(jline)) {
23432
+ sanitizers.push({
23433
+ type: "java_wrapper_template_delim_strip",
23434
+ method: name2,
23435
+ line: j + 1,
23436
+ sanitizes: [...kinds]
23437
+ });
23438
+ }
23439
+ }
23440
+ }
23441
+ }
23442
+ return sanitizers;
23443
+ }
22035
23444
  function findCrlfWrapperFunctionSanitizers(code, language) {
22036
23445
  const sanitizers = [];
22037
23446
  const lines = code.split("\n");
@@ -24447,6 +25856,79 @@ function findJsLdapInjectionFindings(code, file) {
24447
25856
  snippet: line.trim()
24448
25857
  });
24449
25858
  }
25859
+ const positionalSearchRe = /\.\s*search\s*\(\s*(?:'[^']*'|"[^"]*")\s*,\s*([^,{]+)/;
25860
+ for (let i2 = 0; i2 < lines.length; i2++) {
25861
+ const line = lines[i2];
25862
+ const pm = line.match(positionalSearchRe);
25863
+ if (!pm) continue;
25864
+ const expr = pm[1].trim();
25865
+ if (expr.startsWith("{")) continue;
25866
+ let tainted = false;
25867
+ for (const v of taintedVars) {
25868
+ if (new RegExp(`\\b${v}\\b`).test(expr)) {
25869
+ tainted = true;
25870
+ break;
25871
+ }
25872
+ }
25873
+ if (!tainted) continue;
25874
+ const lineNo = i2 + 1;
25875
+ if (seen.has(lineNo)) continue;
25876
+ seen.add(lineNo);
25877
+ findings.push({
25878
+ id: `ldap_injection-${file}-${lineNo}-js-ldap-positional-filter`,
25879
+ pass: "language-sources",
25880
+ category: "security",
25881
+ rule_id: "ldap_injection",
25882
+ cwe: "CWE-90",
25883
+ severity: "critical",
25884
+ level: "error",
25885
+ message: "LDAP injection: ldapjs/ldapts `client.search(base, <tainted-filter>, ...)` lets the attacker break out of the filter expression. Escape user input or use a structured filter builder.",
25886
+ file,
25887
+ line: lineNo,
25888
+ snippet: line.trim()
25889
+ });
25890
+ }
25891
+ return findings;
25892
+ }
25893
+ function findJsZipHeaderFailopenFindings(code, file) {
25894
+ const findings = [];
25895
+ if (typeof code !== "string" || code.length === 0) return findings;
25896
+ const lines = code.split("\n");
25897
+ const hasPkLocalSig = lines.some((l) => {
25898
+ const t = l.trim();
25899
+ if (t.startsWith("//")) return false;
25900
+ return /0x50/.test(t) && /0x4[bB]/.test(t) && /0x03/.test(t) && /0x04/.test(t);
25901
+ });
25902
+ if (!hasPkLocalSig) return findings;
25903
+ const seen = /* @__PURE__ */ new Set();
25904
+ for (let i2 = 0; i2 < lines.length; i2++) {
25905
+ const trimmed = lines[i2].trim();
25906
+ if (trimmed.startsWith("//")) continue;
25907
+ const failBranch = trimmed.match(/^if\s*\(\s*!(\w+)\s*\)\s*(.+)$/);
25908
+ if (!failBranch) continue;
25909
+ const tail = failBranch[2];
25910
+ if (/\bthrow\b/.test(tail)) continue;
25911
+ if (!/\breturn\b/.test(tail)) continue;
25912
+ if (!/\.(subarray|slice)\s*\(/.test(tail) && !/Buffer\.from\s*\(/.test(tail)) {
25913
+ continue;
25914
+ }
25915
+ const lineNo = i2 + 1;
25916
+ if (seen.has(lineNo)) continue;
25917
+ seen.add(lineNo);
25918
+ findings.push({
25919
+ id: `input_validation-${file}-${lineNo}-js-zip-header-failopen`,
25920
+ pass: "language-sources",
25921
+ category: "security",
25922
+ rule_id: "input_validation",
25923
+ cwe: "CWE-20",
25924
+ severity: "high",
25925
+ level: "error",
25926
+ message: "Input validation fail-open: an invalid ZIP local header check still returns a slice of the caller-controlled buffer. On parse failure, throw or return empty \u2014 do not pass through untrusted bytes.",
25927
+ file,
25928
+ line: lineNo,
25929
+ snippet: trimmed.substring(0, 120)
25930
+ });
25931
+ }
24450
25932
  return findings;
24451
25933
  }
24452
25934
  function findGoLdapInjectionFindings(code, file) {
@@ -24949,6 +26431,40 @@ function findGoGobDeserializationFindings(code, file) {
24949
26431
  }
24950
26432
  return findings;
24951
26433
  }
26434
+ function maskJsLiteralsAndComments(code) {
26435
+ const out2 = code.split("");
26436
+ let i2 = 0;
26437
+ const blank = (from, to) => {
26438
+ for (let k = from; k < to; k++) if (out2[k] !== "\n") out2[k] = " ";
26439
+ };
26440
+ while (i2 < code.length) {
26441
+ const c = code[i2];
26442
+ const n = code[i2 + 1];
26443
+ if (c === "/" && n === "/") {
26444
+ let j = i2;
26445
+ while (j < code.length && code[j] !== "\n") j++;
26446
+ blank(i2, j);
26447
+ i2 = j;
26448
+ } else if (c === "/" && n === "*") {
26449
+ const end = code.indexOf("*/", i2 + 2);
26450
+ const j = end < 0 ? code.length : end + 2;
26451
+ blank(i2, j);
26452
+ i2 = j;
26453
+ } else if (c === '"' || c === "'" || c === "`") {
26454
+ let j = i2 + 1;
26455
+ while (j < code.length && code[j] !== c) {
26456
+ if (code[j] === "\\") j++;
26457
+ else if (c !== "`" && code[j] === "\n") break;
26458
+ j++;
26459
+ }
26460
+ blank(i2 + 1, Math.min(j, code.length));
26461
+ i2 = j + 1;
26462
+ } else {
26463
+ i2++;
26464
+ }
26465
+ }
26466
+ return out2.join("");
26467
+ }
24952
26468
  function findJsJsonParseBodyFindings(code, file) {
24953
26469
  const findings = [];
24954
26470
  if (typeof code !== "string" || code.length === 0) return findings;
@@ -24982,12 +26498,13 @@ function findJsJsonParseBodyFindings(code, file) {
24982
26498
  }
24983
26499
  const callRe = /\bJSON\s*\.\s*parse\s*\(\s*([^)]+?)\s*\)/g;
24984
26500
  const seen = /* @__PURE__ */ new Set();
26501
+ const codeLines = maskJsLiteralsAndComments(code).split("\n");
24985
26502
  for (let i2 = 0; i2 < lines.length; i2++) {
24986
26503
  const line = lines[i2];
24987
- if (line.trim().startsWith("//")) continue;
26504
+ const codeLine = codeLines[i2] ?? "";
24988
26505
  callRe.lastIndex = 0;
24989
26506
  let m;
24990
- while ((m = callRe.exec(line)) !== null) {
26507
+ while ((m = callRe.exec(codeLine)) !== null) {
24991
26508
  const arg = m[1];
24992
26509
  let tainted = reqBodyRe.test(arg);
24993
26510
  if (!tainted) {
@@ -25407,198 +26924,6 @@ function findJavaSpringReturnXSSSinks(code, types, sources, xssSanitizerLines) {
25407
26924
  return out2;
25408
26925
  }
25409
26926
 
25410
- // src/analysis/js-crlf-strip.ts
25411
- var MAX_PATTERN_LENGTH = 200;
25412
- var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
25413
- "replace",
25414
- "replaceAll",
25415
- "trim",
25416
- "trimStart",
25417
- "trimEnd",
25418
- "toString",
25419
- "toLowerCase",
25420
- "toUpperCase",
25421
- "slice",
25422
- "substring"
25423
- ]);
25424
- var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
25425
- function readQuoted(s, i2) {
25426
- const q = s[i2];
25427
- if (q !== '"' && q !== "'" && q !== "`") return null;
25428
- let out2 = "";
25429
- for (let j = i2 + 1; j < s.length; j++) {
25430
- const c = s[j];
25431
- if (c === q) return { value: out2, end: j + 1 };
25432
- if (q === "`" && c === "$" && s[j + 1] === "{") return null;
25433
- if (c === "\\") {
25434
- const n = s[j + 1];
25435
- if (n === void 0) return null;
25436
- switch (n) {
25437
- case "n":
25438
- out2 += "\n";
25439
- j++;
25440
- continue;
25441
- case "r":
25442
- out2 += "\r";
25443
- j++;
25444
- continue;
25445
- case "t":
25446
- out2 += " ";
25447
- j++;
25448
- continue;
25449
- case "0":
25450
- out2 += "\0";
25451
- j++;
25452
- continue;
25453
- case "x": {
25454
- const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
25455
- if (!h) return null;
25456
- out2 += String.fromCharCode(parseInt(h[0], 16));
25457
- j += 3;
25458
- continue;
25459
- }
25460
- case "u": {
25461
- const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
25462
- if (!h) return null;
25463
- out2 += String.fromCharCode(parseInt(h[0], 16));
25464
- j += 5;
25465
- continue;
25466
- }
25467
- default:
25468
- out2 += n;
25469
- j++;
25470
- continue;
25471
- }
25472
- }
25473
- if (c === "\n" && q !== "`") return null;
25474
- out2 += c;
25475
- }
25476
- return null;
25477
- }
25478
- function readRegexLiteral(s, i2) {
25479
- if (s[i2] !== "/") return null;
25480
- let inClass = false;
25481
- for (let j = i2 + 1; j < s.length; j++) {
25482
- const c = s[j];
25483
- if (c === "\n") return null;
25484
- if (c === "\\") {
25485
- j++;
25486
- continue;
25487
- }
25488
- if (inClass) {
25489
- if (c === "]") inClass = false;
25490
- continue;
25491
- }
25492
- if (c === "[") {
25493
- inClass = true;
25494
- continue;
25495
- }
25496
- if (c === "/") {
25497
- const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
25498
- return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
25499
- }
25500
- }
25501
- return null;
25502
- }
25503
- var skipWs = (s, i2) => {
25504
- while (i2 < s.length && /\s/.test(s[i2])) i2++;
25505
- return i2;
25506
- };
25507
- function parseSteps(chainText) {
25508
- const steps = [];
25509
- const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
25510
- let m;
25511
- while ((m = callRe.exec(chainText)) !== null) {
25512
- const kind = m[1];
25513
- let i2 = skipWs(chainText, m.index + m[0].length);
25514
- let pattern;
25515
- const rx = readRegexLiteral(chainText, i2);
25516
- if (rx) {
25517
- if (rx.source.length > MAX_PATTERN_LENGTH) return null;
25518
- try {
25519
- pattern = new RegExp(rx.source, rx.flags);
25520
- } catch {
25521
- return null;
25522
- }
25523
- i2 = rx.end;
25524
- } else {
25525
- const str = readQuoted(chainText, i2);
25526
- if (!str) return null;
25527
- pattern = str.value;
25528
- i2 = str.end;
25529
- }
25530
- i2 = skipWs(chainText, i2);
25531
- if (chainText[i2] !== ",") return null;
25532
- i2 = skipWs(chainText, i2 + 1);
25533
- const rep = readQuoted(chainText, i2);
25534
- if (!rep) return null;
25535
- i2 = skipWs(chainText, rep.end);
25536
- if (chainText[i2] !== ")") return null;
25537
- steps.push({ kind, pattern, replacement: rep.value });
25538
- callRe.lastIndex = i2 + 1;
25539
- }
25540
- return steps;
25541
- }
25542
- function run2(steps, input) {
25543
- let s = input;
25544
- try {
25545
- for (const st of steps) {
25546
- if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
25547
- s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
25548
- }
25549
- } catch {
25550
- return null;
25551
- }
25552
- return s;
25553
- }
25554
- function jsReplaceChainVerdict(chainText) {
25555
- const steps = parseSteps(chainText);
25556
- if (!steps || steps.length === 0) return "undecided";
25557
- const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
25558
- for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
25559
- if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
25560
- }
25561
- const out2 = run2(steps, PROBE);
25562
- if (out2 === null) return "undecided";
25563
- return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
25564
- }
25565
- function jsChainTextAt(sourceLines, line, column) {
25566
- const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
25567
- let depth = 0;
25568
- let prevSig = "";
25569
- let i2 = 0;
25570
- for (; i2 < text.length; i2++) {
25571
- const c = text[i2];
25572
- if (c === '"' || c === "'" || c === "`") {
25573
- for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
25574
- prevSig = c;
25575
- continue;
25576
- }
25577
- if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
25578
- const rx = readRegexLiteral(text, i2);
25579
- if (rx) {
25580
- i2 = rx.end - 1;
25581
- prevSig = "/";
25582
- continue;
25583
- }
25584
- }
25585
- if (c === "(" || c === "[" || c === "{") {
25586
- depth++;
25587
- prevSig = c;
25588
- continue;
25589
- }
25590
- if (c === ")" || c === "]" || c === "}") {
25591
- if (depth === 0) break;
25592
- depth--;
25593
- prevSig = c;
25594
- continue;
25595
- }
25596
- if (depth === 0 && !/[\w$.\s]/.test(c)) break;
25597
- if (!/\s/.test(c)) prevSig = c;
25598
- }
25599
- return text.slice(0, i2);
25600
- }
25601
-
25602
26927
  // src/analysis/constant-propagation/patterns.ts
25603
26928
  var TAINT_PATTERNS = [
25604
26929
  // HTTP Sources (Servlet API)
@@ -25996,13 +27321,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
25996
27321
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
25997
27322
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
25998
27323
  const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
25999
- let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
27324
+ let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
27325
+ sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
26000
27326
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
26001
27327
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
26002
27328
  const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
26003
27329
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
26004
27330
  return { sources, sinks: gatedSinks, sanitizers };
26005
27331
  }
27332
+ function isBoundParameterCall(call, pattern, sourceLines) {
27333
+ if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
27334
+ const first = call.arguments[0]?.expression?.trim() ?? "";
27335
+ if (first.startsWith("[") || first.startsWith("{")) return true;
27336
+ const receiver = call.receiver;
27337
+ if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
27338
+ const assignedFromPrepare = new RegExp(
27339
+ `\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
27340
+ );
27341
+ return sourceLines.some((line) => assignedFromPrepare.test(line));
27342
+ }
27343
+ function sinkPatternImportApplies(pattern, imports) {
27344
+ const required = pattern.requires_import;
27345
+ if (!required || required.length === 0) return true;
27346
+ if (!imports || imports.length === 0) return false;
27347
+ return imports.some((imp) => {
27348
+ const from = imp.from_package;
27349
+ if (!from) return false;
27350
+ return required.some((m) => from === m || from.startsWith(m + "/"));
27351
+ });
27352
+ }
26006
27353
  function sinkPatternAppliesTo(pattern, language) {
26007
27354
  if (language === void 0) return true;
26008
27355
  if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
@@ -26404,6 +27751,9 @@ function isInterproceduralTaintableType(typeName, language) {
26404
27751
  "IEnumerable",
26405
27752
  "ICollection",
26406
27753
  "IList",
27754
+ "List",
27755
+ "Hashtable",
27756
+ "ArrayList",
26407
27757
  "Dictionary",
26408
27758
  "IDictionary",
26409
27759
  "IReadOnlyList",
@@ -26417,6 +27767,46 @@ function isInterproceduralTaintableType(typeName, language) {
26417
27767
  const leaf = baseType.split(".").pop() ?? baseType;
26418
27768
  if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
26419
27769
  }
27770
+ if (language === "rust") {
27771
+ const trimmed = typeName.trim();
27772
+ const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
27773
+ const rustBase = unref.split("<")[0].trim();
27774
+ const rustScalars = [
27775
+ "String",
27776
+ "str",
27777
+ "bool",
27778
+ "char",
27779
+ "i8",
27780
+ "i16",
27781
+ "i32",
27782
+ "i64",
27783
+ "i128",
27784
+ "isize",
27785
+ "u8",
27786
+ "u16",
27787
+ "u32",
27788
+ "u64",
27789
+ "u128",
27790
+ "usize",
27791
+ "f32",
27792
+ "f64"
27793
+ ];
27794
+ if (rustScalars.includes(rustBase)) return true;
27795
+ if (rustBase === "()" || rustBase === "!") return false;
27796
+ if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
27797
+ return true;
27798
+ }
27799
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
27800
+ }
27801
+ if (language === "javascript" || language === "typescript") {
27802
+ const trimmed = typeName.trim();
27803
+ const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
27804
+ if (jsScalars.includes(baseType)) return true;
27805
+ if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
27806
+ if (/\bRecord\s*</.test(trimmed)) return true;
27807
+ if (["void", "undefined", "null", "never"].includes(baseType)) return false;
27808
+ if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
27809
+ }
26420
27810
  if (typeName.endsWith("[]")) {
26421
27811
  const elementType = typeName.slice(0, -2);
26422
27812
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -26531,6 +27921,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
26531
27921
  return false;
26532
27922
  }
26533
27923
  }
27924
+ if (program === "git") {
27925
+ const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
27926
+ const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
27927
+ let subcommand;
27928
+ if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
27929
+ subcommand = String(subcommandArg.literal);
27930
+ } else {
27931
+ const expr = (subcommandArg?.expression ?? "").trim();
27932
+ if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
27933
+ subcommand = expr.slice(1, -1);
27934
+ }
27935
+ }
27936
+ if (subcommand === "ls-remote") {
27937
+ const hasEndOfOptions = call.arguments.some((a) => {
27938
+ if (a.literal !== null && a.literal !== void 0) {
27939
+ return String(a.literal) === "--";
27940
+ }
27941
+ const expr = (a.expression ?? "").trim();
27942
+ return expr === '"--"' || expr === "'--'" || expr === "`--`";
27943
+ });
27944
+ if (!hasEndOfOptions) {
27945
+ return false;
27946
+ }
27947
+ }
27948
+ }
26534
27949
  return true;
26535
27950
  }
26536
27951
  function isSafeJSChildProcessCall(call, pattern, language) {
@@ -26934,6 +28349,29 @@ function isRegexReceiver(receiver, sourceLines) {
26934
28349
  const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
26935
28350
  return sourceLines.some((l) => decl.test(l));
26936
28351
  }
28352
+ function isNavigationReceiverName(receiver) {
28353
+ const r = (receiver ?? "").trim();
28354
+ if (!r) return false;
28355
+ const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
28356
+ return /router|history|navigat|^nav$/i.test(last);
28357
+ }
28358
+ function isInMemoryMapGetReceiver(receiver, sourceLines) {
28359
+ const raw = (receiver ?? "").trim();
28360
+ if (!raw || !sourceLines) return false;
28361
+ if (/\.properties$/i.test(raw)) return true;
28362
+ const lastDot = raw.lastIndexOf(".");
28363
+ const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
28364
+ if (/^(properties|memoryCache)$/i.test(r)) return true;
28365
+ if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
28366
+ const n = escapeRe(r);
28367
+ const mapCtor = new RegExp(
28368
+ `\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
28369
+ );
28370
+ const mapType = new RegExp(
28371
+ `\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
28372
+ );
28373
+ return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
28374
+ }
26937
28375
  function isDeclaredFunctionReference(arg, sourceLines) {
26938
28376
  if (arg.literal !== null && arg.literal !== void 0) return false;
26939
28377
  if (!sourceLines) return false;
@@ -27138,6 +28576,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27138
28576
  if (isParameterizedQueryCall(call, pattern)) {
27139
28577
  continue;
27140
28578
  }
28579
+ if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
28580
+ continue;
28581
+ }
28582
+ if (isBoundParameterCall(call, pattern, sourceLines)) {
28583
+ continue;
28584
+ }
27141
28585
  if (isSafePythonSubprocessCall(call, pattern, language)) {
27142
28586
  continue;
27143
28587
  }
@@ -27195,9 +28639,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27195
28639
  if (isArgumentlessJsResponseWriter(call, pattern, language)) {
27196
28640
  continue;
27197
28641
  }
28642
+ 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)) {
28643
+ continue;
28644
+ }
27198
28645
  if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
27199
28646
  continue;
27200
28647
  }
28648
+ if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
28649
+ continue;
28650
+ }
27201
28651
  if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
27202
28652
  continue;
27203
28653
  }
@@ -27214,7 +28664,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27214
28664
  continue;
27215
28665
  }
27216
28666
  const location = formatCallLocation(call);
27217
- const key = `${location}:${call.location.line}:${pattern.cwe}`;
28667
+ const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
27218
28668
  const confidence = calculateSinkConfidence(call, pattern);
27219
28669
  const existing = sinkMap.get(key);
27220
28670
  if (!existing || confidence > existing.confidence) {
@@ -27225,6 +28675,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27225
28675
  cwe: pattern.cwe,
27226
28676
  location,
27227
28677
  line: call.location.line,
28678
+ callColumn: call.location.column,
27228
28679
  confidence,
27229
28680
  method: call.method_name,
27230
28681
  argPositions: pattern.arg_positions,
@@ -27262,6 +28713,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
27262
28713
  return false;
27263
28714
  }
27264
28715
  }
28716
+ if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
28717
+ const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
28718
+ if (expressRequestAccessors.has(pattern.method)) {
28719
+ const recv = call.receiver?.toLowerCase() ?? "";
28720
+ if (recv !== "req" && recv !== "request" && recv !== "ctx") {
28721
+ return false;
28722
+ }
28723
+ }
28724
+ }
27265
28725
  return pattern.return_tainted === true;
27266
28726
  }
27267
28727
  return false;
@@ -27355,6 +28815,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
27355
28815
  "database",
27356
28816
  "sqlite",
27357
28817
  "conn",
28818
+ "con",
27358
28819
  "connection",
27359
28820
  "client",
27360
28821
  "pool",
@@ -27631,7 +29092,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
27631
29092
  }
27632
29093
  }
27633
29094
  }
27634
- if (!pattern.class && call.receiver) {
29095
+ if (!pattern.class && call.receiver && !pattern.requires_import) {
27635
29096
  if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
27636
29097
  return false;
27637
29098
  }
@@ -28364,6 +29825,8 @@ function javaReplaceChainCredit(chainText) {
28364
29825
  let lf = false;
28365
29826
  let lt = false;
28366
29827
  let gt = false;
29828
+ let openBrace = false;
29829
+ let closeBrace = false;
28367
29830
  for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
28368
29831
  const kind = m[1];
28369
29832
  const rawFrom = m[2] ?? m[3] ?? "";
@@ -28374,10 +29837,13 @@ function javaReplaceChainCredit(chainText) {
28374
29837
  if (cov.lf) lf = true;
28375
29838
  if (from === "<" && to.startsWith("&")) lt = true;
28376
29839
  if (from === ">" && to.startsWith("&")) gt = true;
29840
+ if (from === "{") openBrace = true;
29841
+ if (from === "}") closeBrace = true;
28377
29842
  }
28378
29843
  const out2 = [];
28379
29844
  if (cr && lf) out2.push("log_injection");
28380
29845
  if (lt && gt) out2.push("xss");
29846
+ if (openBrace && closeBrace) out2.push("code_injection");
28381
29847
  return out2;
28382
29848
  }
28383
29849
  function matchesSanitizerPattern(call, pattern) {
@@ -30089,7 +31555,7 @@ var AnalysisPipeline = class {
30089
31555
  this.passes.push(pass);
30090
31556
  return this;
30091
31557
  }
30092
- run(graph, code, language, config, resources) {
31558
+ run(graph, code, language, config, resources, limit) {
30093
31559
  const results = /* @__PURE__ */ new Map();
30094
31560
  const findings = [];
30095
31561
  const context = {
@@ -30118,6 +31584,7 @@ var AnalysisPipeline = class {
30118
31584
  };
30119
31585
  const timingEnabled = globalThis.__circleIrPassTiming === true;
30120
31586
  for (const pass of this.passes) {
31587
+ if (limit && Date.now() > limit.deadline) throw limit.onExpired(pass.name);
30121
31588
  if (timingEnabled) {
30122
31589
  console.error(`[pass-start] ${pass.name} file=${graph.ir.meta.file}`);
30123
31590
  }
@@ -30980,10 +32447,49 @@ var JAVA_LANG_EXCEPTION_TYPES = /* @__PURE__ */ new Set([
30980
32447
  "Error",
30981
32448
  "AssertionError"
30982
32449
  ]);
32450
+ var JAVA_LANG_STRING_ACCUMULATOR_TYPES = /* @__PURE__ */ new Set(["StringBuilder", "StringBuffer"]);
30983
32451
  var JAVA_FORMATTER_CLASSES = /* @__PURE__ */ new Map([
30984
32452
  ["String", "java.lang"],
30985
32453
  ["MessageFormat", "java.text"]
30986
32454
  ]);
32455
+ var JAVA_LOGGER_METHODS = /* @__PURE__ */ new Set([
32456
+ "severe",
32457
+ "warning",
32458
+ "config",
32459
+ "fine",
32460
+ "finer",
32461
+ "finest",
32462
+ "logp",
32463
+ "logrb",
32464
+ "log",
32465
+ "trace",
32466
+ "debug",
32467
+ "info",
32468
+ "warn",
32469
+ "error",
32470
+ "fatal",
32471
+ "Log",
32472
+ "LogInformation",
32473
+ "LogWarning",
32474
+ "LogError",
32475
+ "LogDebug",
32476
+ "LogTrace",
32477
+ "LogCritical",
32478
+ "Information",
32479
+ "Warning",
32480
+ "Verbose",
32481
+ "Fatal",
32482
+ "BeginScope"
32483
+ ]);
32484
+ function isJavaLoggerCall(call) {
32485
+ return JAVA_LOGGER_METHODS.has(call.method_name);
32486
+ }
32487
+ var JAVA_OBJECTS_CLASS = /* @__PURE__ */ new Map([["Objects", "java.util"]]);
32488
+ var JAVA_OBJECTS_NULL_CHECK_METHODS = /* @__PURE__ */ new Set([
32489
+ "requireNonNull",
32490
+ "requireNonNullElse",
32491
+ "requireNonNullElseGet"
32492
+ ]);
30987
32493
  function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
30988
32494
  if (call.method_name !== "format" || !call.receiver) return false;
30989
32495
  const needle = `${call.receiver}.format(`;
@@ -30993,6 +32499,10 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
30993
32499
  }
30994
32500
  return isJdkClassReceiver(call.receiver, JAVA_FORMATTER_CLASSES, declaredTypeNames, imports);
30995
32501
  }
32502
+ function isJavaObjectsNullCheckCall(call, declaredTypeNames, imports) {
32503
+ if (!call.receiver || !JAVA_OBJECTS_NULL_CHECK_METHODS.has(call.method_name)) return false;
32504
+ return isJdkClassReceiver(call.receiver, JAVA_OBJECTS_CLASS, declaredTypeNames, imports);
32505
+ }
30996
32506
  function isJdkClassReceiver(receiver, classes, declaredTypeNames, imports) {
30997
32507
  const dot = receiver.lastIndexOf(".");
30998
32508
  const simple = dot < 0 ? receiver : receiver.slice(dot + 1);
@@ -31035,12 +32545,18 @@ var JAVA_CHARACTER_PREDICATES = /* @__PURE__ */ new Set([
31035
32545
  "isBmpCodePoint",
31036
32546
  "isSupplementaryCodePoint"
31037
32547
  ]);
32548
+ var JAVA_CHARACTER_METADATA = /* @__PURE__ */ new Set(["getName", "getType", "getNumericValue"]);
31038
32549
  function isJavaCharacterPredicateCall(call, declaredTypeNames, imports) {
31039
- if (!call.receiver || !JAVA_CHARACTER_PREDICATES.has(call.method_name)) return false;
32550
+ if (!call.receiver) return false;
32551
+ if (!JAVA_CHARACTER_PREDICATES.has(call.method_name) && !JAVA_CHARACTER_METADATA.has(call.method_name)) {
32552
+ return false;
32553
+ }
31040
32554
  return isJdkClassReceiver(call.receiver, JAVA_CHARACTER_CLASS, declaredTypeNames, imports);
31041
32555
  }
31042
32556
  function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
31043
32557
  if (isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls)) return true;
32558
+ if (isJavaObjectsNullCheckCall(call, declaredTypeNames, imports)) return true;
32559
+ if (isJavaLoggerCall(call)) return true;
31044
32560
  if (isJavaCharacterPredicateCall(call, declaredTypeNames, imports)) return true;
31045
32561
  if (call.receiver) return false;
31046
32562
  const isCtor = call.is_constructor === true || (call.resolution?.target ?? "").endsWith(".<init>");
@@ -31048,11 +32564,11 @@ function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
31048
32564
  const written = call.method_name;
31049
32565
  const simple = written.split(".").pop() ?? written;
31050
32566
  if (written.includes(".")) {
31051
- return written.startsWith("java.lang.") && (JAVA_LANG_EXCEPTION_TYPES.has(simple) || /(Exception|Error)$/.test(simple));
32567
+ return written.startsWith("java.lang.") && (JAVA_LANG_EXCEPTION_TYPES.has(simple) || JAVA_LANG_STRING_ACCUMULATOR_TYPES.has(simple) || /(Exception|Error)$/.test(simple));
31052
32568
  }
31053
32569
  if (call.receiver_type_fqn && !call.receiver_type_fqn.startsWith("java.lang.")) return false;
31054
32570
  if (declaredTypeNames.has(simple)) return false;
31055
- return JAVA_LANG_EXCEPTION_TYPES.has(simple);
32571
+ return JAVA_LANG_EXCEPTION_TYPES.has(simple) || JAVA_LANG_STRING_ACCUMULATOR_TYPES.has(simple);
31056
32572
  }
31057
32573
  function isGoConstRegexMatchCall(call, graph, calls) {
31058
32574
  if (call.method_name !== "MatchString" || call.arguments.length !== 1) return false;
@@ -31313,6 +32829,14 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31313
32829
  sanitizerMethods.add(match[1]);
31314
32830
  }
31315
32831
  }
32832
+ const bashScriptFunctions = /* @__PURE__ */ new Set();
32833
+ if (graph.ir.meta.language === "bash") {
32834
+ for (const d of graph.ir.dfg.defs) {
32835
+ if (d.kind === "param" && d.expression?.startsWith("bash-fn:")) {
32836
+ bashScriptFunctions.add(d.expression.slice("bash-fn:".length));
32837
+ }
32838
+ }
32839
+ }
31316
32840
  for (const call of calls) {
31317
32841
  const taintedArgPositions = [];
31318
32842
  const taintedArgVars = [];
@@ -31336,7 +32860,8 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31336
32860
  }
31337
32861
  }
31338
32862
  }
31339
- const targetMethod = getMethodNode(methodNodes, call.method_name);
32863
+ const resolvedCalleeFqn = resolveMethodCall2(call, methodNodes, types);
32864
+ const targetMethod = resolvedCalleeFqn ? methodNodes.byFqn.get(resolvedCalleeFqn) : getMethodNode(methodNodes, call.method_name);
31340
32865
  if (!targetMethod) {
31341
32866
  if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name) && !ormQueryBuilderMethods.has(call.method_name)) {
31342
32867
  const isBash = graph.ir.meta.language === "bash";
@@ -31370,6 +32895,9 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31370
32895
  if (isBash && bashSafeBuiltins.has(call.method_name)) {
31371
32896
  continue;
31372
32897
  }
32898
+ if (isBash && bashScriptFunctions.has(call.method_name)) {
32899
+ continue;
32900
+ }
31373
32901
  if (isBash && taintedArgPositions.length > 0) {
31374
32902
  const terminatorPos = call.arguments.filter((a) => a.expression === "--").map((a) => a.position).reduce((min, p) => min === null || p < min ? p : min, null);
31375
32903
  const earliestTainted = Math.min(...taintedArgPositions);
@@ -31427,6 +32955,12 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31427
32955
  if (pos < targetMethod.parameters.length) {
31428
32956
  targetMethod.parameters[pos].isTainted = true;
31429
32957
  targetMethod.parameters[pos].sourceLine = call.location.line;
32958
+ const paramName = targetMethod.parameters[pos].name;
32959
+ for (const def of graph.ir.dfg.defs) {
32960
+ if (def.kind === "param" && def.variable === paramName && def.line >= targetMethod.startLine && def.line <= targetMethod.endLine) {
32961
+ taintedDefIds.add(def.id);
32962
+ }
32963
+ }
31430
32964
  }
31431
32965
  }
31432
32966
  taintedMethods.add(targetMethod.fqn);
@@ -32027,6 +33561,11 @@ var ConstantPropagator = class _ConstantPropagator {
32027
33561
  taintedArrayElements = /* @__PURE__ */ new Map();
32028
33562
  // Track current method name for scoping local variables
32029
33563
  currentMethod = null;
33564
+ /** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
33565
+ blockScopeStack = [];
33566
+ lineBlockScopes = /* @__PURE__ */ new Map();
33567
+ /** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
33568
+ skipMethodBodyBlockScope = false;
32030
33569
  // Track conditional taints: which variables were tainted under which conditions
32031
33570
  // Maps condition expression string → set of variables tainted under that condition
32032
33571
  conditionalTaints = /* @__PURE__ */ new Map();
@@ -32040,6 +33579,8 @@ var ConstantPropagator = class _ConstantPropagator {
32040
33579
  inSynchronizedBlock = false;
32041
33580
  // Track iterator sources: iterator variable name → collection name it was created from
32042
33581
  iteratorSources = /* @__PURE__ */ new Map();
33582
+ /** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
33583
+ taintedFromIteratorNext = /* @__PURE__ */ new Set();
32043
33584
  // Track class field names (declared at class level, not local variables)
32044
33585
  classFields = /* @__PURE__ */ new Set();
32045
33586
  // Track tainted method parameters for inter-procedural analysis
@@ -32097,12 +33638,16 @@ var ConstantPropagator = class _ConstantPropagator {
32097
33638
  this.sanitizedVars.clear();
32098
33639
  this.sanitizedVarUncovered.clear();
32099
33640
  this.currentMethod = null;
33641
+ this.blockScopeStack = [];
33642
+ this.lineBlockScopes.clear();
33643
+ this.skipMethodBodyBlockScope = false;
32100
33644
  this.conditionalTaints.clear();
32101
33645
  this.conditionStack = [];
32102
33646
  this.lineConditions.clear();
32103
33647
  this.synchronizedLines.clear();
32104
33648
  this.inSynchronizedBlock = false;
32105
33649
  this.iteratorSources.clear();
33650
+ this.taintedFromIteratorNext.clear();
32106
33651
  this.classFields.clear();
32107
33652
  this.instanceFieldTaint.clear();
32108
33653
  this.currentClassName = null;
@@ -32127,6 +33672,9 @@ var ConstantPropagator = class _ConstantPropagator {
32127
33672
  const resultSymbols = new Map(this.symbols);
32128
33673
  for (const name2 of this.tainted) {
32129
33674
  if (name2.includes(":")) {
33675
+ if (/^[^:]+:b\d+:/.test(name2)) {
33676
+ continue;
33677
+ }
32130
33678
  const unscoped = name2.substring(name2.indexOf(":") + 1);
32131
33679
  resultTainted.add(unscoped);
32132
33680
  }
@@ -32141,6 +33689,9 @@ var ConstantPropagator = class _ConstantPropagator {
32141
33689
  for (const [name2, value] of this.symbols) {
32142
33690
  if (this.goScopedNames.has(name2)) continue;
32143
33691
  if (name2.includes(":")) {
33692
+ if (/^[^:]+:b\d+:/.test(name2)) {
33693
+ continue;
33694
+ }
32144
33695
  const unscoped = name2.substring(name2.indexOf(":") + 1);
32145
33696
  if (!resultSymbols.has(unscoped)) {
32146
33697
  resultSymbols.set(unscoped, value);
@@ -32150,6 +33701,7 @@ var ConstantPropagator = class _ConstantPropagator {
32150
33701
  return {
32151
33702
  symbols: resultSymbols,
32152
33703
  tainted: resultTainted,
33704
+ lineBlockScopes: new Map(this.lineBlockScopes),
32153
33705
  unreachableLines: new Set(this.unreachableLines),
32154
33706
  taintedCollections: new Map(this.taintedCollections),
32155
33707
  taintedArrayElements: new Map(this.taintedArrayElements),
@@ -32693,6 +34245,18 @@ var ConstantPropagator = class _ConstantPropagator {
32693
34245
  // ===========================================================================
32694
34246
  // Taint Refinement
32695
34247
  // ===========================================================================
34248
+ /** True when `node` is `iter.next()` (possibly under a cast). */
34249
+ isIteratorNextExpression(node) {
34250
+ const text = getNodeText2(node, this.source);
34251
+ if (/\.\s*next\s*\(/.test(text)) return true;
34252
+ let current = node;
34253
+ if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
34254
+ current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
34255
+ }
34256
+ if (!current || current.type !== "method_invocation") return false;
34257
+ const nameNode = current.childForFieldName("name");
34258
+ return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
34259
+ }
32696
34260
  refineTaintFromConstants() {
32697
34261
  let changed = true;
32698
34262
  let iterations = 0;
@@ -32702,6 +34266,9 @@ var ConstantPropagator = class _ConstantPropagator {
32702
34266
  iterations++;
32703
34267
  const toRemove = [];
32704
34268
  for (const varName of this.tainted) {
34269
+ if (this.taintedFromIteratorNext.has(varName)) {
34270
+ continue;
34271
+ }
32705
34272
  const symbol = this.symbols.get(varName);
32706
34273
  if (symbol && symbol.type !== "unknown") {
32707
34274
  toRemove.push(varName);
@@ -32750,6 +34317,9 @@ var ConstantPropagator = class _ConstantPropagator {
32750
34317
  if (this.unreachableLines.has(line)) {
32751
34318
  return true;
32752
34319
  }
34320
+ if (this.blockScopeStack.length > 0) {
34321
+ this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
34322
+ }
32753
34323
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
32754
34324
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
32755
34325
  }
@@ -32775,12 +34345,24 @@ var ConstantPropagator = class _ConstantPropagator {
32775
34345
  case "assignment_statement":
32776
34346
  this.handleGoAssignment(node);
32777
34347
  return false;
34348
+ case "var_declaration":
34349
+ this.handleGoVarDeclaration(node);
34350
+ return false;
32778
34351
  case "update_expression":
32779
34352
  this.handleUpdateExpression(node);
32780
34353
  return false;
32781
34354
  case "if_statement":
32782
34355
  this.handleIfStatement(node);
32783
34356
  return true;
34357
+ case "block": {
34358
+ if (this.skipMethodBodyBlockScope) {
34359
+ this.skipMethodBodyBlockScope = false;
34360
+ }
34361
+ for (const child of node.children) {
34362
+ this.visit(child);
34363
+ }
34364
+ return true;
34365
+ }
32784
34366
  case "switch_expression":
32785
34367
  case "switch_statement":
32786
34368
  this.handleSwitch(node);
@@ -32866,7 +34448,9 @@ var ConstantPropagator = class _ConstantPropagator {
32866
34448
  }
32867
34449
  const body2 = node.childForFieldName("body");
32868
34450
  if (body2) {
34451
+ this.skipMethodBodyBlockScope = true;
32869
34452
  this.visit(body2);
34453
+ this.skipMethodBodyBlockScope = false;
32870
34454
  }
32871
34455
  this.currentMethod = prevMethod;
32872
34456
  this.inConstructor = prevInConstructor;
@@ -32880,10 +34464,31 @@ var ConstantPropagator = class _ConstantPropagator {
32880
34464
  if (varName.includes(".") || varName.includes(":")) {
32881
34465
  return varName;
32882
34466
  }
34467
+ const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
32883
34468
  if (this.currentMethod) {
32884
- return `${this.currentMethod}:${varName}`;
34469
+ return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
32885
34470
  }
32886
- return varName;
34471
+ return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
34472
+ }
34473
+ /** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
34474
+ getScopedNameCandidates(varName) {
34475
+ if (varName.includes(".") || varName.includes(":")) {
34476
+ return [varName];
34477
+ }
34478
+ const out2 = [];
34479
+ for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
34480
+ const blockId = this.blockScopeStack[i2];
34481
+ if (this.currentMethod) {
34482
+ out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
34483
+ } else {
34484
+ out2.push(`b${blockId}:${varName}`);
34485
+ }
34486
+ }
34487
+ if (this.currentMethod) {
34488
+ out2.push(`${this.currentMethod}:${varName}`);
34489
+ }
34490
+ out2.push(varName);
34491
+ return out2;
32887
34492
  }
32888
34493
  /**
32889
34494
  * Look up a variable value, checking both scoped and unscoped names.
@@ -32891,11 +34496,12 @@ var ConstantPropagator = class _ConstantPropagator {
32891
34496
  * either local (scoped) or global (unscoped, like class fields).
32892
34497
  */
32893
34498
  lookupSymbol(varName) {
32894
- if (this.currentMethod && !varName.includes(".") && !varName.includes(":")) {
32895
- const scopedName = `${this.currentMethod}:${varName}`;
32896
- const scopedValue = this.symbols.get(scopedName);
32897
- if (scopedValue) {
32898
- return scopedValue;
34499
+ if (!varName.includes(".") && !varName.includes(":")) {
34500
+ for (const scopedName of this.getScopedNameCandidates(varName)) {
34501
+ const scopedValue = this.symbols.get(scopedName);
34502
+ if (scopedValue) {
34503
+ return scopedValue;
34504
+ }
32899
34505
  }
32900
34506
  }
32901
34507
  return this.symbols.get(varName);
@@ -32947,8 +34553,22 @@ var ConstantPropagator = class _ConstantPropagator {
32947
34553
  this.trackIteratorsInNode(initNode);
32948
34554
  }
32949
34555
  }
32950
- for (const child of node.children) {
32951
- this.visit(child);
34556
+ if (node.type === "while_statement" || node.type === "do_statement") {
34557
+ const body2 = node.childForFieldName("body");
34558
+ for (const child of node.children) {
34559
+ if (child !== body2) this.visit(child);
34560
+ }
34561
+ if (body2?.type === "block") {
34562
+ this.blockScopeStack.push(getNodeLine(body2));
34563
+ this.visit(body2);
34564
+ this.blockScopeStack.pop();
34565
+ } else if (body2) {
34566
+ this.visit(body2);
34567
+ }
34568
+ } else {
34569
+ for (const child of node.children) {
34570
+ this.visit(child);
34571
+ }
32952
34572
  }
32953
34573
  for (const varName of loopVarNames) {
32954
34574
  this.symbols.set(varName, createUnknown(getNodeLine(node)));
@@ -33090,6 +34710,10 @@ var ConstantPropagator = class _ConstantPropagator {
33090
34710
  this.sanitizedVars.delete(scopedName);
33091
34711
  this.sanitizedVarUncovered.delete(scopedName);
33092
34712
  this.definitionNodes.set(scopedName, valueNode);
34713
+ if (this.isIteratorNextExpression(valueNode)) {
34714
+ this.taintedFromIteratorNext.add(scopedName);
34715
+ this.taintedFromIteratorNext.add(varName);
34716
+ }
33093
34717
  this.symbols.set(scopedName, createUnknown(line));
33094
34718
  } else {
33095
34719
  if (this.inConditionalBranch) {
@@ -33131,6 +34755,64 @@ var ConstantPropagator = class _ConstantPropagator {
33131
34755
  * helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
33132
34756
  * assignments got before this handler existed: the DFG decides.
33133
34757
  */
34758
+ /** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
34759
+ handleGoVarDeclaration(node) {
34760
+ for (let i2 = 0; i2 < node.childCount; i2++) {
34761
+ const spec = node.child(i2);
34762
+ if (!spec || spec.type !== "var_spec") continue;
34763
+ const nameNode = spec.childForFieldName("name");
34764
+ let valueNode = spec.childForFieldName("value");
34765
+ if (!nameNode || !valueNode) continue;
34766
+ const names = [];
34767
+ if (nameNode.type === "identifier") {
34768
+ names.push(getNodeText2(nameNode, this.source));
34769
+ } else if (nameNode.type === "identifier_list") {
34770
+ for (const c of nameNode.children) {
34771
+ if (c.type === "identifier") names.push(getNodeText2(c, this.source));
34772
+ }
34773
+ }
34774
+ if (valueNode.type === "expression_list") {
34775
+ valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
34776
+ }
34777
+ if (names.length === 0) continue;
34778
+ const line = getNodeLine(node);
34779
+ for (const varName of names) {
34780
+ if (varName === "_") continue;
34781
+ const scopedName = this.getScopedName(varName);
34782
+ this.goScopedNames.add(scopedName);
34783
+ if (this.isTaintedExpression(valueNode)) {
34784
+ this.goUnknownVars.add(scopedName);
34785
+ this.tainted.add(scopedName);
34786
+ this.sanitizedVars.delete(scopedName);
34787
+ this.sanitizedVarUncovered.delete(scopedName);
34788
+ this.definitionNodes.set(scopedName, valueNode);
34789
+ this.symbols.set(scopedName, createUnknown(line));
34790
+ continue;
34791
+ }
34792
+ if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
34793
+ this.goUnknownVars.add(scopedName);
34794
+ this.symbols.delete(scopedName);
34795
+ this.sanitizedVars.delete(scopedName);
34796
+ this.sanitizedVarUncovered.delete(scopedName);
34797
+ if (!this.inConditionalBranch) {
34798
+ this.tainted.delete(scopedName);
34799
+ this.definitionNodes.delete(scopedName);
34800
+ }
34801
+ continue;
34802
+ }
34803
+ if (this.inConditionalBranch) {
34804
+ this.goUnknownVars.add(scopedName);
34805
+ this.symbols.delete(scopedName);
34806
+ continue;
34807
+ }
34808
+ this.tainted.delete(scopedName);
34809
+ this.definitionNodes.delete(scopedName);
34810
+ this.symbols.set(scopedName, this.evaluateExpression(valueNode));
34811
+ this.sanitizedVars.delete(scopedName);
34812
+ this.sanitizedVarUncovered.delete(scopedName);
34813
+ }
34814
+ }
34815
+ }
33134
34816
  handleGoAssignment(node) {
33135
34817
  const left = node.childForFieldName("left");
33136
34818
  const right = node.childForFieldName("right");
@@ -33217,6 +34899,12 @@ var ConstantPropagator = class _ConstantPropagator {
33217
34899
  this.sanitizedVars.delete(scopedName);
33218
34900
  this.sanitizedVarUncovered.delete(scopedName);
33219
34901
  this.definitionNodes.set(scopedName, right);
34902
+ if (this.isIteratorNextExpression(right)) {
34903
+ this.taintedFromIteratorNext.add(scopedName);
34904
+ if (!varName.includes(".")) {
34905
+ this.taintedFromIteratorNext.add(varName);
34906
+ }
34907
+ }
33220
34908
  this.symbols.set(scopedName, createUnknown(line));
33221
34909
  if (this.inConstructor && varName.startsWith("this.")) {
33222
34910
  const fieldName = varName.substring(5);
@@ -33941,6 +35629,24 @@ var ConstantPropagator = class _ConstantPropagator {
33941
35629
  cache.set(rootId, false);
33942
35630
  return false;
33943
35631
  }
35632
+ /** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
35633
+ argIsSanitizedTaint(call) {
35634
+ const argsNode = call.childForFieldName("arguments");
35635
+ if (!argsNode) return false;
35636
+ for (const arg of argsNode.children) {
35637
+ if (arg.type !== "method_invocation") continue;
35638
+ const name2 = arg.childForFieldName("name");
35639
+ if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
35640
+ const inner = arg.childForFieldName("arguments");
35641
+ if (!inner) continue;
35642
+ for (const a of inner.children) {
35643
+ if (a.type === "(" || a.type === ")" || a.type === ",") continue;
35644
+ if (this.isTaintedExpression(a)) return true;
35645
+ }
35646
+ if (this.argIsSanitizedTaint(arg)) return true;
35647
+ }
35648
+ return false;
35649
+ }
33944
35650
  isTaintedExpressionStep(node) {
33945
35651
  const text = getNodeText2(node, this.source);
33946
35652
  if (node.type === "method_invocation") {
@@ -33948,6 +35654,9 @@ var ConstantPropagator = class _ConstantPropagator {
33948
35654
  const objectNode = node.childForFieldName("object");
33949
35655
  if (nameNode) {
33950
35656
  const methodName = getNodeText2(nameNode, this.source);
35657
+ if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
35658
+ return true;
35659
+ }
33951
35660
  if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
33952
35661
  return false;
33953
35662
  }
@@ -34207,10 +35916,17 @@ var ConstantPropagator = class _ConstantPropagator {
34207
35916
  }
34208
35917
  }
34209
35918
  if (node.type === "identifier") {
34210
- const scopedName = this.currentMethod ? `${this.currentMethod}:${text}` : text;
34211
- const isTainted = this.tainted.has(scopedName) || this.tainted.has(text);
34212
- if (isTainted) {
34213
- if (this.isExcludedByCondition(scopedName) || this.isExcludedByCondition(text)) {
35919
+ let isTainted = false;
35920
+ let boundName = null;
35921
+ for (const candidate of this.getScopedNameCandidates(text)) {
35922
+ if (this.tainted.has(candidate)) {
35923
+ isTainted = true;
35924
+ boundName = candidate;
35925
+ break;
35926
+ }
35927
+ }
35928
+ if (isTainted && boundName) {
35929
+ if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
34214
35930
  return false;
34215
35931
  }
34216
35932
  }
@@ -34417,6 +36133,14 @@ function analyzeConstantPropagation(tree, sourceCode, options = {}) {
34417
36133
  options.taintedParameters ?? []
34418
36134
  );
34419
36135
  }
36136
+ function constPropScopedVar(method, varName, line, result) {
36137
+ if (!method) return varName;
36138
+ const blockId = result.lineBlockScopes?.get(line);
36139
+ if (blockId !== void 0) {
36140
+ return `${method}:b${blockId}:${varName}`;
36141
+ }
36142
+ return `${method}:${varName}`;
36143
+ }
34420
36144
  function isFalsePositive(result, sinkLine, taintedVar) {
34421
36145
  if (result.unreachableLines.has(sinkLine)) {
34422
36146
  return { isFalsePositive: true, reason: "sink_in_dead_code" };
@@ -34908,6 +36632,8 @@ function classifyEntryPointTier(method, enclosingType, ctx) {
34908
36632
  return classifyJsTsEntryPoint(method, enclosingType, ctx);
34909
36633
  case "go":
34910
36634
  return classifyGoEntryPoint(method, enclosingType, ctx);
36635
+ case "csharp":
36636
+ return classifyCSharpEntryPoint(method, enclosingType, ctx);
34911
36637
  case "bash":
34912
36638
  case "shell":
34913
36639
  return classifyBashEntryPoint(method, enclosingType, ctx);
@@ -35160,6 +36886,84 @@ function fileHasJsTsFrameworkRegistration(regs) {
35160
36886
  }
35161
36887
  return false;
35162
36888
  }
36889
+ var CSHARP_TIER_1_METHOD_ANNOTATIONS = /* @__PURE__ */ new Set([
36890
+ "HttpGet",
36891
+ "HttpPost",
36892
+ "HttpPut",
36893
+ "HttpDelete",
36894
+ "HttpPatch",
36895
+ "HttpHead",
36896
+ "HttpOptions",
36897
+ "Route",
36898
+ "AcceptVerbs",
36899
+ "MapGet",
36900
+ "MapPost",
36901
+ "MapPut",
36902
+ "MapDelete",
36903
+ "MapPatch"
36904
+ ]);
36905
+ var CSHARP_TIER_1_PARAM_ANNOTATIONS = /* @__PURE__ */ new Set([
36906
+ "FromQuery",
36907
+ "FromBody",
36908
+ "FromRoute",
36909
+ "FromHeader",
36910
+ "FromForm",
36911
+ "FromServices"
36912
+ ]);
36913
+ var CSHARP_TIER_1_CLASS_ANNOTATIONS = /* @__PURE__ */ new Set([
36914
+ "ApiController",
36915
+ "Controller",
36916
+ "Route"
36917
+ ]);
36918
+ var CSHARP_MVC_BASE_SIMPLE_NAMES = /* @__PURE__ */ new Set([
36919
+ "Controller",
36920
+ "ControllerBase",
36921
+ "PageModel"
36922
+ ]);
36923
+ function csharpBaseTypeLooksLikeMvc(base) {
36924
+ const simple = (base.split(".").pop() ?? base).trim();
36925
+ if (CSHARP_MVC_BASE_SIMPLE_NAMES.has(simple)) return true;
36926
+ return simple.endsWith("Controller") && simple !== "Controller";
36927
+ }
36928
+ function csharpTypeLooksLikeController(enclosingType) {
36929
+ if (!enclosingType) return false;
36930
+ if (annotationsInclude(enclosingType.annotations, CSHARP_TIER_1_CLASS_ANNOTATIONS)) return true;
36931
+ if (enclosingType.extends && csharpBaseTypeLooksLikeMvc(enclosingType.extends)) return true;
36932
+ const name2 = enclosingType.name ?? "";
36933
+ if (csharpBaseTypeLooksLikeMvc(name2)) return true;
36934
+ return name2.endsWith("Controller") && name2 !== "Controller";
36935
+ }
36936
+ function csharpMethodHasMvcBindingParams(method) {
36937
+ for (const p of method.parameters ?? []) {
36938
+ if (annotationsInclude(p.annotations, CSHARP_TIER_1_PARAM_ANNOTATIONS)) return true;
36939
+ }
36940
+ return false;
36941
+ }
36942
+ function looksLikeCSharpMain(method) {
36943
+ const mods = method.modifiers ?? [];
36944
+ if (!mods.includes("static")) return false;
36945
+ if (method.name !== "Main") return false;
36946
+ const params = method.parameters ?? [];
36947
+ return params.length === 0 || params.length === 1 && /\bstring\s*\[\s*\]/.test(params[0]?.type ?? "");
36948
+ }
36949
+ function classifyCSharpEntryPoint(method, enclosingType, ctx) {
36950
+ if (pathLooksLikeLibraryOrTest(ctx.filePath)) {
36951
+ return "TIER_3_LIBRARY_API";
36952
+ }
36953
+ if (annotationsInclude(method.annotations, CSHARP_TIER_1_METHOD_ANNOTATIONS)) {
36954
+ return "TIER_1_ENTRY_POINT";
36955
+ }
36956
+ if (csharpMethodHasMvcBindingParams(method)) {
36957
+ return "TIER_1_ENTRY_POINT";
36958
+ }
36959
+ if (csharpTypeLooksLikeController(enclosingType)) {
36960
+ return "TIER_1_ENTRY_POINT";
36961
+ }
36962
+ if (looksLikeCSharpMain(method)) {
36963
+ return "TIER_1_ENTRY_POINT";
36964
+ }
36965
+ return "TIER_3_LIBRARY_API";
36966
+ }
35163
36967
  var GO_HTTP_REGISTRAR_METHODS = /* @__PURE__ */ new Set([
35164
36968
  // net/http
35165
36969
  "HandleFunc",
@@ -38768,6 +40572,10 @@ var CSharpCredit = class {
38768
40572
  if (type === "path_traversal") {
38769
40573
  if (/\b([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\([^)]*\)\s*\?\s*\1\s*:\s*null\b/.test(text)) return true;
38770
40574
  }
40575
+ if (type === "open_redirect") {
40576
+ if (/\b(?:Url\s*\.\s*)?IsLocalUrl\s*\(/.test(text)) return true;
40577
+ if (/\bLocalRedirect\s*\(/.test(text)) return true;
40578
+ }
38771
40579
  return false;
38772
40580
  }
38773
40581
  /**
@@ -38781,6 +40589,10 @@ var CSharpCredit = class {
38781
40589
  const set = /* @__PURE__ */ new Set();
38782
40590
  this.helperCache.set(type, set);
38783
40591
  for (const m of this.methods) {
40592
+ if (type === "open_redirect" && this.openRedirectHelperMethod(m)) {
40593
+ set.add(m.name);
40594
+ continue;
40595
+ }
38784
40596
  const returns = this.returnExprs(m);
38785
40597
  if (returns.length === 0) continue;
38786
40598
  const ok = returns.every((r) => {
@@ -38792,6 +40604,23 @@ var CSharpCredit = class {
38792
40604
  }
38793
40605
  return set;
38794
40606
  }
40607
+ /**
40608
+ * Same-file helper whose returns are safe for open_redirect: literal fallbacks
40609
+ * and parameters returned only under `Url.IsLocalUrl` (cognium-dev#946).
40610
+ */
40611
+ openRedirectHelperMethod(m) {
40612
+ const returns = this.returnExprs(m);
40613
+ if (returns.length === 0) return false;
40614
+ for (const r of returns) {
40615
+ const e = r.expr.trim();
40616
+ if (/^"[^"]*"$/.test(e) || /^'[^']*'$/.test(e)) continue;
40617
+ if (this.builtinSanitizes("open_redirect", e)) continue;
40618
+ const id = /^([A-Za-z_]\w*)$/.exec(e);
40619
+ if (!id) return false;
40620
+ if (!this.guardedVarsAt("open_redirect", r.line).has(id[1])) return false;
40621
+ }
40622
+ return true;
40623
+ }
38795
40624
  /** Every returned expression of a method: `return e;` and expression bodies. */
38796
40625
  returnExprs(m) {
38797
40626
  const out2 = [];
@@ -39024,6 +40853,14 @@ var CSharpCredit = class {
39024
40853
  }
39025
40854
  return out2;
39026
40855
  }
40856
+ /** Variables proven local by `Url.IsLocalUrl(x)` (cognium-dev#275/#946). */
40857
+ localUrlVars(cond) {
40858
+ const out2 = [];
40859
+ for (const m of cond.matchAll(/\b(?:Url\s*\.\s*)?IsLocalUrl\s*\(\s*([A-Za-z_]\w*)\s*\)/g)) {
40860
+ out2.push(m[1]);
40861
+ }
40862
+ return out2;
40863
+ }
39027
40864
  /** Variables whose host a condition compares against a constant or an allowlist. */
39028
40865
  hostVars(cond, hostOf) {
39029
40866
  const out2 = [];
@@ -39065,7 +40902,7 @@ var CSharpCredit = class {
39065
40902
  return map;
39066
40903
  }
39067
40904
  provenBy(kind, cond, parsedFrom, hostOf, withHelpers) {
39068
- const out2 = kind === "allowlist" ? this.allowlistVars(cond) : kind === "numeric" ? this.numericVars(cond, parsedFrom) : this.hostVars(cond, hostOf);
40905
+ const out2 = kind === "allowlist" ? this.allowlistVars(cond) : kind === "numeric" ? this.numericVars(cond, parsedFrom) : kind === "localUrl" ? this.localUrlVars(cond) : this.hostVars(cond, hostOf);
39069
40906
  if (withHelpers) {
39070
40907
  for (const [name2, pos] of this.guardHelpers(kind)) {
39071
40908
  for (const call of cond.matchAll(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`, "g"))) {
@@ -39095,6 +40932,7 @@ var CSharpCredit = class {
39095
40932
  const kinds = [];
39096
40933
  if (INERT_WHEN_ALPHANUMERIC.has(type)) kinds.push("allowlist", "numeric");
39097
40934
  if (type === "ssrf") kinds.push("host");
40935
+ if (type === "open_redirect") kinds.push("localUrl");
39098
40936
  const proven = /* @__PURE__ */ new Set();
39099
40937
  if (kinds.length === 0) return proven;
39100
40938
  const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
@@ -39337,6 +41175,15 @@ var InterproceduralPass = class {
39337
41175
  }
39338
41176
  }
39339
41177
  }
41178
+ appendCalleeSourceCallerSinkFlows(
41179
+ graph,
41180
+ sources,
41181
+ sinks,
41182
+ interProc,
41183
+ additionalFlows,
41184
+ language === "java" ? ctx.code : void 0,
41185
+ language === "java" ? constProp : void 0
41186
+ );
39340
41187
  const taintBridges = findTaintBridges(interProc);
39341
41188
  interprocedural = {
39342
41189
  tainted_methods: Array.from(interProc.taintedMethods),
@@ -39462,6 +41309,401 @@ var InterproceduralPass = class {
39462
41309
  return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
39463
41310
  }
39464
41311
  };
41312
+ function assigneeIsConstPropClean(constProp, method, variable) {
41313
+ if (!method) return false;
41314
+ const scoped = `${method}:${variable}`;
41315
+ if (!constProp.symbols.has(scoped)) return false;
41316
+ if (constProp.tainted.has(scoped)) return false;
41317
+ for (const t of constProp.tainted) {
41318
+ if (t.startsWith(`${method}:`) && t.endsWith(`:${variable}`)) return false;
41319
+ }
41320
+ if (constProp.sanitizedVarUncovered?.has(scoped)) return false;
41321
+ return true;
41322
+ }
41323
+ function constPropTaints(constProp, method, variable) {
41324
+ if (constProp.tainted.has(`${method}:${variable}`)) return true;
41325
+ for (const t of constProp.tainted) {
41326
+ if (t.startsWith(`${method}:`) && t.endsWith(`:${variable}`)) return true;
41327
+ }
41328
+ return false;
41329
+ }
41330
+ function callArgumentIsConstPropTainted(graph, constProp, method, line) {
41331
+ if (!method) return false;
41332
+ for (const call of graph.ir.calls) {
41333
+ if (call.location.line !== line) continue;
41334
+ for (const arg of call.arguments) {
41335
+ const names = arg.variable ? [arg.variable] : (arg.expression ?? "").match(/[A-Za-z_]\w*/g) ?? [];
41336
+ if (names.some((n) => constPropTaints(constProp, method, n))) return true;
41337
+ }
41338
+ }
41339
+ return false;
41340
+ }
41341
+ function calleeReturnsTaintedContainer(constProp, code, startLine, endLine) {
41342
+ const lines = code.split("\n");
41343
+ for (let l = startLine; l <= endLine; l++) {
41344
+ const m = /^\s*return\s+([A-Za-z_]\w*)\s*;/.exec(lines[l - 1] ?? "");
41345
+ if (!m) continue;
41346
+ if ((constProp.taintedArrayElements.get(m[1])?.size ?? 0) > 0) return true;
41347
+ if ((constProp.taintedCollections.get(m[1])?.size ?? 0) > 0) return true;
41348
+ }
41349
+ return false;
41350
+ }
41351
+ var DEAD_BRANCH_CHECKED_SINKS = /* @__PURE__ */ new Set([
41352
+ "sql_injection",
41353
+ "trust_boundary",
41354
+ "path_traversal",
41355
+ "ldap_injection",
41356
+ "xpath_injection"
41357
+ ]);
41358
+ function calleeSourceReachesReturn(graph, code, startLine, endLine, sourceLine) {
41359
+ const lines = code.split("\n");
41360
+ const returnLines = /* @__PURE__ */ new Set();
41361
+ for (let l = startLine; l <= endLine; l++) {
41362
+ if (/^\s*return\b/.test(lines[l - 1] ?? "")) returnLines.add(l);
41363
+ }
41364
+ if (returnLines.size === 0) return true;
41365
+ if (returnLines.has(sourceLine)) return true;
41366
+ const inRange = (l) => l >= startLine && l <= endLine;
41367
+ const ends = (l) => /[;{}]\s*$/.test((lines[l - 1] ?? "").replace(/\/\/.*$/, "").trimEnd());
41368
+ const statementStart = (l) => {
41369
+ let s = l;
41370
+ while (s > startLine && !ends(s - 1) && (lines[s - 2] ?? "").trim() !== "") s--;
41371
+ return s;
41372
+ };
41373
+ const reached = new Set(
41374
+ graph.ir.dfg.defs.filter((d) => d.line === sourceLine && d.kind === "local").map((d) => d.id)
41375
+ );
41376
+ if (reached.size === 0) return true;
41377
+ let changed = true;
41378
+ while (changed) {
41379
+ changed = false;
41380
+ for (const u of graph.ir.dfg.uses) {
41381
+ if (u.def_id === null || !reached.has(u.def_id) || !inRange(u.line)) continue;
41382
+ const stmt = statementStart(u.line);
41383
+ if (returnLines.has(u.line) || returnLines.has(stmt)) return true;
41384
+ for (const d of graph.ir.dfg.defs) {
41385
+ if ((d.line === u.line || d.line === stmt) && d.kind === "local" && !reached.has(d.id)) {
41386
+ reached.add(d.id);
41387
+ changed = true;
41388
+ }
41389
+ }
41390
+ const base = storeBase(lines[stmt - 1] ?? "");
41391
+ if (base && base !== u.variable) {
41392
+ for (const b of graph.ir.dfg.uses) {
41393
+ if ((b.line === u.line || b.line === stmt) && b.variable === base && b.def_id !== null && !reached.has(b.def_id)) {
41394
+ reached.add(b.def_id);
41395
+ changed = true;
41396
+ }
41397
+ }
41398
+ }
41399
+ }
41400
+ }
41401
+ return false;
41402
+ }
41403
+ function storeBase(line) {
41404
+ const m = /^\s*([A-Za-z_]\w*)\s*(?:\[[^\]=]*\]\s*)+=(?!=)/.exec(line) ?? /^\s*([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*=(?!=)/.exec(line);
41405
+ if (!m || m[1] === "this") return null;
41406
+ return m[1];
41407
+ }
41408
+ function appendCalleeSourceCallerSinkFlows(graph, sources, sinks, interProc, additionalFlows, fileCode, constProp) {
41409
+ const types = graph.ir.types ?? [];
41410
+ for (const edge of interProc.callEdges) {
41411
+ const callee = interProc.methodNodes.get(edge.calleeMethod);
41412
+ if (!callee) continue;
41413
+ const calleeSources = sources.filter(
41414
+ (s) => s.line >= callee.startLine && s.line <= callee.endLine && (!fileCode || s.type === "interprocedural_param" || calleeSourceReachesReturn(graph, fileCode, callee.startLine, callee.endLine, s.line))
41415
+ );
41416
+ if (calleeSources.length === 0) continue;
41417
+ const assignee = graph.ir.dfg.defs.find(
41418
+ (d) => d.line === edge.callLine && d.kind === "local"
41419
+ );
41420
+ if (!assignee?.variable) continue;
41421
+ if (constProp && callArgumentIsConstPropTainted(graph, constProp, edge.callerMethod, edge.callLine) && assigneeIsConstPropClean(constProp, edge.callerMethod, assignee.variable) && !(fileCode && calleeReturnsTaintedContainer(constProp, fileCode, callee.startLine, callee.endLine))) {
41422
+ continue;
41423
+ }
41424
+ const derivedLocals = localsDerivedFromAssignee(
41425
+ graph,
41426
+ assignee.variable,
41427
+ assignee.line
41428
+ );
41429
+ for (const sink of sinks) {
41430
+ if (sink.type === "external_taint_escape") continue;
41431
+ if (sink.line <= edge.callLine) continue;
41432
+ if (enclosingMethodName(types, sink.line) !== edge.callerMethod) continue;
41433
+ const sinkVar = sinkVariableConsumingDerived(graph, sink, derivedLocals);
41434
+ if (!sinkVar) continue;
41435
+ if (additionalFlows.some((f) => f.source_line === calleeSources[0].line && f.sink_line === sink.line)) {
41436
+ continue;
41437
+ }
41438
+ const source = calleeSources[0];
41439
+ if (fileCode && source.type === "interprocedural_param" && source.variable && DEAD_BRANCH_CHECKED_SINKS.has(sink.type) && interproceduralParamReachIsDeadBranchOnly(
41440
+ fileCode,
41441
+ callee.startLine,
41442
+ callee.endLine,
41443
+ source.variable,
41444
+ assignee.variable
41445
+ )) {
41446
+ continue;
41447
+ }
41448
+ if (fileCode && HELPER_RETURN_GATED_SINKS.has(sink.type) && calleeSources.every(
41449
+ (s) => (s.type === "interprocedural_param" || s.type === "plugin_param") && !!s.variable
41450
+ ) && !calleeReturnMayCarryParam(
41451
+ fileCode,
41452
+ callee.startLine,
41453
+ callee.endLine,
41454
+ calleeSources.map((s) => s.variable)
41455
+ )) {
41456
+ continue;
41457
+ }
41458
+ additionalFlows.push({
41459
+ source_line: source.line,
41460
+ sink_line: sink.line,
41461
+ source_type: source.type,
41462
+ sink_type: sink.type,
41463
+ path: [
41464
+ { variable: source.location, line: source.line, type: "source" },
41465
+ { variable: assignee.variable, line: edge.callLine, type: "use" },
41466
+ { variable: sinkVar, line: sink.line, type: "sink" }
41467
+ ],
41468
+ confidence: sink.confidence * source.confidence * 0.85,
41469
+ sanitized: false
41470
+ });
41471
+ }
41472
+ }
41473
+ }
41474
+ function interproceduralParamReachIsDeadBranchOnly(code, startLine, endLine, paramName, localName) {
41475
+ if (paramName === localName) return false;
41476
+ const lines = code.split("\n");
41477
+ const slice = lines.slice(startLine - 1, endLine);
41478
+ const sliceText = slice.join("\n");
41479
+ const dead = computeDeadJavaBranchLines(slice);
41480
+ const onLivePaths = buildJavaTaintedVars(sliceText, /* @__PURE__ */ new Set([paramName]), dead);
41481
+ const ifDeadLinesCount = buildJavaTaintedVars(sliceText, /* @__PURE__ */ new Set([paramName]), /* @__PURE__ */ new Set());
41482
+ return ifDeadLinesCount.has(localName) && !onLivePaths.has(localName);
41483
+ }
41484
+ var HELPER_RETURN_GATED_SINKS = /* @__PURE__ */ new Set([
41485
+ "sql_injection",
41486
+ "trust_boundary",
41487
+ "command_injection",
41488
+ "path_traversal",
41489
+ // #976: the same helper shapes feed LDAP and XPath sinks (OWASP ldapi/xpathi).
41490
+ "ldap_injection",
41491
+ "xpath_injection"
41492
+ ]);
41493
+ function calleeReturnMayCarryParam(code, startLine, endLine, sourceVars) {
41494
+ const raw = code.split("\n").slice(startLine - 1, endLine);
41495
+ const paramNames = [...sourceVars, ...javaMethodParamNames(raw)];
41496
+ const dead = computeDeadJavaBranchLines(raw);
41497
+ const inlined = /* @__PURE__ */ new Set();
41498
+ const slice = joinWrappedStatements(raw, inlined);
41499
+ const text = slice.join("\n");
41500
+ const seeds = new Set(paramNames);
41501
+ const mutated = /* @__PURE__ */ new Set();
41502
+ let live = new Set(seeds);
41503
+ for (let round = 0; round < 8; round++) {
41504
+ live = /* @__PURE__ */ new Set([...seeds, ...buildJavaTaintedVars(text, seeds, dead, { trackSlots: true }).keys()]);
41505
+ let grew = false;
41506
+ for (let i2 = 0; i2 < slice.length; i2++) {
41507
+ if (dead.has(i2)) continue;
41508
+ for (const call of slice[i2].matchAll(/([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*\(([^;]*)\)/g)) {
41509
+ if (live.has(call[1])) continue;
41510
+ const args2 = call[2].replace(/"(?:[^"\\]|\\.)*"/g, " ").match(/[\p{L}\p{N}_]+/gu) ?? [];
41511
+ if (args2.some((t) => live.has(t))) {
41512
+ seeds.add(call[1]);
41513
+ mutated.add(call[1]);
41514
+ grew = true;
41515
+ }
41516
+ }
41517
+ const store = /^\s*([A-Za-z_]\w*)\s*(?:(?:\[[^\]=]*\]\s*)+|\.\s*[A-Za-z_]\w*\s*)=(?!=)\s*(.*?);?\s*$/.exec(slice[i2]);
41518
+ if (store && store[1] !== "this" && !live.has(store[1])) {
41519
+ const rhs = store[2].replace(/"(?:[^"\\]|\\.)*"/g, " ").match(/[\p{L}\p{N}_]+/gu) ?? [];
41520
+ if (rhs.some((t) => live.has(t))) {
41521
+ seeds.add(store[1]);
41522
+ mutated.add(store[1]);
41523
+ grew = true;
41524
+ }
41525
+ }
41526
+ }
41527
+ if (!grew) break;
41528
+ }
41529
+ let sawReturn = false;
41530
+ for (let i2 = 0; i2 < slice.length; i2++) {
41531
+ if (dead.has(i2) || !/^\s*return\b/.test(slice[i2])) continue;
41532
+ const m = /^\s*return\b\s*(.*?)\s*;\s*(?:\/\/.*)?$/.exec(slice[i2]);
41533
+ if (!m) return true;
41534
+ sawReturn = true;
41535
+ const expr = m[1];
41536
+ const bare = /^[A-Za-z_]\w*$/.test(expr) ? expr : null;
41537
+ if (bare && mutated.has(bare)) return true;
41538
+ if (bare && live.has(bare)) {
41539
+ const def = mustReachingAssignment(slice, dead, i2, bare, inlined);
41540
+ if (!def) return true;
41541
+ const upTo = slice.slice(0, def.line);
41542
+ upTo.push(`__helperReturn = ${def.rhs};`);
41543
+ const atDef = buildJavaTaintedVars(upTo.join("\n"), seeds, dead, { trackSlots: true });
41544
+ if (atDef.has("__helperReturn")) return true;
41545
+ continue;
41546
+ }
41547
+ const idents = expr.replace(/"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ").match(/[\p{L}\p{N}_]+/gu) ?? [];
41548
+ if (idents.some((t) => live.has(t))) return true;
41549
+ }
41550
+ return !sawReturn;
41551
+ }
41552
+ function javaMethodParamNames(lines) {
41553
+ const header = lines.join("\n").split("{")[0];
41554
+ const open = header.indexOf("(");
41555
+ if (open < 0) return [];
41556
+ let depth = 0;
41557
+ let end = -1;
41558
+ for (let k = open; k < header.length; k++) {
41559
+ if (header[k] === "(") depth++;
41560
+ else if (header[k] === ")" && --depth === 0) {
41561
+ end = k;
41562
+ break;
41563
+ }
41564
+ }
41565
+ if (end < 0) return [];
41566
+ const names = [];
41567
+ let angle = 0;
41568
+ let piece = "";
41569
+ for (const c of header.slice(open + 1, end) + ",") {
41570
+ if (c === "<") angle++;
41571
+ else if (c === ">") angle--;
41572
+ if (c === "," && angle === 0) {
41573
+ const m = /([A-Za-z_]\w*)\s*(?:\[\s*\])*\s*$/.exec(piece.trim());
41574
+ if (m) names.push(m[1]);
41575
+ piece = "";
41576
+ } else piece += c;
41577
+ }
41578
+ return names;
41579
+ }
41580
+ function joinWrappedStatements(lines, inlined) {
41581
+ const out2 = [...lines];
41582
+ const ends = /[;{}:]\s*(?:\/\/.*)?$/;
41583
+ const header = /^\s*(?:if|else|for|while|do|switch|case|default|try|catch|finally|synchronized)\b|^\s*@|^\s*(?:\/\/|\/?\*)/;
41584
+ for (let i2 = 0; i2 < out2.length; i2++) {
41585
+ const line = out2[i2];
41586
+ if (line.trim() === "" || ends.test(line) || header.test(line)) continue;
41587
+ let joined = line;
41588
+ let j = i2 + 1;
41589
+ for (; j < out2.length && j < i2 + 15; j++) {
41590
+ joined += " " + out2[j].trim();
41591
+ if (ends.test(out2[j])) break;
41592
+ }
41593
+ if (j >= out2.length || j >= i2 + 15) continue;
41594
+ out2[i2] = joined;
41595
+ for (let k = i2 + 1; k <= j; k++) out2[k] = "";
41596
+ i2 = j;
41597
+ }
41598
+ return out2.map((line, i2) => {
41599
+ const body2 = inlineControlBody(line);
41600
+ if (body2 !== line) inlined.add(i2);
41601
+ return body2;
41602
+ });
41603
+ }
41604
+ function inlineControlBody(line) {
41605
+ let rest = line;
41606
+ const indent = /^\s*/.exec(line)[0];
41607
+ for (let guard = 0; guard < 4; guard++) {
41608
+ const t = rest.trimStart();
41609
+ const elseM = /^(?:\}\s*)?else\b\s*/.exec(t);
41610
+ if (elseM && !/^(?:\}\s*)?else\s+if\b/.test(t)) {
41611
+ rest = t.slice(elseM[0].length);
41612
+ continue;
41613
+ }
41614
+ const head = /^(?:\}\s*)?(?:else\s+)?(?:if|for|while)\s*\(/.exec(t);
41615
+ if (!head) break;
41616
+ let depth = 1;
41617
+ let k = head[0].length;
41618
+ for (; k < t.length && depth > 0; k++) {
41619
+ if (t[k] === "(") depth++;
41620
+ else if (t[k] === ")") depth--;
41621
+ }
41622
+ if (depth !== 0) return line;
41623
+ rest = t.slice(k);
41624
+ }
41625
+ const body2 = rest.trim();
41626
+ if (body2 === "" || body2 === rest.trim() && body2 === line.trim() || body2.startsWith("{")) return line;
41627
+ return indent + body2;
41628
+ }
41629
+ function mustReachingAssignment(slice, dead, retLine, name2, inlined) {
41630
+ const depth = [];
41631
+ let d = 0;
41632
+ for (const line of slice) {
41633
+ depth.push(d);
41634
+ const code = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''").replace(/\/\/.*$/, "");
41635
+ for (const c of code) {
41636
+ if (c === "{") d++;
41637
+ else if (c === "}") d--;
41638
+ }
41639
+ }
41640
+ const target = depth[retLine];
41641
+ const esc = name2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
41642
+ const assign = new RegExp(`^\\s*(?:(?:final\\s+)?[A-Za-z_][\\w.]*(?:\\s*<[^>]*>)?(?:\\s*\\[\\s*\\])*\\s+)?${esc}\\s*=\\s*(.+?);\\s*(?://.*)?$`);
41643
+ const mentionsAssign = new RegExp(`(?<![\\w.])${esc}\\s*=(?!=)`);
41644
+ for (let j = retLine - 1; j >= 0; j--) {
41645
+ if (depth[j] < target) return null;
41646
+ if (dead.has(j)) continue;
41647
+ if (!mentionsAssign.test(slice[j])) continue;
41648
+ if (depth[j] > target || inlined.has(j)) return null;
41649
+ const m = assign.exec(slice[j]);
41650
+ if (!m) return null;
41651
+ return { line: j, rhs: m[1] };
41652
+ }
41653
+ return null;
41654
+ }
41655
+ function localsDerivedFromAssignee(graph, seedVar, seedLine) {
41656
+ const defs = graph.ir.dfg.defs;
41657
+ const chains = graph.ir.dfg.chains ?? [];
41658
+ const seedDefs = defs.filter(
41659
+ (d) => d.variable === seedVar && d.line === seedLine && d.kind === "local"
41660
+ );
41661
+ if (seedDefs.length === 0) return /* @__PURE__ */ new Set([seedVar]);
41662
+ const defIds = new Set(seedDefs.map((d) => d.id));
41663
+ const frontier = seedDefs.map((d) => d.id);
41664
+ const visited = new Set(frontier);
41665
+ while (frontier.length > 0) {
41666
+ const cur = frontier.shift();
41667
+ for (const ch of chains) {
41668
+ if (ch.from_def !== cur || visited.has(ch.to_def)) continue;
41669
+ visited.add(ch.to_def);
41670
+ defIds.add(ch.to_def);
41671
+ frontier.push(ch.to_def);
41672
+ }
41673
+ }
41674
+ const names = /* @__PURE__ */ new Set([seedVar]);
41675
+ for (const d of defs) {
41676
+ if (d.variable && defIds.has(d.id)) names.add(d.variable);
41677
+ }
41678
+ return names;
41679
+ }
41680
+ function sinkVariableConsumingDerived(graph, sink, derived) {
41681
+ for (const u of graph.usesAtLine(sink.line)) {
41682
+ if (u.variable && derived.has(u.variable)) return u.variable;
41683
+ }
41684
+ for (const call of graph.callsByLine.get(sink.line) ?? []) {
41685
+ for (const arg of call.arguments ?? []) {
41686
+ if (arg.variable && derived.has(arg.variable)) return arg.variable;
41687
+ if (arg.expression) {
41688
+ for (const v of derived) {
41689
+ const re = new RegExp(`\\b${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
41690
+ if (re.test(arg.expression)) return v;
41691
+ }
41692
+ }
41693
+ }
41694
+ }
41695
+ return void 0;
41696
+ }
41697
+ function enclosingMethodName(types, line) {
41698
+ for (const type of types) {
41699
+ for (const method of type.methods) {
41700
+ if (line >= method.start_line && line <= method.end_line) {
41701
+ return method.name;
41702
+ }
41703
+ }
41704
+ }
41705
+ return null;
41706
+ }
39465
41707
  function csharpParamReachesSink(code, method, taintedArgs, sink, credit) {
39466
41708
  const lines = code.split(/\r?\n/);
39467
41709
  const params = taintedArgs.length > 0 ? method.parameters.filter((p) => taintedArgs.includes(p.position)) : method.parameters;
@@ -40202,12 +42444,26 @@ var ConstantPropagationPass = class {
40202
42444
  }
40203
42445
  }
40204
42446
  }
40205
- return analyzeConstantPropagation(this.tree, code, {
42447
+ const result = analyzeConstantPropagation(this.tree, code, {
40206
42448
  sanitizerMethods: taintMatcher.sanitizerMethods,
40207
42449
  taintedParameters
40208
42450
  });
42451
+ if (ctx.language === "java") pruneDeadChains(ctx, result.unreachableLines);
42452
+ return result;
40209
42453
  }
40210
42454
  };
42455
+ function pruneDeadChains(ctx, unreachable) {
42456
+ if (unreachable.size === 0) return;
42457
+ const dfg = ctx.graph.ir.dfg;
42458
+ if (!dfg.chains || dfg.chains.length === 0) return;
42459
+ const lines = ctx.code.split("\n");
42460
+ const dead = /* @__PURE__ */ new Set();
42461
+ for (const d of dfg.defs) {
42462
+ if (unreachable.has(d.line) && !/\?[^?.:]*:/.test(lines[d.line - 1] ?? "")) dead.add(d.id);
42463
+ }
42464
+ if (dead.size === 0) return;
42465
+ dfg.chains = dfg.chains.filter((c) => !dead.has(c.from_def) && !dead.has(c.to_def));
42466
+ }
40211
42467
 
40212
42468
  // src/analysis/passes/source-semantics-pass.ts
40213
42469
  var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
@@ -40694,13 +42950,123 @@ function argvFirstLiteralIsShell(lineText) {
40694
42950
  const base = m[1].split(/[\\/]/).pop().replace(/\.exe$/i, "").toLowerCase();
40695
42951
  return SHELL_PROGRAMS.has(base);
40696
42952
  }
40697
- function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, calls) {
42953
+ function javaLocalInitRhsBeforeLine(varName, beforeLine, methodStart, methodEnd, sourceLines) {
42954
+ const declAssign = new RegExp(
42955
+ `^\\s*(?:final\\s+)?[\\w<>,\\s\\[\\]]+\\s+${escapeReSf(varName)}\\s*=\\s*(.+?)\\s*;\\s*(?://.*)?$`
42956
+ );
42957
+ const reassign = new RegExp(`^\\s*${escapeReSf(varName)}\\s*=\\s*(.+?)\\s*;\\s*(?://.*)?$`);
42958
+ let lastRhs;
42959
+ for (let l = methodStart; l < beforeLine && l <= methodEnd; l++) {
42960
+ const line = (sourceLines[l - 1] ?? "").trim();
42961
+ const m = declAssign.exec(line) ?? reassign.exec(line);
42962
+ if (m) lastRhs = m[1].trim();
42963
+ }
42964
+ return lastRhs;
42965
+ }
42966
+ function callArgLooksLikeShellArgv(expr, probePrefix) {
42967
+ return argvFirstLiteralIsShell(`${probePrefix}${expr}`);
42968
+ }
42969
+ function parseSimpleJavaCall(expr) {
42970
+ const trimmed = expr.trim();
42971
+ const m = /^([\w][\w.]*)\s*\(/.exec(trimmed);
42972
+ if (!m) return null;
42973
+ const head = m[1];
42974
+ const dot = head.lastIndexOf(".");
42975
+ if (dot < 0) return { method: head };
42976
+ return { qualifier: head.slice(0, dot), method: head.slice(dot + 1) };
42977
+ }
42978
+ function javaMethodBodyReturnLooksLikeShellArgv(methodStart, methodEnd, sourceLines) {
42979
+ for (let l = methodStart; l <= methodEnd; l++) {
42980
+ const line = (sourceLines[l - 1] ?? "").trim();
42981
+ const ret = /\breturn\s+(.+?)\s*;/.exec(line);
42982
+ if (!ret) continue;
42983
+ if (callArgLooksLikeShellArgv(ret[1].trim(), "new ProcessBuilder(")) return true;
42984
+ }
42985
+ return false;
42986
+ }
42987
+ function javaResolveCalleeMethodLines(methodName, qualifier, refLine, types, sourceLines, ctx) {
42988
+ const enclosing = types.find((t) => refLine >= t.start_line && refLine <= t.end_line);
42989
+ const typeName = qualifier ? qualifier.includes(".") ? qualifier.split(".").pop() : qualifier : enclosing?.name;
42990
+ if (!typeName) return void 0;
42991
+ const tryType = (t, lines) => {
42992
+ const mm = t.methods.find((m) => m.name === methodName);
42993
+ if (!mm) return void 0;
42994
+ return { start: mm.start_line, end: mm.end_line, lines };
42995
+ };
42996
+ if (ctx?.projectGraph && ctx.sourceLinesByFile) {
42997
+ for (const fp of ctx.projectGraph.filePaths) {
42998
+ const ir = ctx.projectGraph.getIR(fp);
42999
+ if (!ir) continue;
43000
+ const t = ir.types.find((tt) => tt.name === typeName);
43001
+ if (!t) continue;
43002
+ const lines = ctx.sourceLinesByFile.get(fp);
43003
+ if (!lines) continue;
43004
+ const hit = tryType(t, lines);
43005
+ if (hit) return hit;
43006
+ }
43007
+ }
43008
+ const local = types.find((t) => t.name === typeName);
43009
+ if (local) return tryType(local, sourceLines);
43010
+ return void 0;
43011
+ }
43012
+ function javaExpressionLooksLikeShellArgv(expr, refLine, types, sourceLines, ctx) {
43013
+ if (callArgLooksLikeShellArgv(expr, "new ProcessBuilder(")) return true;
43014
+ const parsed = parseSimpleJavaCall(expr);
43015
+ if (!parsed) return false;
43016
+ const body2 = javaResolveCalleeMethodLines(
43017
+ parsed.method,
43018
+ parsed.qualifier,
43019
+ refLine,
43020
+ types,
43021
+ sourceLines,
43022
+ ctx
43023
+ );
43024
+ if (!body2) return false;
43025
+ return javaMethodBodyReturnLooksLikeShellArgv(body2.start, body2.end, body2.lines);
43026
+ }
43027
+ function javaFieldShellArgvWrittenBeforeLine(fieldName, beforeLine, methodStart, methodEnd, sourceLines, types, ctx) {
43028
+ const cap = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
43029
+ const beanSetter = new RegExp(`\\bset${escapeReSf(cap)}\\s*\\((.+)\\)\\s*;?\\s*$`);
43030
+ const fieldAssign = new RegExp(`^\\s*(?:this\\.)?${escapeReSf(fieldName)}\\s*=\\s*(.+?)\\s*;\\s*(?://.*)?$`);
43031
+ for (let l = methodStart; l < beforeLine && l <= methodEnd; l++) {
43032
+ const line = (sourceLines[l - 1] ?? "").trim();
43033
+ const sm = beanSetter.exec(line);
43034
+ if (sm && javaExpressionLooksLikeShellArgv(sm[1].trim(), l, types, sourceLines, ctx)) {
43035
+ return true;
43036
+ }
43037
+ const fm = fieldAssign.exec(line);
43038
+ if (fm && callArgLooksLikeShellArgv(fm[1].trim(), "new ProcessBuilder(")) return true;
43039
+ }
43040
+ return false;
43041
+ }
43042
+ function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, calls, sourceLines) {
40698
43043
  if (!methodName) return false;
40699
43044
  for (const t of types) {
40700
43045
  const m = t.methods.find((mm) => mm.name === methodName && sinkLine >= mm.start_line && sinkLine <= mm.end_line);
40701
43046
  if (!m) continue;
40702
43047
  const pos = m.parameters.findIndex((p) => p.name === paramName);
40703
- if (pos < 0) return false;
43048
+ if (pos < 0) {
43049
+ const isField = t.fields?.some((f) => f.name === paramName);
43050
+ if (!isField) return false;
43051
+ return calls.some((c) => {
43052
+ if (c.method_name !== methodName) return false;
43053
+ if (c.receiver != null && c.receiver !== "this") return false;
43054
+ if (c.location.line < t.start_line || c.location.line > t.end_line) return false;
43055
+ if (c.arguments.length !== m.parameters.length) return false;
43056
+ const callerMethod = t.methods.find(
43057
+ (mm) => c.location.line >= mm.start_line && c.location.line <= mm.end_line
43058
+ );
43059
+ if (!callerMethod) return false;
43060
+ return javaFieldShellArgvWrittenBeforeLine(
43061
+ paramName,
43062
+ c.location.line,
43063
+ callerMethod.start_line,
43064
+ callerMethod.end_line,
43065
+ sourceLines,
43066
+ types
43067
+ );
43068
+ });
43069
+ }
40704
43070
  return calls.some((c) => {
40705
43071
  if (c.method_name !== methodName) return false;
40706
43072
  if (c.receiver != null && c.receiver !== "this") return false;
@@ -40708,8 +43074,30 @@ function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, c
40708
43074
  if (c.arguments.length !== m.parameters.length) return false;
40709
43075
  const shadowed = types.some((u) => u !== t && u.start_line >= t.start_line && u.end_line <= t.end_line && (u.start_line > t.start_line || u.end_line < t.end_line) && c.location.line >= u.start_line && c.location.line <= u.end_line && u.methods.some((um) => um.name === methodName && um.parameters.length === c.arguments.length));
40710
43076
  if (shadowed) return false;
40711
- const expr = c.arguments[pos]?.expression;
40712
- return typeof expr === "string" && argvFirstLiteralIsShell(`new ProcessBuilder(${expr}`);
43077
+ const expr = (c.arguments[pos]?.expression ?? "").trim();
43078
+ if (!expr) return false;
43079
+ if (javaExpressionLooksLikeShellArgv(expr, c.location.line, types, sourceLines)) {
43080
+ return true;
43081
+ }
43082
+ const argVar = c.arguments[pos]?.variable;
43083
+ if (argVar && expr === argVar && !/[.(]/.test(expr)) {
43084
+ const callerMethod = t.methods.find(
43085
+ (mm) => c.location.line >= mm.start_line && c.location.line <= mm.end_line
43086
+ );
43087
+ if (callerMethod) {
43088
+ const rhs = javaLocalInitRhsBeforeLine(
43089
+ argVar,
43090
+ c.location.line,
43091
+ callerMethod.start_line,
43092
+ callerMethod.end_line,
43093
+ sourceLines
43094
+ );
43095
+ if (rhs && javaExpressionLooksLikeShellArgv(rhs, c.location.line, types, sourceLines)) {
43096
+ return true;
43097
+ }
43098
+ }
43099
+ }
43100
+ return false;
40713
43101
  });
40714
43102
  }
40715
43103
  return false;
@@ -40764,12 +43152,42 @@ var FETCH_GLOBAL_RECEIVER_RE = /(?:^|[^.\w])(?:window|globalThis|self|global)\s*
40764
43152
  var FETCH_MEMBER_RECEIVER_RE = /[\w$)\]]\s*\.\s*fetch\s*\(/;
40765
43153
  var NOSQL_ARRAY_METHODS = /* @__PURE__ */ new Set(["find", "filter", "some", "every", "findIndex", "findLast"]);
40766
43154
  var FUNCTION_FIRST_ARG_RE = /\.\s*(?:find|filter|some|every|findIndex|findLast)\s*\(\s*(?:async\s+)?(?:function\b|(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>)/;
43155
+ var NAMED_ARRAY_PREDICATE_RE = /([A-Za-z_$][\w$]*)\s*\.\s*(find|filter|some|every|findIndex|findLast)\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/;
43156
+ function isDeclaredFunctionName(name2, lines) {
43157
+ const n = name2.replace(/[$]/g, "\\$");
43158
+ const decls = [
43159
+ new RegExp(`\\bfunction\\s+${n}\\s*[(<]`),
43160
+ new RegExp(`\\b(?:const|let|var)\\s+${n}\\s*(?::[^=]*)?=\\s*(?:async\\s+)?(?:function\\b|\\(|[A-Za-z_$][\\w$]*\\s*=>)`)
43161
+ ];
43162
+ return lines.some((l) => decls.some((re) => re.test(l)));
43163
+ }
43164
+ function isDeclaredArrayName(name2, lines) {
43165
+ const n = name2.replace(/[$]/g, "\\$");
43166
+ const re = new RegExp(
43167
+ `\\b${n}\\s*(?:\\??\\s*:\\s*(?:readonly\\s+)?(?:[\\w$.<>,\\s]+\\[\\]|(?:Readonly)?Array\\s*<)|(?::[^=;]*)?=\\s*(?:\\[|new\\s+Array\\b|Array\\.(?:from|of)\\b))`
43168
+ );
43169
+ return lines.some((l) => re.test(l));
43170
+ }
40767
43171
  var ORM_BUILDER_KEY_RE = /[{,]\s*(?:where|select|populate|include|orderBy|relations|attributes)\s*:/;
40768
43172
  var BROWSER_COMPONENT_EXT_RE = /\.(?:jsx|tsx|vue|svelte)$/;
40769
43173
  var CLIENT_SIGNAL_RE = /["']use client["']|\buse(?:State|Effect|Ref|Callback|Memo|Context|Reducer|LayoutEffect)\s*\(|\b(?:window|document|localStorage|sessionStorage|navigator)\s*\.|\son(?:Click|Change|Submit|Input|KeyDown|KeyUp|MouseOver|Focus|Blur)\s*=/;
40770
43174
  var SERVER_SIGNAL_RE = /["']use server["']|\b(?:getServerSideProps|getStaticProps|getStaticPaths)\b|from\s+["']next\/server["']|\bNext(?:Api)?(?:Request|Response)\b|from\s+["'](?:node:)?(?:fs|child_process|http|https|net|dns|dgram)["']|\bexport\s+(?:default\s+)?(?:async\s+)?function\s+handler\s*\(/;
40771
43175
  var FIXED_HOST_TEMPLATE_RE = /`https?:\/\/[^/${}\s`]+[/`]/;
40772
43176
  var BASH_FIXED_HOST_URL_RE = /^(?:https?|ftp):\/\/[A-Za-z0-9.-]+(?::\d+)?\//;
43177
+ var RUST_FIXED_HOST_FORMAT_RE = /format!\(\s*"https?:\/\/[^"{}]+\/\{\}"/;
43178
+ function rustSsrfRequestUsesFixedHostFormat(code, sinkLine) {
43179
+ const lines = code.split("\n");
43180
+ const sinkText = lines[sinkLine - 1] ?? "";
43181
+ const argMatch = sinkText.match(/(?:reqwest::)?get\s*\(\s*&?(\w+)/);
43182
+ if (!argMatch) return false;
43183
+ const varName = argMatch[1];
43184
+ const assignRe = new RegExp(`\\blet\\s+${escapeRegex2(varName)}\\s*=\\s*format!`);
43185
+ for (let i2 = sinkLine - 2; i2 >= Math.max(0, sinkLine - 20); i2--) {
43186
+ const line = lines[i2] ?? "";
43187
+ if (assignRe.test(line) && RUST_FIXED_HOST_FORMAT_RE.test(line)) return true;
43188
+ }
43189
+ return false;
43190
+ }
40773
43191
  function literalEndsInitializer(code, end) {
40774
43192
  let i2 = end;
40775
43193
  let crossedNewline = false;
@@ -40941,7 +43359,9 @@ function isUnwrittenLocalMapKeyRead(code, source) {
40941
43359
  if (!read) return false;
40942
43360
  const [, recv, key] = read;
40943
43361
  const ctorRe = new RegExp(
40944
- `\\b${recv}\\s*=\\s*new\\s+(?:Hash|Tree|LinkedHash|ConcurrentHash|Concurrent|Identity|Weak)?Map\\s*(?:<[^>]*>)?\\s*\\(`
43362
+ // `new HashMap<>()` or the qualified `new java.util.HashMap<>()` (OWASP
43363
+ // BenchmarkJava writes the qualified form throughout).
43364
+ `\\b${recv}\\s*=\\s*new\\s+(?:java\\.util\\.(?:concurrent\\.)?)?(?:Hash|Tree|LinkedHash|ConcurrentHash|Concurrent|Identity|Weak)?Map\\s*(?:<[^>]*>)?\\s*\\(`
40945
43365
  );
40946
43366
  if (!lines.some((l) => ctorRe.test(l))) return false;
40947
43367
  const escapeRe2 = new RegExp(`\\w\\s*\\([^)]*(?<![\\w.])${recv}(?![\\w.])[^)]*\\)`);
@@ -40951,26 +43371,46 @@ function isUnwrittenLocalMapKeyRead(code, source) {
40951
43371
  if (escapeRe2.test(stripped)) return false;
40952
43372
  }
40953
43373
  const putRe = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`);
40954
- if (lines.some((l) => putRe.test(l))) return isLiteralOnlyLocalMap(lines, recv);
43374
+ if (lines.some((l) => putRe.test(l))) return isLiteralOnlyLocalMapKey(lines, recv, key);
40955
43375
  return true;
40956
43376
  }
40957
- function isLiteralOnlyLocalMap(lines, recv) {
40958
- const anyPut = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(`, "g");
40959
- const literalPut = new RegExp(
40960
- `\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*(?:"(?:[^"\\\\]|\\\\.)*"|[A-Za-z_][\\w.]*)\\s*,\\s*"(?:[^"\\\\]|\\\\.)*"\\s*\\)`,
40961
- "g"
40962
- );
43377
+ function isLiteralOnlyLocalMapKey(lines, recv, key) {
43378
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
40963
43379
  const otherMutator = new RegExp(
40964
43380
  `\\b${recv}\\s*\\.\\s*(?:putAll|putIfAbsent|compute|computeIfAbsent|computeIfPresent|merge|replace|replaceAll|entrySet|values)\\s*\\(`
40965
43381
  );
40966
- let puts = 0;
40967
- let literalPuts = 0;
43382
+ if (lines.some((l) => otherMutator.test(l))) return false;
43383
+ const keyPut = new RegExp(
43384
+ `\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${escapedKey}"\\s*,\\s*"(?:[^"\\\\]|\\\\.)*"\\s*\\)`
43385
+ );
43386
+ const anyKeyPut = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${escapedKey}"`);
43387
+ let putsForKey = 0;
43388
+ let literalPutsForKey = 0;
40968
43389
  for (const l of lines) {
40969
- if (otherMutator.test(l)) return false;
40970
- puts += (l.match(anyPut) ?? []).length;
40971
- literalPuts += (l.match(literalPut) ?? []).length;
43390
+ putsForKey += (l.match(anyKeyPut) ?? []).length;
43391
+ literalPutsForKey += (l.match(keyPut) ?? []).length;
40972
43392
  }
40973
- return puts > 0 && puts === literalPuts;
43393
+ return putsForKey > 0 && putsForKey === literalPutsForKey;
43394
+ }
43395
+ function isJsDisguisedCacheDbInputRead(code, source) {
43396
+ if (source.type !== "db_input") return false;
43397
+ const loc = source.location ?? "";
43398
+ if (!/\.cache\.get\s*\(/i.test(loc) && !/\bcache\.get\s*\(/i.test(loc)) return false;
43399
+ const lines = code.split("\n");
43400
+ const lineText = lines[source.line - 1] ?? "";
43401
+ if (/\w+\.cache\.get\s*\(/.test(lineText)) return true;
43402
+ const read = /\b([A-Za-z_]\w*)\s*\.\s*get\s*\(/.exec(lineText);
43403
+ if (!read) return false;
43404
+ const recv = read[1];
43405
+ if (recv !== "cache") return false;
43406
+ const mapCtor = new RegExp(
43407
+ `\\b${recv}\\s*=\\s*new\\s+(?:Hash|Tree|LinkedHash|ConcurrentHash|Concurrent|Identity|Weak)?Map\\s*(?:<[^>]*>)?\\s*\\(`
43408
+ );
43409
+ if (lines.some((l) => mapCtor.test(l))) return true;
43410
+ const redisClient = new RegExp(
43411
+ `\\b${recv}\\s*=\\s*(?:await\\s+)?(?:redis\\.)?createClient\\s*\\(`
43412
+ );
43413
+ return !lines.some((l) => redisClient.test(l));
40974
43414
  }
40975
43415
  function csharpContentServesHtml(call) {
40976
43416
  if (call.receiver !== null && call.receiver !== "this" && call.receiver !== "base") return false;
@@ -40978,8 +43418,8 @@ function csharpContentServesHtml(call) {
40978
43418
  if (contentType === void 0) return true;
40979
43419
  const literal = /^@?"([^"]*)"$/.exec(contentType);
40980
43420
  if (literal) return /^\s*(?:text\/html|text\/xml|application\/xml|[\w.-]+\/[\w.-]+\+xml)\b/i.test(literal[1]);
40981
- const named = /^(?:System\s*\.\s*Net\s*\.\s*Mime\s*\.\s*)?MediaTypeNames\s*\.\s*(Text|Application)\s*\.\s*(\w+)$/.exec(contentType);
40982
- if (named) return named[2] === "Html" || named[2] === "Xml";
43421
+ const named2 = /^(?:System\s*\.\s*Net\s*\.\s*Mime\s*\.\s*)?MediaTypeNames\s*\.\s*(Text|Application)\s*\.\s*(\w+)$/.exec(contentType);
43422
+ if (named2) return named2[2] === "Html" || named2[2] === "Xml";
40983
43423
  return true;
40984
43424
  }
40985
43425
  function csharpContentCallInheritsHelper(call, types, sourceLines) {
@@ -41146,7 +43586,7 @@ function findEnclosingMethodFromIr(types, sinkLine) {
41146
43586
  return null;
41147
43587
  }
41148
43588
  function splitJavaConcatTokens(rhs) {
41149
- const tokens = [];
43589
+ const tokens2 = [];
41150
43590
  let cur = "";
41151
43591
  let depth = 0;
41152
43592
  let inString = false;
@@ -41183,7 +43623,7 @@ function splitJavaConcatTokens(rhs) {
41183
43623
  continue;
41184
43624
  }
41185
43625
  if (ch === "+" && depth === 0) {
41186
- tokens.push(cur.trim());
43626
+ tokens2.push(cur.trim());
41187
43627
  cur = "";
41188
43628
  i2++;
41189
43629
  continue;
@@ -41191,8 +43631,8 @@ function splitJavaConcatTokens(rhs) {
41191
43631
  cur += ch;
41192
43632
  i2++;
41193
43633
  }
41194
- if (cur.trim()) tokens.push(cur.trim());
41195
- return tokens;
43634
+ if (cur.trim()) tokens2.push(cur.trim());
43635
+ return tokens2;
41196
43636
  }
41197
43637
  function isImplicitlyAnchoredAllowlistRegex(re) {
41198
43638
  if (re === "") return false;
@@ -41297,7 +43737,7 @@ var SinkFilterPass = class {
41297
43737
  const executableSources = ctx.code && ctx.language ? mergedSources.filter(
41298
43738
  (s) => !isNonExecutableSourceLine(ctx.code, s.line, ctx.language)
41299
43739
  ) : mergedSources;
41300
- const sources = ctx.code ? executableSources.filter((s) => !isUnwrittenLocalMapKeyRead(ctx.code, s)) : executableSources;
43740
+ const sources = ctx.code ? executableSources.filter((s) => !isUnwrittenLocalMapKeyRead(ctx.code, s)).filter((s) => !isJsDisguisedCacheDbInputRead(ctx.code, s)) : executableSources;
41301
43741
  const sinks = [...taintMatcher.sinks];
41302
43742
  for (const s of langSources.additionalSinks) {
41303
43743
  const springReturn = s.type === "xss" && typeof s.location === "string" && s.location.startsWith("Spring handler returns HTML");
@@ -41605,7 +44045,7 @@ var SinkFilterPass = class {
41605
44045
  const varName = arg0.variable ?? "";
41606
44046
  if (varName !== "" && argExpr === varName && !/[(.]/.test(argExpr)) {
41607
44047
  const t = resolveIdentifierType(varName, ctorCall.in_method, irTypes);
41608
- if (isArgvContainerType(t) && !sameFileCallerPassesShellArgv(varName, ctorCall.in_method, ctorCall.location.line, irTypes, irCalls)) {
44048
+ if (isArgvContainerType(t) && !sameFileCallerPassesShellArgv(varName, ctorCall.in_method, ctorCall.location.line, irTypes, irCalls, sourceLines)) {
41609
44049
  return false;
41610
44050
  }
41611
44051
  }
@@ -41788,11 +44228,11 @@ var SinkFilterPass = class {
41788
44228
  rhs = sqlVar;
41789
44229
  }
41790
44230
  if (!rhs) return true;
41791
- const tokens = splitJavaConcatTokens(rhs);
41792
- if (tokens.length < 2) return true;
44231
+ const tokens2 = splitJavaConcatTokens(rhs);
44232
+ if (tokens2.length < 2) return true;
41793
44233
  let hasPlaceholder = false;
41794
44234
  const methodCallNames = [];
41795
- for (const tk of tokens) {
44235
+ for (const tk of tokens2) {
41796
44236
  if (/^"(?:[^"\\]|\\.)*"$/.test(tk)) {
41797
44237
  if (tk.includes("?")) hasPlaceholder = true;
41798
44238
  continue;
@@ -41855,7 +44295,15 @@ var SinkFilterPass = class {
41855
44295
  if (sink.type !== "nosql_injection") return true;
41856
44296
  if (!NOSQL_ARRAY_METHODS.has(sink.method ?? "")) return true;
41857
44297
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
41858
- return !FUNCTION_FIRST_ARG_RE.test(sinkLineText);
44298
+ if (FUNCTION_FIRST_ARG_RE.test(sinkLineText)) return false;
44299
+ const named2 = NAMED_ARRAY_PREDICATE_RE.exec(sinkLineText);
44300
+ if (named2) {
44301
+ const [, receiver, method, arg] = named2;
44302
+ if (NOSQL_ARRAY_METHODS.has(method) && (isDeclaredFunctionName(arg, sourceLines) || isDeclaredArrayName(receiver, sourceLines))) {
44303
+ return false;
44304
+ }
44305
+ }
44306
+ return true;
41859
44307
  });
41860
44308
  }
41861
44309
  if (["javascript", "typescript"].includes(language)) {
@@ -41972,6 +44420,12 @@ var SinkFilterPass = class {
41972
44420
  return bodyCalls.length === 0 || !bodyCalls.every(rustBodyDeclaresNonHtmlType);
41973
44421
  });
41974
44422
  }
44423
+ if (language === "rust") {
44424
+ filtered = filtered.filter((sink) => {
44425
+ if (sink.type !== "ssrf") return true;
44426
+ return !rustSsrfRequestUsesFixedHostFormat(ctx.code, sink.line);
44427
+ });
44428
+ }
41975
44429
  if (language === "bash") {
41976
44430
  const sourceLines = ctx.code.split("\n");
41977
44431
  filtered = filtered.filter((sink) => {
@@ -42494,7 +44948,9 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
42494
44948
  return sinks.filter((sink) => {
42495
44949
  const callsAtSink = callsByLine.get(sink.line) ?? [];
42496
44950
  const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
42497
- const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
44951
+ const relevantCalls = sink.method ? callsAtSink.filter(
44952
+ (c) => c.method_name === sink.method && (sink.callColumn === void 0 || c.location.column === sink.callColumn)
44953
+ ) : callsAtSink;
42498
44954
  const trustArgPositions = language !== "bash" && language !== "shell";
42499
44955
  for (const call of relevantCalls) {
42500
44956
  let allArgsAreClean = true;
@@ -43259,19 +45715,59 @@ function rustAttributesAbove(lines, startLine) {
43259
45715
  continue;
43260
45716
  }
43261
45717
  if (t === "" || t.startsWith("//")) continue;
45718
+ if (out2.length > 0) break;
43262
45719
  break;
43263
45720
  }
43264
45721
  return out2;
43265
45722
  }
45723
+ function isModuleLevelFunction(method, lines) {
45724
+ const first = lines[method.start_line - 1] ?? "";
45725
+ return !/^\s/.test(first);
45726
+ }
45727
+ function jsTsExportBinding(exportName, lines) {
45728
+ const esc = exportName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45729
+ const text = lines.join("\n");
45730
+ const first = lines.find((l) => /^\s*(?:export|function|const|async)/.test(l)) ?? "";
45731
+ if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+)/.test(first.trimStart()) && new RegExp(`\\b${esc}\\b`).test(first)) {
45732
+ return true;
45733
+ }
45734
+ if (new RegExp(`(?:^|\\n)\\s*export\\s*\\{[^}]*\\b${esc}\\b`).test(text)) return true;
45735
+ if (new RegExp(`(?:^|[^\\w$.])(?:module\\s*\\.\\s*)?exports\\s*\\.\\s*${esc}\\s*=`).test(text)) return true;
45736
+ if (new RegExp(`module\\s*\\.\\s*exports\\s*=\\s*\\{[^}]*\\b${esc}\\b`).test(text)) return true;
45737
+ return false;
45738
+ }
45739
+ var CHANNEL_EXPORT_HANDLER_NAMES = /* @__PURE__ */ new Set([
45740
+ "handle",
45741
+ "getUser",
45742
+ "onMessage",
45743
+ "onWebhook",
45744
+ "resolveUser",
45745
+ "sse"
45746
+ ]);
45747
+ var CHANNEL_HANDLER_PARAM = /^(event|req|request|ctx|context|message|payload|topic|id|user|_|args|info)$/;
45748
+ function hasChannelHandlerParamSignature(method) {
45749
+ if (method.parameters.length === 0) return false;
45750
+ return method.parameters.some((p) => CHANNEL_HANDLER_PARAM.test(p.name));
45751
+ }
45752
+ function isChannelExportedHandler(method, language, lines) {
45753
+ if (language !== "javascript" && language !== "typescript") return false;
45754
+ if (!isModuleLevelFunction(method, lines)) return false;
45755
+ if (!CHANNEL_EXPORT_HANDLER_NAMES.has(method.name)) return false;
45756
+ if (!hasChannelHandlerParamSignature(method)) return false;
45757
+ return jsTsExportBinding(method.name, lines);
45758
+ }
43266
45759
  function isLambdaEntryPoint(method, language, lines) {
43267
45760
  const names = method.parameters.map((p) => p.name);
43268
45761
  const first = lines[method.start_line - 1] ?? "";
43269
- if (/^\s/.test(first)) return false;
45762
+ if (!isModuleLevelFunction(method, lines)) return false;
43270
45763
  if (language === "python") {
43271
45764
  return method.name === "lambda_handler" && names.length === 2 && names[0] === "event" && names[1] === "context";
43272
45765
  }
43273
45766
  if (language !== "javascript" && language !== "typescript") return false;
43274
45767
  if (method.name !== "handler") return false;
45768
+ if (names.length === 1 && names[0] === "event") {
45769
+ return jsTsExportBinding("handler", lines) || /^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+handler\b)/.test(first);
45770
+ }
43275
45771
  if (names.length < 2 || names.length > 3 || names[0] !== "event" || names[1] !== "context") return false;
43276
45772
  if (names.length === 3 && names[2] !== "callback") return false;
43277
45773
  if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+handler\b)/.test(first)) return true;
@@ -43281,6 +45777,7 @@ function isLambdaEntryPoint(method, language, lines) {
43281
45777
  function isFrameworkHandler(method, language, lines) {
43282
45778
  if (method.annotations.some((a) => ROUTE_ANNOTATION.test(a))) return true;
43283
45779
  if (isLambdaEntryPoint(method, language, lines)) return true;
45780
+ if (isChannelExportedHandler(method, language, lines)) return true;
43284
45781
  if (language === "rust") {
43285
45782
  if (rustAttributesAbove(lines, method.start_line).some((a) => RUST_ROUTE_ATTR.test(a))) return true;
43286
45783
  if (method.return_type && RUST_HANDLER_RETURN.test(method.return_type)) return true;
@@ -43296,6 +45793,20 @@ function isReactHtmlComponent(m, f, lines) {
43296
45793
  const sig = lines[m.start_line - 1] ?? "";
43297
45794
  return /\bfunction\s+[A-Z][A-Za-z0-9]*\s*\(\s*\{/.test(sig);
43298
45795
  }
45796
+ function isRustDieselSqlParamFlow(f, lines) {
45797
+ if (f.source_type !== "interprocedural_param" || f.sink_type !== "sql_injection") return false;
45798
+ const sinkLine = lines[f.sink_line - 1] ?? "";
45799
+ return /\b(?:sql_query|raw_sql)\s*\(/.test(sinkLine);
45800
+ }
45801
+ function isPythonYamlLoadParamDeser(f, lines) {
45802
+ if (f.source_type !== "interprocedural_param" || f.sink_type !== "deserialization") return false;
45803
+ const head = f.path[0]?.variable;
45804
+ if (!head || f.path.length < 2) return false;
45805
+ const sinkLine = lines[f.sink_line - 1] ?? "";
45806
+ if (!/\byaml\.load\s*\(/.test(sinkLine) || /\bLoader\s*=\s*yaml\.SafeLoader/.test(sinkLine)) return false;
45807
+ const esc = head.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45808
+ return new RegExp(`\\byaml\\.load\\s*\\(\\s*${esc}\\b`).test(sinkLine);
45809
+ }
43299
45810
  var FIELD_CARRIED_SOURCE = /constructor-injected field|returns tainted field/;
43300
45811
  var EXECUTOR_CALLEES = /* @__PURE__ */ new Map([
43301
45812
  ["submit", 0],
@@ -43416,8 +45927,8 @@ var ParamSourceFlowGatePass = class {
43416
45927
  const viaReceiver = !isCtor && !!owner && owner.name !== "<module>";
43417
45928
  const feeds = (pos) => fed.params.has(`${viaReceiver ? "m" : "f"}:${callee}:${pos}`);
43418
45929
  const head = f.path[0]?.variable;
43419
- const named = m.parameters.findIndex((p) => p.name === head);
43420
- if (named >= 0) return feeds(named);
45930
+ const named2 = m.parameters.findIndex((p) => p.name === head);
45931
+ if (named2 >= 0) return feeds(named2);
43421
45932
  for (const s of sources) {
43422
45933
  if (s.line !== f.source_line || s.type !== "interprocedural_param") continue;
43423
45934
  if (FIELD_CARRIED_SOURCE.test(s.location ?? "")) {
@@ -43435,7 +45946,7 @@ var ParamSourceFlowGatePass = class {
43435
45946
  let keep = true;
43436
45947
  if (f.source_type === "interprocedural_param" && !realSourceLines.has(f.source_line)) {
43437
45948
  const m = enclosingMethod(methods, f.source_line);
43438
- keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines));
45949
+ keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines) || ctx.language === "python" && isPythonYamlLoadParamDeser(f, lines) || ctx.language === "rust" && isRustDieselSqlParamFlow(f, lines));
43439
45950
  }
43440
45951
  if (keep) flows[w++] = f;
43441
45952
  else {
@@ -44068,6 +46579,32 @@ function javaReceiverChainDerivedVar(rhsByLocal, localVar, seedVar) {
44068
46579
  const receiverOnRhs = new RegExp(`(?:^|[^\\w.$])${esc}\\s*\\.`, "u");
44069
46580
  return rhsList.some((rhs) => receiverOnRhs.test(rhs));
44070
46581
  }
46582
+ function defLineForUse(graph, varName, useLine) {
46583
+ const own = (graph.defsByLine.get(useLine) ?? []).find((d2) => d2.variable === varName);
46584
+ if (own) return own.line;
46585
+ const use = (graph.usesByLine.get(useLine) ?? []).find((u) => u.variable === varName && u.def_id != null);
46586
+ const d = use ? graph.defById.get(use.def_id) : void 0;
46587
+ return d?.line ?? useLine;
46588
+ }
46589
+ function constPropFlowLookup(language, method, varName, blockLine, constProp, preferMethodScoped = false) {
46590
+ if (language === "go") {
46591
+ return constPropScopedVar(method, varName, blockLine, constProp);
46592
+ }
46593
+ if (!method || varName.includes(":")) return varName;
46594
+ const blockScoped = constPropScopedVar(method, varName, blockLine, constProp);
46595
+ const hasTaintBinding = (name2) => constProp.tainted.has(name2) || constProp.sanitizedVars.has(name2);
46596
+ if (blockScoped.includes(":b") && hasTaintBinding(blockScoped)) {
46597
+ return blockScoped;
46598
+ }
46599
+ const methodScoped = `${method}:${varName}`;
46600
+ if (hasTaintBinding(methodScoped)) {
46601
+ return methodScoped;
46602
+ }
46603
+ if (preferMethodScoped && method) {
46604
+ return methodScoped;
46605
+ }
46606
+ return constProp.lineBlockScopes?.has(blockLine) ? blockScoped : varName;
46607
+ }
44071
46608
  function indexJavaLocalAssignments(code) {
44072
46609
  const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
44073
46610
  const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
@@ -44085,6 +46622,32 @@ function indexJavaLocalAssignments(code) {
44085
46622
  }
44086
46623
  return index;
44087
46624
  }
46625
+ function goCalleeBaseName(callee) {
46626
+ const dot = callee.lastIndexOf(".");
46627
+ return dot === -1 ? callee : callee.slice(dot + 1);
46628
+ }
46629
+ function goConstantHelperCalleeOnLine(line) {
46630
+ const m = line.match(/:=\s*([\w.]+)\s*\(/) ?? line.match(/\bvar\s+\w+\s*=\s*([\w.]+)\s*\(/);
46631
+ if (!m) return null;
46632
+ return goCalleeBaseName(m[1]);
46633
+ }
46634
+ function goConstantHelperCalleeFromLines(lines, lineNumber) {
46635
+ const direct = goConstantHelperCalleeOnLine(lines[lineNumber - 1] ?? "");
46636
+ if (direct) return direct;
46637
+ const cur = lines[lineNumber - 1] ?? "";
46638
+ const partial = cur.match(/:=\s*([\w.]+)\s*$/) ?? cur.match(/\bvar\s+\w+\s*=\s*([\w.]+)\s*$/);
46639
+ if (partial) {
46640
+ const next = (lines[lineNumber] ?? "").trimStart();
46641
+ if (next.startsWith("(")) return goCalleeBaseName(partial[1]);
46642
+ }
46643
+ if (!cur.includes(":=") && !/\bvar\s+\w+\s*=/.test(cur)) return null;
46644
+ if (cur.includes("(")) return null;
46645
+ for (let i2 = lineNumber + 1; i2 <= Math.min(lines.length, lineNumber + 4); i2++) {
46646
+ const m = (lines[i2 - 1] ?? "").match(/^\s*([\w.]+)\s*\(/);
46647
+ if (m) return goCalleeBaseName(m[1]);
46648
+ }
46649
+ return null;
46650
+ }
44088
46651
  function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
44089
46652
  const lines = typeof code === "string" ? code.split("\n") : [];
44090
46653
  const selfReassign = /* @__PURE__ */ new Map();
@@ -44113,10 +46676,24 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44113
46676
  };
44114
46677
  return (step, sourceLine, sinkLine, sinkType) => {
44115
46678
  const method = methodAtLine(graph, step.line);
44116
- const scopedVar = method ? `${method}:${step.variable}` : step.variable;
44117
- const r = isFalsePositive(constProp, step.line, language === "go" ? scopedVar : step.variable);
46679
+ const blockLine = defLineForUse(graph, step.variable, step.line);
46680
+ const fpLookup = constPropFlowLookup(
46681
+ language,
46682
+ method,
46683
+ step.variable,
46684
+ blockLine,
46685
+ constProp
46686
+ );
46687
+ const r = isFalsePositive(constProp, step.line, fpLookup);
44118
46688
  if (!r.isFalsePositive) return false;
44119
46689
  if (r.reason !== "variable_not_tainted") return true;
46690
+ if (r.reason === "variable_not_tainted" && language === "java" && method && fpLookup === step.variable && !step.variable.includes(".")) {
46691
+ const v = step.variable;
46692
+ if (constProp.tainted.has(`${method}:${v}`)) return false;
46693
+ for (const t of constProp.tainted) {
46694
+ if (t.startsWith(`${method}:b`) && t.endsWith(`:${v}`)) return false;
46695
+ }
46696
+ }
44120
46697
  if (sinkType !== void 0 && constProp.sanitizedVarUncovered) {
44121
46698
  const unc = constProp.sanitizedVarUncovered;
44122
46699
  if (step.variable.includes(":")) {
@@ -44124,7 +46701,9 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44124
46701
  } else {
44125
46702
  const method2 = methodAtLine(graph, step.line);
44126
46703
  if (method2) {
44127
- if (unc.get(`${method2}:${step.variable}`)?.has(sinkType)) return false;
46704
+ if (unc.get(fpLookup)?.has(sinkType)) {
46705
+ return false;
46706
+ }
44128
46707
  } else if (unc.get(step.variable)?.has(sinkType)) {
44129
46708
  return false;
44130
46709
  }
@@ -44162,10 +46741,10 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44162
46741
  }
44163
46742
  if (!d) return true;
44164
46743
  if (d.line === sourceLine && sources.some((s) => s.line === sourceLine && !CONTAINER_READ_SOURCES.has(s.type))) {
44165
- if (step.line === sinkLine && !constProp.tainted.has(scopedVar)) {
46744
+ if (step.line === sinkLine && !constProp.tainted.has(fpLookup)) {
44166
46745
  for (let ln = sourceLine; ln <= sinkLine; ln++) {
44167
- const calleeMatch = (lines[ln - 1] ?? "").match(/:=\s*(\w+)\s*\(/);
44168
- if (calleeMatch && constProp.constantReturningMethods?.has(calleeMatch[1])) {
46746
+ const callee = goConstantHelperCalleeFromLines(lines, ln);
46747
+ if (callee && constProp.constantReturningMethods?.has(callee)) {
44169
46748
  return true;
44170
46749
  }
44171
46750
  }
@@ -44341,7 +46920,15 @@ var TaintPropagationPass = class {
44341
46920
  for (const f of arrayFlows) {
44342
46921
  pushIfNew(f);
44343
46922
  }
44344
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
46923
+ const collectionFlows = detectCollectionFlows(
46924
+ calls,
46925
+ sources,
46926
+ sinks,
46927
+ constProp,
46928
+ ctx.code,
46929
+ types,
46930
+ graph
46931
+ ) ?? [];
44345
46932
  for (const f of collectionFlows) {
44346
46933
  if (flowKeys.has(flowKey(f))) continue;
44347
46934
  const flowForCheck = {
@@ -44360,7 +46947,17 @@ var TaintPropagationPass = class {
44360
46947
  if (isFP) continue;
44361
46948
  pushIfNew(f);
44362
46949
  }
44363
- const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
46950
+ const paramFlows = detectParameterSinkFlows(
46951
+ types,
46952
+ calls,
46953
+ sources,
46954
+ sinks,
46955
+ constProp.unreachableLines,
46956
+ constProp.tainted,
46957
+ ctx.code,
46958
+ ctx.language,
46959
+ graph.ir.meta.file
46960
+ ) ?? [];
44364
46961
  for (const f of paramFlows) {
44365
46962
  pushIfNew(f);
44366
46963
  }
@@ -44395,6 +46992,17 @@ var TaintPropagationPass = class {
44395
46992
  pushIfNew(f);
44396
46993
  }
44397
46994
  }
46995
+ if ((ctx.language === "javascript" || ctx.language === "typescript") && typeof ctx.code === "string") {
46996
+ const jsModuleFlows = detectJsModuleScopeFlows(
46997
+ ctx.code,
46998
+ sources,
46999
+ sinks,
47000
+ constProp.unreachableLines
47001
+ ) ?? [];
47002
+ for (const f of jsModuleFlows) {
47003
+ pushIfNew(f);
47004
+ }
47005
+ }
44398
47006
  const sanitizedNames = constProp.sanitizedVars;
44399
47007
  const sanitizedUncovered = constProp.sanitizedVarUncovered;
44400
47008
  let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
@@ -44424,6 +47032,22 @@ var TaintPropagationPass = class {
44424
47032
  if (sanitizedNames.has(sourceVar) && coversFlow(sourceVar)) return false;
44425
47033
  return true;
44426
47034
  });
47035
+ if (ctx.language === "csharp") {
47036
+ finalFlows = finalFlows.filter(
47037
+ (f) => !shouldGateCSharpPatPermsLibraryParamFlow(
47038
+ f,
47039
+ sources,
47040
+ sinks,
47041
+ types,
47042
+ {
47043
+ language: ctx.language,
47044
+ types,
47045
+ filePath: graph.ir.meta.file,
47046
+ calls
47047
+ }
47048
+ )
47049
+ );
47050
+ }
44427
47051
  if (sanitizers && sanitizers.length > 0) {
44428
47052
  const sanitizersByLine = /* @__PURE__ */ new Map();
44429
47053
  for (const san of sanitizers) {
@@ -44808,6 +47432,87 @@ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLin
44808
47432
  }
44809
47433
  return flows;
44810
47434
  }
47435
+ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
47436
+ const flows = [];
47437
+ if (!code || sinks.length === 0) return flows;
47438
+ const lines = code.split("\n");
47439
+ const moduleVars = /* @__PURE__ */ new Set();
47440
+ for (const line of lines) {
47441
+ const stripped = line.replace(/\s+$/, "");
47442
+ if (!stripped || /^[ \t]/.test(line)) continue;
47443
+ const m = stripped.match(/^(?:export\s+)?(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
47444
+ if (m) moduleVars.add(m[1]);
47445
+ }
47446
+ if (moduleVars.size === 0) return flows;
47447
+ const sourceVarNames = /* @__PURE__ */ new Set();
47448
+ for (const s of sources) {
47449
+ if (typeof s.variable === "string" && s.variable.length > 0) {
47450
+ sourceVarNames.add(s.variable);
47451
+ }
47452
+ }
47453
+ const JS_HTTP_SOURCE_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies|get)\b|\breq\.(?:param|query|body)\(/;
47454
+ const writes = [];
47455
+ for (let i2 = 0; i2 < lines.length; i2++) {
47456
+ const line = lines[i2];
47457
+ if (unreachableLines.has(i2 + 1)) continue;
47458
+ for (const lhs of moduleVars) {
47459
+ const declRe = new RegExp(
47460
+ `\\b(?:export\\s+)?(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
47461
+ );
47462
+ if (declRe.test(line)) continue;
47463
+ const m = line.match(
47464
+ new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*([^;]+)`)
47465
+ );
47466
+ if (!m) continue;
47467
+ const rhs = m[1];
47468
+ let taintedBy = null;
47469
+ const onLine = sources.filter((s) => s.line === i2 + 1);
47470
+ if (onLine.length > 0) {
47471
+ taintedBy = { line: onLine[0].line, type: onLine[0].type };
47472
+ } else if (JS_HTTP_SOURCE_RE.test(rhs)) {
47473
+ taintedBy = { line: i2 + 1, type: "http_body" };
47474
+ } else {
47475
+ for (const v of sourceVarNames) {
47476
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
47477
+ if (re.test(rhs)) {
47478
+ const src = sources.find((s) => s.variable === v);
47479
+ if (src) {
47480
+ taintedBy = { line: src.line, type: src.type };
47481
+ break;
47482
+ }
47483
+ }
47484
+ }
47485
+ }
47486
+ if (!taintedBy) continue;
47487
+ writes.push({ varName: lhs, line: i2 + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
47488
+ }
47489
+ }
47490
+ if (writes.length === 0) return flows;
47491
+ for (const sink of sinks) {
47492
+ if (unreachableLines.has(sink.line)) continue;
47493
+ const sinkLineText = lines[sink.line - 1] ?? "";
47494
+ if (!sinkLineText) continue;
47495
+ for (const w of writes) {
47496
+ if (w.line >= sink.line) continue;
47497
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
47498
+ if (!re.test(sinkLineText)) continue;
47499
+ flows.push({
47500
+ source_line: w.sourceLine,
47501
+ sink_line: sink.line,
47502
+ source_type: w.sourceType,
47503
+ sink_type: sink.type,
47504
+ path: [
47505
+ { variable: w.varName, line: w.sourceLine, type: "source" },
47506
+ { variable: w.varName, line: w.line, type: "assignment" },
47507
+ { variable: w.varName, line: sink.line, type: "sink" }
47508
+ ],
47509
+ confidence: 0.9,
47510
+ sanitized: false
47511
+ });
47512
+ }
47513
+ }
47514
+ return flows;
47515
+ }
44811
47516
  function methodHasLocalDecl(code, methodName, varName, atLine) {
44812
47517
  if (!code || !methodName) return false;
44813
47518
  const esc = methodName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -44875,7 +47580,19 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
44875
47580
  }
44876
47581
  return true;
44877
47582
  }
44878
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
47583
+ function mapGetLiteralKeyFromExpr(expr) {
47584
+ const m = expr.match(/\b([A-Za-z_]\w*)\s*\.\s*get\s*\(\s*"([^"]*)"\s*\)/);
47585
+ return m ? { map: m[1], key: m[2] } : null;
47586
+ }
47587
+ function isMapLiteralKeyTainted(constProp, mapName, key, inMethod) {
47588
+ const scoped = inMethod ? `${inMethod}:${mapName}` : mapName;
47589
+ const keys = constProp.taintedCollections.get(mapName) ?? constProp.taintedCollections.get(scoped);
47590
+ if (!keys || keys.size === 0) return false;
47591
+ return keys.has(key) || keys.has("*");
47592
+ }
47593
+ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
47594
+ const taintedVars = constProp.tainted;
47595
+ const unreachableLines = constProp.unreachableLines;
44879
47596
  const flows = [];
44880
47597
  const fieldNames = /* @__PURE__ */ new Set();
44881
47598
  for (const t of types ?? []) for (const f of t.fields ?? []) fieldNames.add(f.name);
@@ -44895,7 +47612,18 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
44895
47612
  }
44896
47613
  if (arg.variable) {
44897
47614
  const varName = arg.variable;
44898
- const scopedName = call.in_method ? `${call.in_method}:${varName}` : varName;
47615
+ let scopedName = varName;
47616
+ if (call.in_method) {
47617
+ const blockLine = graph ? defLineForUse(graph, varName, call.location.line) : call.location.line;
47618
+ scopedName = constPropFlowLookup(
47619
+ "java",
47620
+ call.in_method,
47621
+ varName,
47622
+ blockLine,
47623
+ constProp,
47624
+ true
47625
+ );
47626
+ }
44899
47627
  const expr = arg.expression ?? "";
44900
47628
  const explicitField = isFieldUseExpression(
44901
47629
  expr,
@@ -44904,7 +47632,23 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
44904
47632
  );
44905
47633
  const bareFieldRead = fieldNames.has(varName) && expr === varName && !methodHasLocalDecl(code, call.in_method, varName, call.location.line);
44906
47634
  const isFieldUse = explicitField || bareFieldRead;
44907
- if (isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isFieldUse)) {
47635
+ const mapKeyRead = mapGetLiteralKeyFromExpr(expr);
47636
+ let argTainted = isTaintedInMethod(
47637
+ taintedVars,
47638
+ varName,
47639
+ scopedName,
47640
+ fieldNames,
47641
+ isFieldUse
47642
+ );
47643
+ if (mapKeyRead && mapKeyRead.map === varName) {
47644
+ argTainted = isMapLiteralKeyTainted(
47645
+ constProp,
47646
+ varName,
47647
+ mapKeyRead.key,
47648
+ call.in_method
47649
+ );
47650
+ }
47651
+ if (argTainted) {
44908
47652
  const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, varName);
44909
47653
  if (source) {
44910
47654
  if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
@@ -44939,12 +47683,23 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
44939
47683
  { method: "peek", re: /(\w+)\.peek\(/ },
44940
47684
  { method: "toArray", re: /(\w+)\.toArray\(/ }
44941
47685
  ];
44942
- for (const { re } of collectionPatterns) {
47686
+ for (const { method, re } of collectionPatterns) {
44943
47687
  const match = expr.match(re);
44944
47688
  if (match) {
44945
47689
  const collectionVar = match[1];
44946
47690
  const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
44947
- if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
47691
+ const mapKey = method === "get" ? mapGetLiteralKeyFromExpr(expr) : null;
47692
+ let collectionTainted = taintedVars.has(collectionVar) || taintedVars.has(scopedCollection);
47693
+ if (mapKey && mapKey.map === collectionVar) {
47694
+ collectionTainted = isMapLiteralKeyTainted(
47695
+ constProp,
47696
+ collectionVar,
47697
+ mapKey.key,
47698
+ call.in_method
47699
+ );
47700
+ }
47701
+ if (!collectionTainted) continue;
47702
+ {
44948
47703
  const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
44949
47704
  if (source) {
44950
47705
  if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
@@ -45021,6 +47776,39 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
45021
47776
  }
45022
47777
  return flows;
45023
47778
  }
47779
+ function csharpPatPermsDirectParamSink(sink, paramVar) {
47780
+ const method = (sink.method ?? "").replace(/^.*\./, "");
47781
+ if (method !== "FileStream") return false;
47782
+ const code = sink.code ?? "";
47783
+ if (!code) return true;
47784
+ const escaped = paramVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47785
+ return new RegExp(`\\bFileStream\\s*\\(\\s*${escaped}\\b`).test(code);
47786
+ }
47787
+ function shouldGateCSharpPatPermsLibraryParamFlow(flow, sources, sinks, types, ctx) {
47788
+ if (flow.source_type !== "interprocedural_param" || flow.sink_type !== "path_traversal") {
47789
+ return false;
47790
+ }
47791
+ const srcVar = flow.path?.[0]?.variable;
47792
+ const sinkVar = flow.path?.[flow.path.length - 1]?.variable;
47793
+ if (!srcVar || srcVar !== sinkVar) return false;
47794
+ const sink = sinks.find(
47795
+ (s) => s.line === flow.sink_line && s.type === "path_traversal"
47796
+ );
47797
+ if (!sink || !csharpPatPermsDirectParamSink(sink, srcVar)) return false;
47798
+ const src = sources.find(
47799
+ (s) => s.line === flow.source_line && s.type === "interprocedural_param"
47800
+ );
47801
+ const methodName = src?.in_method;
47802
+ if (!methodName) return false;
47803
+ const enclosingType = types.find((t) => t.methods.some((m) => m.name === methodName));
47804
+ const enclosingMethod2 = enclosingType?.methods.find((m) => m.name === methodName);
47805
+ return shouldGateInterproceduralParam(
47806
+ flow.source_type,
47807
+ enclosingMethod2,
47808
+ enclosingType,
47809
+ ctx
47810
+ );
47811
+ }
45024
47812
  function compoundArgParamNames(expression, paramNames) {
45025
47813
  const stripped = expression.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, (m) => " ".repeat(m.length));
45026
47814
  const matched = [];
@@ -45034,7 +47822,7 @@ function compoundArgParamNames(expression, paramNames) {
45034
47822
  }
45035
47823
  return matched;
45036
47824
  }
45037
- function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
47825
+ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code, language, filePath) {
45038
47826
  const flows = [];
45039
47827
  const paramSourcesByMethod = /* @__PURE__ */ new Map();
45040
47828
  for (const source of sources) {
@@ -45075,6 +47863,20 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
45075
47863
  for (const varName of varNames) {
45076
47864
  const paramSource = methodParamSources.get(varName);
45077
47865
  if (paramSource) {
47866
+ if (language === "csharp" && paramSource.type === "interprocedural_param" && sink.type === "path_traversal" && csharpPatPermsDirectParamSink(sink, varName)) {
47867
+ const enclosingType = types.find(
47868
+ (t) => t.methods.some((m) => m.name === methodName)
47869
+ );
47870
+ const enclosingMethod2 = enclosingType?.methods.find((m) => m.name === methodName);
47871
+ if (shouldGateInterproceduralParam(
47872
+ paramSource.type,
47873
+ enclosingMethod2,
47874
+ enclosingType,
47875
+ { language, types, filePath, calls }
47876
+ )) {
47877
+ continue;
47878
+ }
47879
+ }
45078
47880
  const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
45079
47881
  if (!exists) {
45080
47882
  if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, paramSource.line, sink.line, unreachableLines)) {
@@ -46069,9 +48871,18 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
46069
48871
  if (!prev || s.line < prev.line) realSourceByVar.set(s.variable, s);
46070
48872
  }
46071
48873
  for (const [varName, derivedLine] of derived) {
46072
- if (!varName || existingVars.has(varName)) continue;
48874
+ if (!varName) continue;
46073
48875
  const owner = methodAt(derivedLine);
46074
48876
  const fieldAlias = isDeclaredField(derivedLine, varName);
48877
+ if (existingVars.has(varName)) {
48878
+ if (!fieldAlias) continue;
48879
+ const stubIdx = sourcesWithVar.findIndex(
48880
+ (s) => s.variable === varName && s.type === "interprocedural_param"
48881
+ );
48882
+ if (stubIdx < 0) continue;
48883
+ sourcesWithVar.splice(stubIdx, 1);
48884
+ existingVars.delete(varName);
48885
+ }
46075
48886
  let scopedAnchor = anchor;
46076
48887
  if (owner) {
46077
48888
  let best;
@@ -51430,10 +54241,28 @@ var InsecureCookiePass = class {
51430
54241
  * unassigned `new Cookie(...)` only looks at that statement, so another
51431
54242
  * cookie's setter cannot cover it.
51432
54243
  */
54244
+ /** Cookie variable assigned to this `new Cookie(...)` call site (per-cookie, #595). */
54245
+ javaCookieVarName(code, call) {
54246
+ const lines = code.split("\n");
54247
+ const idx = (call.location.line ?? 1) - 1;
54248
+ const line = lines[idx] ?? "";
54249
+ const sameLine = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(line);
54250
+ if (sameLine?.[1]) return sameLine[1];
54251
+ if (/\bnew\s+(?:[\w.]+\.)?Cookie\s*\(/.test(line)) {
54252
+ for (let i2 = idx - 1; i2 >= Math.max(0, idx - 4); i2--) {
54253
+ const prev = (lines[i2] ?? "").trimEnd();
54254
+ const splitAssign = /\b([A-Za-z_]\w*)\s*=\s*$/.exec(prev);
54255
+ if (splitAssign?.[1]) return splitAssign[1];
54256
+ const prevSame = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(prev);
54257
+ if (prevSame?.[1]) return prevSame[1];
54258
+ }
54259
+ }
54260
+ return void 0;
54261
+ }
51433
54262
  javaCookieSetterTrue(code, call, setter) {
51434
- const line = code.split("\n")[(call.location.line ?? 1) - 1] ?? "";
51435
- const assign = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(line);
51436
- const varName = assign?.[1];
54263
+ const lines = code.split("\n");
54264
+ const line = lines[(call.location.line ?? 1) - 1] ?? "";
54265
+ const varName = this.javaCookieVarName(code, call);
51437
54266
  const re = varName ? new RegExp(`\\b${varName}\\.${setter}\\s*\\(\\s*true\\s*\\)`) : new RegExp(`\\.${setter}\\s*\\(\\s*true\\s*\\)`);
51438
54267
  return re.test(varName ? code : line);
51439
54268
  }
@@ -52516,6 +55345,9 @@ var WeakRandomPass = class {
52516
55345
  if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
52517
55346
  return "Math.random";
52518
55347
  }
55348
+ if (this.jsIsModuleScopedCryptoRandomUuid(call, ctx)) {
55349
+ return "crypto.randomUUID (module scope)";
55350
+ }
52519
55351
  return null;
52520
55352
  }
52521
55353
  if (language === "go") {
@@ -52553,6 +55385,26 @@ var WeakRandomPass = class {
52553
55385
  * file (i.e. `math/rand` imported unaliased; `crypto/rand` is either not
52554
55386
  * imported or imported under an alias).
52555
55387
  */
55388
+ /**
55389
+ * Module-scoped `crypto.randomUUID()` / `randomUUID()` — entropy fixed at load.
55390
+ */
55391
+ jsIsModuleScopedCryptoRandomUuid(call, ctx) {
55392
+ if (call.method_name !== "randomUUID") return false;
55393
+ if (call.in_method != null && call.in_method !== "") return false;
55394
+ const receiver = call.receiver ?? "";
55395
+ if (receiver === "crypto" || receiver.endsWith(".crypto")) return true;
55396
+ if (!receiver) {
55397
+ const imports = ctx.graph.ir.imports ?? [];
55398
+ return imports.some((imp) => {
55399
+ const pkg = imp.from_package ?? "";
55400
+ if (pkg !== "crypto" && pkg !== "node:crypto") return false;
55401
+ const name2 = imp.imported_name ?? imp.alias ?? "";
55402
+ const alias = imp.alias ?? imp.imported_name ?? "";
55403
+ return name2 === "randomUUID" || alias === "randomUUID";
55404
+ });
55405
+ }
55406
+ return false;
55407
+ }
52556
55408
  goMathRandIsActive(ctx) {
52557
55409
  const imports = ctx.graph.ir.imports ?? [];
52558
55410
  let mathRandUnaliased = false;
@@ -53864,6 +56716,13 @@ var PY_HTTP_RECEIVERS = /* @__PURE__ */ new Set([
53864
56716
  var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
53865
56717
  var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
53866
56718
  var INSECURE_SKIP_VERIFY_TRUE_RE = /\bInsecureSkipVerify\s*:\s*true\b/;
56719
+ var GO_X509_VERIFY_CALL_RE = /\.Verify\s*\(/;
56720
+ var GO_INTERMEDIATE_DENYLIST_GUARD_RE = /\b(denylist|denyList|deny_list|blocklist|blockList|blockedIntermediate|blocked_intermediate|deniedIntermediate|deniedIntermediates|denied_intermediate|isDeniedIntermediate|intermediateDenylist|intermediate_denylist)\b/i;
56721
+ function goSourceWithoutComments(code) {
56722
+ let s = code.replace(/\/\*[\s\S]*?\*\//g, " ");
56723
+ s = s.replace(/\/\/[^\n]*/g, " ");
56724
+ return s;
56725
+ }
53867
56726
  var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
53868
56727
  var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
53869
56728
  var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
@@ -53872,6 +56731,14 @@ var ALLOW_ALL_HOSTNAME_VERIFIERS = /* @__PURE__ */ new Set([
53872
56731
  "new AllowAllHostnameVerifier()",
53873
56732
  "new NoopHostnameVerifier()"
53874
56733
  ]);
56734
+ var JS_CHECK_SERVER_IDENTITY_NOP_RE = /\bcheckServerIdentity\s*:\s*(?:async\s*)?\([^)]*\)\s*=>\s*(?:undefined|null)\b/;
56735
+ var JS_CHECK_SERVER_IDENTITY_NOP_FN_RE = /\bcheckServerIdentity\s*:\s*function\s*\([^)]*\)\s*\{\s*return\s+(?:undefined|null)\s*;?\s*\}/;
56736
+ var JS_CHECK_SERVER_IDENTITY_DECL_RE = /\bfunction\s+checkServerIdentity\s*\([^)]*\)\s*\{/g;
56737
+ var JS_LOOSE_IP_HOST_STRIP_RE = /\.replace\s*\(\s*\/\[\^\\d\.\]\\]\//;
56738
+ var JS_CHECK_SERVER_IDENTITY_LOOSE_BODY_RE = /(?:looseIp\s*\(|\.replace\s*\(\s*\/\[\^\\d\.\]\\]\/)[\s\S]*?return\s+undefined\b/;
56739
+ var JS_NFKC_NORMALIZE_RE = /\.normalize\s*\(\s*['"]NFKC['"]\s*\)/i;
56740
+ var JS_SUBJECT_ALT_NAME_RE = /\bsubjectaltname\b|\bsubjectAltName\b|\baltNames\b|\bDNS:\b|\bIP Address:/i;
56741
+ var JS_ISSUER_CHAIN_WALK_RE = /\bissuerCertificate\b/;
53875
56742
  var TlsVerifyDisabledPass = class {
53876
56743
  name = "tls-verify-disabled";
53877
56744
  category = "security";
@@ -53986,6 +56853,18 @@ var TlsVerifyDisabledPass = class {
53986
56853
  });
53987
56854
  }
53988
56855
  }
56856
+ if (/\bx509\./.test(code) && /\bVerifyOptions\b/.test(code) && GO_X509_VERIFY_CALL_RE.test(code) && !GO_INTERMEDIATE_DENYLIST_GUARD_RE.test(goSourceWithoutComments(code))) {
56857
+ for (let i2 = 0; i2 < lines.length; i2++) {
56858
+ if (GO_X509_VERIFY_CALL_RE.test(lines[i2])) {
56859
+ out2.push({
56860
+ line: i2 + 1,
56861
+ pattern: "x509.Verify without intermediate denylist",
56862
+ api: "x509.Certificate.Verify"
56863
+ });
56864
+ break;
56865
+ }
56866
+ }
56867
+ }
53989
56868
  }
53990
56869
  if (language === "python") {
53991
56870
  for (let i2 = 0; i2 < lines.length; i2++) {
@@ -54009,6 +56888,35 @@ var TlsVerifyDisabledPass = class {
54009
56888
  api: "process.env"
54010
56889
  });
54011
56890
  }
56891
+ if (JS_CHECK_SERVER_IDENTITY_NOP_RE.test(l) || JS_CHECK_SERVER_IDENTITY_NOP_FN_RE.test(l)) {
56892
+ out2.push({
56893
+ line: i2 + 1,
56894
+ pattern: "checkServerIdentity => undefined",
56895
+ api: "tls.connect / https.request options"
56896
+ });
56897
+ }
56898
+ }
56899
+ const m = JS_CHECK_SERVER_IDENTITY_NOP_RE.exec(code) ?? JS_CHECK_SERVER_IDENTITY_NOP_FN_RE.exec(code);
56900
+ if (m && m.index !== void 0) {
56901
+ const line = code.slice(0, m.index).split("\n").length;
56902
+ const dupKey = `${line}-checkServerIdentity => undefined`;
56903
+ if (!out2.some((o) => `${o.line}-${o.pattern}` === dupKey)) {
56904
+ out2.push({
56905
+ line,
56906
+ pattern: "checkServerIdentity => undefined",
56907
+ api: "tls.connect / https.request options"
56908
+ });
56909
+ }
56910
+ }
56911
+ for (const weak of this.detectWeakCheckServerIdentityCallbacks(code)) {
56912
+ const dupKey = `${weak.line}-${weak.pattern}`;
56913
+ if (!out2.some((o) => `${o.line}-${o.pattern}` === dupKey)) {
56914
+ out2.push({
56915
+ line: weak.line,
56916
+ pattern: weak.pattern,
56917
+ api: "checkServerIdentity (custom)"
56918
+ });
56919
+ }
54012
56920
  }
54013
56921
  }
54014
56922
  if (language === "csharp") {
@@ -54028,16 +56936,90 @@ var TlsVerifyDisabledPass = class {
54028
56936
  }
54029
56937
  return out2;
54030
56938
  }
56939
+ /** Standalone and inline `checkServerIdentity` with weak identity checks. */
56940
+ detectWeakCheckServerIdentityCallbacks(code) {
56941
+ const hits = [];
56942
+ const fileHasDenylist = GO_INTERMEDIATE_DENYLIST_GUARD_RE.test(code);
56943
+ const declRe = new RegExp(JS_CHECK_SERVER_IDENTITY_DECL_RE.source, "g");
56944
+ let match;
56945
+ while ((match = declRe.exec(code)) !== null) {
56946
+ const openBrace = code.indexOf("{", match.index);
56947
+ if (openBrace < 0) continue;
56948
+ const body2 = this.sliceBalancedBraces(code, openBrace);
56949
+ if (!body2) continue;
56950
+ const prelude = code.slice(Math.max(0, match.index - 800), match.index);
56951
+ const line = code.slice(0, match.index).split("\n").length;
56952
+ const weak = this.classifyWeakCheckServerIdentityBody(
56953
+ body2,
56954
+ prelude,
56955
+ fileHasDenylist
56956
+ );
56957
+ if (weak) hits.push({ line, pattern: weak });
56958
+ }
56959
+ const inlineRe = /\bcheckServerIdentity\s*:\s*(?:async\s*)?function\s*\([^)]*\)\s*\{/g;
56960
+ while ((match = inlineRe.exec(code)) !== null) {
56961
+ const openBrace = code.indexOf("{", match.index);
56962
+ if (openBrace < 0) continue;
56963
+ const body2 = this.sliceBalancedBraces(code, openBrace);
56964
+ if (!body2) continue;
56965
+ const prelude = code.slice(Math.max(0, match.index - 800), match.index);
56966
+ const line = code.slice(0, match.index).split("\n").length;
56967
+ const weak = this.classifyWeakCheckServerIdentityBody(
56968
+ body2,
56969
+ prelude,
56970
+ fileHasDenylist
56971
+ );
56972
+ if (weak) hits.push({ line, pattern: weak });
56973
+ }
56974
+ return hits;
56975
+ }
56976
+ classifyWeakCheckServerIdentityBody(body2, prelude, fileHasDenylist) {
56977
+ if (!/\breturn\s+undefined\b/.test(body2)) return null;
56978
+ const loose = JS_LOOSE_IP_HOST_STRIP_RE.test(body2) || JS_LOOSE_IP_HOST_STRIP_RE.test(prelude) || /\blooseIp\s*\(/.test(body2);
56979
+ if (loose && JS_CHECK_SERVER_IDENTITY_LOOSE_BODY_RE.test(body2)) {
56980
+ return "checkServerIdentity loose host parse => undefined";
56981
+ }
56982
+ const nfkc = JS_NFKC_NORMALIZE_RE.test(body2) || JS_NFKC_NORMALIZE_RE.test(prelude);
56983
+ if (nfkc && /\breturn\s+undefined\b/.test(body2)) {
56984
+ return "checkServerIdentity NFKC host normalize => undefined";
56985
+ }
56986
+ if (JS_SUBJECT_ALT_NAME_RE.test(body2) && /\breturn\s+undefined\b/.test(body2)) {
56987
+ return "checkServerIdentity DNS SAN / IP match => undefined";
56988
+ }
56989
+ if (JS_ISSUER_CHAIN_WALK_RE.test(body2) && !fileHasDenylist && /\breturn\s+undefined\b/.test(body2)) {
56990
+ return "checkServerIdentity issuer chain without denylist => undefined";
56991
+ }
56992
+ return null;
56993
+ }
56994
+ sliceBalancedBraces(code, openIndex) {
56995
+ if (code[openIndex] !== "{") return null;
56996
+ let depth = 0;
56997
+ for (let i2 = openIndex; i2 < code.length; i2++) {
56998
+ const ch = code[i2];
56999
+ if (ch === "{") depth++;
57000
+ else if (ch === "}") {
57001
+ depth--;
57002
+ if (depth === 0) return code.slice(openIndex, i2 + 1);
57003
+ }
57004
+ }
57005
+ return null;
57006
+ }
54031
57007
  fixFor(language, pattern) {
54032
57008
  if (pattern.includes("InsecureSkipVerify")) {
54033
57009
  return "Remove `InsecureSkipVerify: true` \u2014 let Go verify the cert. If you need to trust a private CA, set `RootCAs` to a `*x509.CertPool` containing that CA.";
54034
57010
  }
57011
+ if (pattern.includes("x509.Verify without intermediate denylist")) {
57012
+ return "Before calling `x509.Certificate.Verify`, check the chain (including intermediates) against your blocked-CA / denylist policy. Relying on PKIX verification alone accepts chains that include revoked or blocked intermediates.";
57013
+ }
54035
57014
  if (pattern.includes("verify=False")) {
54036
57015
  return "Remove `verify=False`. To trust a private CA, pass `verify='/path/to/ca.pem'`.";
54037
57016
  }
54038
57017
  if (pattern.includes("rejectUnauthorized")) {
54039
57018
  return "Remove `rejectUnauthorized: false`. To trust a private CA, set the `ca` option to the CA cert(s). Never disable TLS verification globally.";
54040
57019
  }
57020
+ if (pattern.includes("checkServerIdentity")) {
57021
+ return "Implement `checkServerIdentity` to compare the certificate to the intended hostname (or remove the override). Returning `undefined` accepts the connection; loose host/IP folding before that check enables MITM.";
57022
+ }
54041
57023
  if (pattern.includes("NODE_TLS_REJECT_UNAUTHORIZED")) {
54042
57024
  return "Remove the `NODE_TLS_REJECT_UNAUTHORIZED=0` assignment \u2014 it globally disables TLS verification for every outbound HTTPS request.";
54043
57025
  }
@@ -56009,6 +58991,36 @@ function boundedMakeDefIds(defs, code, language) {
56009
58991
  return out2;
56010
58992
  }
56011
58993
 
58994
+ // src/analysis/comment-lines.ts
58995
+ function makeCommentOnlyLineTest(root, code) {
58996
+ const lines = code.split("\n");
58997
+ const cache = /* @__PURE__ */ new Map();
58998
+ return (line) => {
58999
+ const hit = cache.get(line);
59000
+ if (hit !== void 0) return hit;
59001
+ let result = false;
59002
+ const text = lines[line - 1];
59003
+ if (text !== void 0) {
59004
+ const first = text.search(/\S/);
59005
+ if (first >= 0) {
59006
+ const end = text.trimEnd().length;
59007
+ const node = root.descendantForPosition({ row: line - 1, column: first });
59008
+ let n = node;
59009
+ while (n && !/comment/.test(n.type) && n.startPosition.row === line - 1 && n.startPosition.column === first && n.parent) {
59010
+ n = n.parent;
59011
+ }
59012
+ if (n && /comment/.test(n.type)) {
59013
+ const startsBefore = n.startPosition.row < line - 1 || n.startPosition.row === line - 1 && n.startPosition.column <= first;
59014
+ const endsAfter = n.endPosition.row > line - 1 || n.endPosition.row === line - 1 && n.endPosition.column >= end;
59015
+ result = startsBefore && endsAfter;
59016
+ }
59017
+ }
59018
+ }
59019
+ cache.set(line, result);
59020
+ return result;
59021
+ };
59022
+ }
59023
+
56012
59024
  // src/analyzer.ts
56013
59025
  var initialized = false;
56014
59026
  async function initAnalyzer(options = {}) {
@@ -56232,10 +59244,24 @@ function neutralizeOverlongLines(code) {
56232
59244
  }
56233
59245
  return { code: out2, dropped };
56234
59246
  }
59247
+ var AnalysisTimeoutError = class extends Error {
59248
+ file;
59249
+ elapsedMs;
59250
+ phase;
59251
+ constructor(file, elapsedMs, phase) {
59252
+ super(`analysis of ${file} timed out after ${elapsedMs}ms (at ${phase})`);
59253
+ this.name = "AnalysisTimeoutError";
59254
+ this.file = file;
59255
+ this.elapsedMs = elapsedMs;
59256
+ this.phase = phase;
59257
+ }
59258
+ };
56235
59259
  async function analyze(code, filePath, language, options = {}) {
56236
59260
  if (!initialized) {
56237
59261
  await initAnalyzer(options);
56238
59262
  }
59263
+ const fileTimeoutMs = options.fileTimeoutMs ?? 0;
59264
+ const fileDeadline = fileTimeoutMs > 0 ? Date.now() + fileTimeoutMs : 0;
56239
59265
  if (language === "html" || language === "vue") {
56240
59266
  return analyzeMarkupFile(code, filePath, options, language);
56241
59267
  }
@@ -56270,6 +59296,9 @@ async function analyze(code, filePath, language, options = {}) {
56270
59296
  const tree = await parse(code, parseGrammar);
56271
59297
  phaseEnd("parse", tParse);
56272
59298
  try {
59299
+ if (fileDeadline > 0 && Date.now() > fileDeadline) {
59300
+ throw new AnalysisTimeoutError(filePath, Date.now() - (fileDeadline - fileTimeoutMs), "parse");
59301
+ }
56273
59302
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
56274
59303
  const tParseStatus = phaseStart();
56275
59304
  const parseStatus = extractParseStatus(tree);
@@ -56322,7 +59351,8 @@ async function analyze(code, filePath, language, options = {}) {
56322
59351
  const imports = extractImports(tree, language, nodeCache);
56323
59352
  phaseEnd("extractImports", tImports);
56324
59353
  const tExports = phaseStart();
56325
- const exports = extractExports(types);
59354
+ if (options.declarationDetail && (language === "typescript" || language === "javascript")) enrichTsDeclarations(tree, types);
59355
+ const exports = (options.declarationDetail && (language === "typescript" || language === "javascript") ? extractTsExports(types) : null) ?? extractExports(types);
56326
59356
  phaseEnd("extractExports", tExports);
56327
59357
  const tCFG = phaseStart();
56328
59358
  const cfg = buildCFG(tree, language, nodeCache);
@@ -56443,7 +59473,14 @@ async function analyze(code, filePath, language, options = {}) {
56443
59473
  if (!disabledPasses.has("unrestricted-file-upload")) pipeline.add(new UnrestrictedFileUploadPass());
56444
59474
  if (!disabledPasses.has("missing-sanitizer-gate")) pipeline.add(new MissingSanitizerGatePass());
56445
59475
  if (!disabledPasses.has("insecure-deserialization-config")) pipeline.add(new InsecureDeserializationConfigPass());
56446
- const { results, findings } = pipeline.run(graph, code, language, config, options.resources);
59476
+ const { results, findings } = pipeline.run(
59477
+ graph,
59478
+ code,
59479
+ language,
59480
+ config,
59481
+ options.resources,
59482
+ fileDeadline > 0 ? { deadline: fileDeadline, onExpired: (phase) => new AnalysisTimeoutError(filePath, Date.now() - (fileDeadline - fileTimeoutMs), phase) } : void 0
59483
+ );
56447
59484
  const sinkFilter = results.get("sink-filter");
56448
59485
  const interProc = results.get("interprocedural");
56449
59486
  const taint = {
@@ -56464,6 +59501,12 @@ async function analyze(code, filePath, language, options = {}) {
56464
59501
  taint.flows = [...taint.flows ?? [], ...constructionFlows];
56465
59502
  }
56466
59503
  }
59504
+ const isCommentOnlyLine = makeCommentOnlyLineTest(tree.rootNode, code);
59505
+ taint.sinks = taint.sinks.filter((s) => !isCommentOnlyLine(s.line));
59506
+ taint.sources = taint.sources.filter((s) => !isCommentOnlyLine(s.line));
59507
+ if (taint.flows) {
59508
+ taint.flows = taint.flows.filter((f) => !isCommentOnlyLine(f.sink_line) && !isCommentOnlyLine(f.source_line));
59509
+ }
56467
59510
  if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
56468
59511
  const sinkTagsByKey = /* @__PURE__ */ new Map();
56469
59512
  for (const s of taint.sinks) {
@@ -56492,7 +59535,10 @@ async function analyze(code, filePath, language, options = {}) {
56492
59535
  unresolvedItems: unresolved.length
56493
59536
  });
56494
59537
  emitFindingsInstrumentation(filePath, findings, taint);
56495
- const verifiedFindings = applyConfidenceFilter(findings, options.includeSpeculative === true);
59538
+ const codeFindings = findings.filter(
59539
+ (f) => f.category !== "security" || f.line <= 1 || !isCommentOnlyLine(f.line)
59540
+ );
59541
+ const verifiedFindings = applyConfidenceFilter(codeFindings, options.includeSpeculative === true);
56496
59542
  const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
56497
59543
  const profiledFindings = applyProjectProfileTransform(
56498
59544
  downgradedFindings,