circle-ir 4.17.0 → 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 (280) 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/propagator.d.ts +4 -0
  17. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  18. package/dist/analysis/constant-propagation/propagator.js +99 -0
  19. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  20. package/dist/analysis/entry-point-detection.d.ts.map +1 -1
  21. package/dist/analysis/entry-point-detection.js +72 -0
  22. package/dist/analysis/entry-point-detection.js.map +1 -1
  23. package/dist/analysis/interprocedural.d.ts.map +1 -1
  24. package/dist/analysis/interprocedural.js +75 -8
  25. package/dist/analysis/interprocedural.js.map +1 -1
  26. package/dist/analysis/maven-declared.d.ts +72 -0
  27. package/dist/analysis/maven-declared.d.ts.map +1 -0
  28. package/dist/analysis/maven-declared.js +216 -0
  29. package/dist/analysis/maven-declared.js.map +1 -0
  30. package/dist/analysis/maven-version.d.ts +28 -0
  31. package/dist/analysis/maven-version.d.ts.map +1 -0
  32. package/dist/analysis/maven-version.js +172 -0
  33. package/dist/analysis/maven-version.js.map +1 -0
  34. package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
  35. package/dist/analysis/passes/constant-propagation-pass.js +29 -1
  36. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
  37. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  38. package/dist/analysis/passes/cross-file-pass.js +81 -1
  39. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  40. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
  41. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  42. package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
  43. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  44. package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
  45. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  46. package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
  47. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  48. package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
  49. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  50. package/dist/analysis/passes/interprocedural-pass.js +562 -0
  51. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  52. package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
  53. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  54. package/dist/analysis/passes/language-sources-pass.js +1057 -14
  55. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  56. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  57. package/dist/analysis/passes/param-source-flow-gate-pass.js +66 -3
  58. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  59. package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
  60. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  61. package/dist/analysis/passes/sink-filter-pass.js +690 -12
  62. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  63. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  64. package/dist/analysis/passes/taint-propagation-pass.js +133 -10
  65. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  66. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
  67. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
  68. package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
  69. package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
  70. package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
  71. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  72. package/dist/analysis/passes/weak-random-pass.js +29 -0
  73. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  74. package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
  75. package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
  76. package/dist/analysis/promote-cross-file-flows.js +32 -0
  77. package/dist/analysis/promote-cross-file-flows.js.map +1 -0
  78. package/dist/analysis/sbom.d.ts +100 -0
  79. package/dist/analysis/sbom.d.ts.map +1 -1
  80. package/dist/analysis/sbom.js +514 -3
  81. package/dist/analysis/sbom.js.map +1 -1
  82. package/dist/analysis/semver-version.d.ts +30 -0
  83. package/dist/analysis/semver-version.d.ts.map +1 -0
  84. package/dist/analysis/semver-version.js +76 -0
  85. package/dist/analysis/semver-version.js.map +1 -0
  86. package/dist/analysis/taint-matcher.d.ts +5 -0
  87. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  88. package/dist/analysis/taint-matcher.js +225 -4
  89. package/dist/analysis/taint-matcher.js.map +1 -1
  90. package/dist/analyzer.d.ts +57 -0
  91. package/dist/analyzer.d.ts.map +1 -1
  92. package/dist/analyzer.js +69 -6
  93. package/dist/analyzer.js.map +1 -1
  94. package/dist/browser/circle-ir.js +2936 -291
  95. package/dist/core/circle-ir-core.cjs +634 -203
  96. package/dist/core/circle-ir-core.js +634 -203
  97. package/dist/core/extractors/dfg.js +205 -2
  98. package/dist/core/extractors/dfg.js.map +1 -1
  99. package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
  100. package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
  101. package/dist/core/extractors/ts-declaration-detail.js +266 -0
  102. package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
  103. package/dist/graph/analysis-pass.d.ts +4 -1
  104. package/dist/graph/analysis-pass.d.ts.map +1 -1
  105. package/dist/graph/analysis-pass.js +4 -1
  106. package/dist/graph/analysis-pass.js.map +1 -1
  107. package/dist/graph-facts/dead-code.d.ts +62 -0
  108. package/dist/graph-facts/dead-code.d.ts.map +1 -0
  109. package/dist/graph-facts/dead-code.js +90 -0
  110. package/dist/graph-facts/dead-code.js.map +1 -0
  111. package/dist/graph-facts/dependency-calls.d.ts +86 -0
  112. package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
  113. package/dist/graph-facts/dependency-calls.js +93 -0
  114. package/dist/graph-facts/dependency-calls.js.map +1 -0
  115. package/dist/graph-facts/index.d.ts +10 -0
  116. package/dist/graph-facts/index.d.ts.map +1 -0
  117. package/dist/graph-facts/index.js +10 -0
  118. package/dist/graph-facts/index.js.map +1 -0
  119. package/dist/graph-facts/maven-packages.d.ts +42 -0
  120. package/dist/graph-facts/maven-packages.d.ts.map +1 -0
  121. package/dist/graph-facts/maven-packages.js +133 -0
  122. package/dist/graph-facts/maven-packages.js.map +1 -0
  123. package/dist/graph-facts/reach.d.ts +203 -0
  124. package/dist/graph-facts/reach.d.ts.map +1 -0
  125. package/dist/graph-facts/reach.js +219 -0
  126. package/dist/graph-facts/reach.js.map +1 -0
  127. package/dist/graph-facts/removal-reach.d.ts +73 -0
  128. package/dist/graph-facts/removal-reach.d.ts.map +1 -0
  129. package/dist/graph-facts/removal-reach.js +64 -0
  130. package/dist/graph-facts/removal-reach.js.map +1 -0
  131. package/dist/index.d.ts +21 -6
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +20 -3
  134. package/dist/index.js.map +1 -1
  135. package/dist/navigation/go-graph.d.ts +68 -0
  136. package/dist/navigation/go-graph.d.ts.map +1 -0
  137. package/dist/navigation/go-graph.js +753 -0
  138. package/dist/navigation/go-graph.js.map +1 -0
  139. package/dist/navigation/index.d.ts +7 -1
  140. package/dist/navigation/index.d.ts.map +1 -1
  141. package/dist/navigation/index.js +6 -0
  142. package/dist/navigation/index.js.map +1 -1
  143. package/dist/navigation/java-entry-tables.d.ts +43 -0
  144. package/dist/navigation/java-entry-tables.d.ts.map +1 -0
  145. package/dist/navigation/java-entry-tables.js +56 -0
  146. package/dist/navigation/java-entry-tables.js.map +1 -0
  147. package/dist/navigation/java-graph.d.ts +64 -0
  148. package/dist/navigation/java-graph.d.ts.map +1 -0
  149. package/dist/navigation/java-graph.js +466 -0
  150. package/dist/navigation/java-graph.js.map +1 -0
  151. package/dist/navigation/navigation-index.d.ts +51 -1
  152. package/dist/navigation/navigation-index.d.ts.map +1 -1
  153. package/dist/navigation/navigation-index.js +72 -0
  154. package/dist/navigation/navigation-index.js.map +1 -1
  155. package/dist/navigation/python-graph.d.ts +73 -0
  156. package/dist/navigation/python-graph.d.ts.map +1 -0
  157. package/dist/navigation/python-graph.js +867 -0
  158. package/dist/navigation/python-graph.js.map +1 -0
  159. package/dist/navigation/ts-call-edges.d.ts +193 -0
  160. package/dist/navigation/ts-call-edges.d.ts.map +1 -0
  161. package/dist/navigation/ts-call-edges.js +2089 -0
  162. package/dist/navigation/ts-call-edges.js.map +1 -0
  163. package/dist/navigation/ts-entry-tables.d.ts +88 -0
  164. package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
  165. package/dist/navigation/ts-entry-tables.js +50 -0
  166. package/dist/navigation/ts-entry-tables.js.map +1 -0
  167. package/dist/path-set/branches.d.ts +52 -0
  168. package/dist/path-set/branches.d.ts.map +1 -0
  169. package/dist/path-set/branches.js +124 -0
  170. package/dist/path-set/branches.js.map +1 -0
  171. package/dist/path-set/finding-diff.d.ts +63 -0
  172. package/dist/path-set/finding-diff.d.ts.map +1 -0
  173. package/dist/path-set/finding-diff.js +150 -0
  174. package/dist/path-set/finding-diff.js.map +1 -0
  175. package/dist/path-set/index.d.ts +18 -0
  176. package/dist/path-set/index.d.ts.map +1 -0
  177. package/dist/path-set/index.js +12 -0
  178. package/dist/path-set/index.js.map +1 -0
  179. package/dist/path-set/line-diff.d.ts +19 -0
  180. package/dist/path-set/line-diff.d.ts.map +1 -0
  181. package/dist/path-set/line-diff.js +98 -0
  182. package/dist/path-set/line-diff.js.map +1 -0
  183. package/dist/path-set/path-set.d.ts +88 -0
  184. package/dist/path-set/path-set.d.ts.map +1 -0
  185. package/dist/path-set/path-set.js +716 -0
  186. package/dist/path-set/path-set.js.map +1 -0
  187. package/dist/path-set/templates.d.ts +42 -0
  188. package/dist/path-set/templates.d.ts.map +1 -0
  189. package/dist/path-set/templates.js +119 -0
  190. package/dist/path-set/templates.js.map +1 -0
  191. package/dist/path-set/types.d.ts +195 -0
  192. package/dist/path-set/types.d.ts.map +1 -0
  193. package/dist/path-set/types.js +8 -0
  194. package/dist/path-set/types.js.map +1 -0
  195. package/dist/remedy-class/index.d.ts +12 -0
  196. package/dist/remedy-class/index.d.ts.map +1 -0
  197. package/dist/remedy-class/index.js +9 -0
  198. package/dist/remedy-class/index.js.map +1 -0
  199. package/dist/remedy-class/match.d.ts +165 -0
  200. package/dist/remedy-class/match.d.ts.map +1 -0
  201. package/dist/remedy-class/match.js +705 -0
  202. package/dist/remedy-class/match.js.map +1 -0
  203. package/dist/remedy-class/output-context.d.ts +25 -0
  204. package/dist/remedy-class/output-context.d.ts.map +1 -0
  205. package/dist/remedy-class/output-context.js +150 -0
  206. package/dist/remedy-class/output-context.js.map +1 -0
  207. package/dist/remedy-class/sha256.d.ts +18 -0
  208. package/dist/remedy-class/sha256.d.ts.map +1 -0
  209. package/dist/remedy-class/sha256.js +89 -0
  210. package/dist/remedy-class/sha256.js.map +1 -0
  211. package/dist/remedy-class/table.d.ts +130 -0
  212. package/dist/remedy-class/table.d.ts.map +1 -0
  213. package/dist/remedy-class/table.js +457 -0
  214. package/dist/remedy-class/table.js.map +1 -0
  215. package/dist/resolution/cross-file.d.ts +9 -0
  216. package/dist/resolution/cross-file.d.ts.map +1 -1
  217. package/dist/resolution/cross-file.js +60 -5
  218. package/dist/resolution/cross-file.js.map +1 -1
  219. package/dist/stable-ids/builder.d.ts +20 -0
  220. package/dist/stable-ids/builder.d.ts.map +1 -0
  221. package/dist/stable-ids/builder.js +65 -0
  222. package/dist/stable-ids/builder.js.map +1 -0
  223. package/dist/stable-ids/diff.d.ts +18 -0
  224. package/dist/stable-ids/diff.d.ts.map +1 -0
  225. package/dist/stable-ids/diff.js +37 -0
  226. package/dist/stable-ids/diff.js.map +1 -0
  227. package/dist/stable-ids/go.d.ts +28 -0
  228. package/dist/stable-ids/go.d.ts.map +1 -0
  229. package/dist/stable-ids/go.js +160 -0
  230. package/dist/stable-ids/go.js.map +1 -0
  231. package/dist/stable-ids/hash.d.ts +22 -0
  232. package/dist/stable-ids/hash.d.ts.map +1 -0
  233. package/dist/stable-ids/hash.js +167 -0
  234. package/dist/stable-ids/hash.js.map +1 -0
  235. package/dist/stable-ids/index.d.ts +30 -0
  236. package/dist/stable-ids/index.d.ts.map +1 -0
  237. package/dist/stable-ids/index.js +59 -0
  238. package/dist/stable-ids/index.js.map +1 -0
  239. package/dist/stable-ids/java.d.ts +28 -0
  240. package/dist/stable-ids/java.d.ts.map +1 -0
  241. package/dist/stable-ids/java.js +224 -0
  242. package/dist/stable-ids/java.js.map +1 -0
  243. package/dist/stable-ids/moves.d.ts +87 -0
  244. package/dist/stable-ids/moves.d.ts.map +1 -0
  245. package/dist/stable-ids/moves.js +222 -0
  246. package/dist/stable-ids/moves.js.map +1 -0
  247. package/dist/stable-ids/nodes.d.ts +32 -0
  248. package/dist/stable-ids/nodes.d.ts.map +1 -0
  249. package/dist/stable-ids/nodes.js +92 -0
  250. package/dist/stable-ids/nodes.js.map +1 -0
  251. package/dist/stable-ids/normalize.d.ts +35 -0
  252. package/dist/stable-ids/normalize.d.ts.map +1 -0
  253. package/dist/stable-ids/normalize.js +174 -0
  254. package/dist/stable-ids/normalize.js.map +1 -0
  255. package/dist/stable-ids/parse-errors.d.ts +24 -0
  256. package/dist/stable-ids/parse-errors.d.ts.map +1 -0
  257. package/dist/stable-ids/parse-errors.js +46 -0
  258. package/dist/stable-ids/parse-errors.js.map +1 -0
  259. package/dist/stable-ids/python.d.ts +27 -0
  260. package/dist/stable-ids/python.d.ts.map +1 -0
  261. package/dist/stable-ids/python.js +212 -0
  262. package/dist/stable-ids/python.js.map +1 -0
  263. package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
  264. package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
  265. package/dist/stable-ids/ts-parse-errors.js +75 -0
  266. package/dist/stable-ids/ts-parse-errors.js.map +1 -0
  267. package/dist/stable-ids/types.d.ts +106 -0
  268. package/dist/stable-ids/types.d.ts.map +1 -0
  269. package/dist/stable-ids/types.js +16 -0
  270. package/dist/stable-ids/types.js.map +1 -0
  271. package/dist/stable-ids/typescript.d.ts +30 -0
  272. package/dist/stable-ids/typescript.d.ts.map +1 -0
  273. package/dist/stable-ids/typescript.js +362 -0
  274. package/dist/stable-ids/typescript.js.map +1 -0
  275. package/dist/types/config.d.ts +9 -0
  276. package/dist/types/config.d.ts.map +1 -1
  277. package/dist/types/index.d.ts +35 -0
  278. package/dist/types/index.d.ts.map +1 -1
  279. package/dist/types/index.js.map +1 -1
  280. package/package.json +3 -3
@@ -10390,6 +10390,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10390
10390
  carryBranchDefsOut(popped, scopeStack, alwaysRuns);
10391
10391
  break;
10392
10392
  }
10393
+ case "try_statement":
10394
+ case "try_with_resources_statement": {
10395
+ if (isJavaScript) {
10396
+ const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
10397
+ uses.push(...stmtUses.uses);
10398
+ useId = stmtUses.nextId;
10399
+ break;
10400
+ }
10401
+ scopeStack.push(/* @__PURE__ */ new Map());
10402
+ const resources = stmt.childForFieldName("resources");
10403
+ for (const res of resources?.namedChildren ?? []) {
10404
+ if (!res || res.type !== "resource") continue;
10405
+ const nameNode = res.childForFieldName("name");
10406
+ const value = res.childForFieldName("value");
10407
+ if (value) {
10408
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
10409
+ uses.push(...valueUses.uses);
10410
+ useId = valueUses.nextId;
10411
+ }
10412
+ if (nameNode && nameNode.type === "identifier") {
10413
+ const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
10414
+ defs.push(def);
10415
+ currentScope(scopeStack).set(def.variable, def.id);
10416
+ markDeclared(currentScope(scopeStack), def.variable);
10417
+ } else if (!value) {
10418
+ const resUses = extractUses(res, useId, scopeStack, isJavaScript);
10419
+ uses.push(...resUses.uses);
10420
+ useId = resUses.nextId;
10421
+ }
10422
+ }
10423
+ for (const part of stmt.namedChildren) {
10424
+ if (!part) continue;
10425
+ if (part.type === "block") {
10426
+ const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
10427
+ defs.push(...r.defs);
10428
+ uses.push(...r.uses);
10429
+ defId = r.nextDefId;
10430
+ useId = r.nextUseId;
10431
+ } else if (part.type === "catch_clause") {
10432
+ scopeStack.push(/* @__PURE__ */ new Map());
10433
+ for (const param of part.namedChildren) {
10434
+ if (!param || param.type !== "catch_formal_parameter") continue;
10435
+ const pname = param.childForFieldName("name");
10436
+ if (pname) {
10437
+ const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
10438
+ defs.push(def);
10439
+ currentScope(scopeStack).set(def.variable, def.id);
10440
+ markDeclared(currentScope(scopeStack), def.variable);
10441
+ }
10442
+ }
10443
+ const body2 = part.childForFieldName("body");
10444
+ if (body2) {
10445
+ const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
10446
+ defs.push(...r.defs);
10447
+ uses.push(...r.uses);
10448
+ defId = r.nextDefId;
10449
+ useId = r.nextUseId;
10450
+ }
10451
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10452
+ } else if (part.type === "finally_clause") {
10453
+ for (const b of part.namedChildren) {
10454
+ if (!b || b.type !== "block") continue;
10455
+ const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
10456
+ defs.push(...r.defs);
10457
+ uses.push(...r.uses);
10458
+ defId = r.nextDefId;
10459
+ useId = r.nextUseId;
10460
+ }
10461
+ }
10462
+ }
10463
+ carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
10464
+ break;
10465
+ }
10393
10466
  case "else_clause": {
10394
10467
  for (let i2 = 0; i2 < stmt.childCount; i2++) {
10395
10468
  const arm = stmt.child(i2);
@@ -11005,6 +11078,42 @@ function buildBashDFG(tree) {
11005
11078
  defs.push(def);
11006
11079
  currentScope(scopeStack).set(p, def.id);
11007
11080
  }
11081
+ const bashFnDefs = /* @__PURE__ */ new Map();
11082
+ const bashFnByNodeId = /* @__PURE__ */ new Map();
11083
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11084
+ const nameNode = fn.childForFieldName("name");
11085
+ if (!nameNode) continue;
11086
+ const fnName = getNodeText(nameNode);
11087
+ if (!fnName || bashFnDefs.has(fnName)) continue;
11088
+ const m = /* @__PURE__ */ new Map();
11089
+ for (const p of positionalParams) {
11090
+ const def = {
11091
+ id: defIdCounter++,
11092
+ variable: p,
11093
+ line: fn.startPosition.row + 1,
11094
+ kind: "param",
11095
+ expression: `bash-fn:${fnName}`
11096
+ };
11097
+ defs.push(def);
11098
+ m.set(p, def.id);
11099
+ }
11100
+ bashFnDefs.set(fnName, m);
11101
+ bashFnByNodeId.set(fn.id, fnName);
11102
+ }
11103
+ const enclosingBashFn = (node) => {
11104
+ let cur = node.parent;
11105
+ while (cur) {
11106
+ if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
11107
+ cur = cur.parent;
11108
+ }
11109
+ return null;
11110
+ };
11111
+ const positionalDef = (varName, node, fallback) => {
11112
+ if (!positionalParams.includes(varName)) return fallback;
11113
+ const fnName = enclosingBashFn(node);
11114
+ if (!fnName) return fallback;
11115
+ return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
11116
+ };
11008
11117
  walkTree(tree.rootNode, (node) => {
11009
11118
  if (node.type === "variable_assignment") {
11010
11119
  const nameNode = node.childForFieldName("name");
@@ -11057,7 +11166,7 @@ function buildBashDFG(tree) {
11057
11166
  if (varNameNode) {
11058
11167
  const varName = getNodeText(varNameNode);
11059
11168
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
11060
- let reachingDef = findReachingDef(varName, scopeStack);
11169
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11061
11170
  if (reachingDef === null && !positionalParams.includes(varName)) {
11062
11171
  const def = {
11063
11172
  id: defIdCounter++,
@@ -11081,7 +11190,7 @@ function buildBashDFG(tree) {
11081
11190
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
11082
11191
  if (varNameNode && varNameNode.type === "variable_name") {
11083
11192
  const varName = getNodeText(varNameNode);
11084
- let reachingDef = findReachingDef(varName, scopeStack);
11193
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11085
11194
  if (reachingDef === null && !positionalParams.includes(varName)) {
11086
11195
  const def = {
11087
11196
  id: defIdCounter++,
@@ -11103,8 +11212,56 @@ function buildBashDFG(tree) {
11103
11212
  }
11104
11213
  });
11105
11214
  const chains = computeChains(defs, uses);
11215
+ chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
11106
11216
  return { defs, uses, chains };
11107
11217
  }
11218
+ function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
11219
+ const chains = [];
11220
+ if (bashFnDefs.size === 0) return chains;
11221
+ const renumbers = /* @__PURE__ */ new Set();
11222
+ for (const fn of findNodes(tree.rootNode, "function_definition")) {
11223
+ const nameNode = fn.childForFieldName("name");
11224
+ if (!nameNode) continue;
11225
+ if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
11226
+ }
11227
+ const usesByLine = /* @__PURE__ */ new Map();
11228
+ for (const u of uses) {
11229
+ const l = usesByLine.get(u.line) ?? [];
11230
+ l.push(u);
11231
+ usesByLine.set(u.line, l);
11232
+ }
11233
+ for (const cmd of findNodes(tree.rootNode, "command")) {
11234
+ const nameNode = cmd.childForFieldName("name");
11235
+ if (!nameNode) continue;
11236
+ const fnDefs = bashFnDefs.get(getNodeText(nameNode));
11237
+ if (!fnDefs) continue;
11238
+ const fnName = getNodeText(nameNode);
11239
+ let position = 0;
11240
+ for (let i2 = 0; i2 < cmd.childCount; i2++) {
11241
+ const arg = cmd.child(i2);
11242
+ if (!arg || arg.id === nameNode.id) continue;
11243
+ if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
11244
+ const text = getNodeText(arg);
11245
+ if (!text.trim()) continue;
11246
+ position++;
11247
+ const names = /* @__PURE__ */ new Set();
11248
+ const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
11249
+ let m;
11250
+ while ((m = re.exec(text)) !== null) names.add(m[1]);
11251
+ if (names.size === 0) continue;
11252
+ const spreads = /\$\{?[@*]/.test(text);
11253
+ const targets = spreads || renumbers.has(fnName) ? positionalParams.map((p) => fnDefs.get(p)).filter((d) => d !== void 0) : [fnDefs.get(String(position))].filter((d) => d !== void 0);
11254
+ const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
11255
+ for (const name2 of names) {
11256
+ for (const u of lineUses) {
11257
+ if (u.variable !== name2 || u.def_id === null) continue;
11258
+ for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
11259
+ }
11260
+ }
11261
+ }
11262
+ }
11263
+ return chains;
11264
+ }
11108
11265
  function buildRustDFG(tree, cache) {
11109
11266
  const defs = [];
11110
11267
  const uses = [];
@@ -11814,6 +11971,198 @@ function javaReplaceCrLfCoverage(method, rawFrom) {
11814
11971
  return { cr, lf };
11815
11972
  }
11816
11973
 
11974
+ // src/analysis/js-crlf-strip.ts
11975
+ var MAX_PATTERN_LENGTH = 200;
11976
+ var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
11977
+ "replace",
11978
+ "replaceAll",
11979
+ "trim",
11980
+ "trimStart",
11981
+ "trimEnd",
11982
+ "toString",
11983
+ "toLowerCase",
11984
+ "toUpperCase",
11985
+ "slice",
11986
+ "substring"
11987
+ ]);
11988
+ var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
11989
+ function readQuoted(s, i2) {
11990
+ const q = s[i2];
11991
+ if (q !== '"' && q !== "'" && q !== "`") return null;
11992
+ let out2 = "";
11993
+ for (let j = i2 + 1; j < s.length; j++) {
11994
+ const c = s[j];
11995
+ if (c === q) return { value: out2, end: j + 1 };
11996
+ if (q === "`" && c === "$" && s[j + 1] === "{") return null;
11997
+ if (c === "\\") {
11998
+ const n = s[j + 1];
11999
+ if (n === void 0) return null;
12000
+ switch (n) {
12001
+ case "n":
12002
+ out2 += "\n";
12003
+ j++;
12004
+ continue;
12005
+ case "r":
12006
+ out2 += "\r";
12007
+ j++;
12008
+ continue;
12009
+ case "t":
12010
+ out2 += " ";
12011
+ j++;
12012
+ continue;
12013
+ case "0":
12014
+ out2 += "\0";
12015
+ j++;
12016
+ continue;
12017
+ case "x": {
12018
+ const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
12019
+ if (!h) return null;
12020
+ out2 += String.fromCharCode(parseInt(h[0], 16));
12021
+ j += 3;
12022
+ continue;
12023
+ }
12024
+ case "u": {
12025
+ const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
12026
+ if (!h) return null;
12027
+ out2 += String.fromCharCode(parseInt(h[0], 16));
12028
+ j += 5;
12029
+ continue;
12030
+ }
12031
+ default:
12032
+ out2 += n;
12033
+ j++;
12034
+ continue;
12035
+ }
12036
+ }
12037
+ if (c === "\n" && q !== "`") return null;
12038
+ out2 += c;
12039
+ }
12040
+ return null;
12041
+ }
12042
+ function readRegexLiteral(s, i2) {
12043
+ if (s[i2] !== "/") return null;
12044
+ let inClass = false;
12045
+ for (let j = i2 + 1; j < s.length; j++) {
12046
+ const c = s[j];
12047
+ if (c === "\n") return null;
12048
+ if (c === "\\") {
12049
+ j++;
12050
+ continue;
12051
+ }
12052
+ if (inClass) {
12053
+ if (c === "]") inClass = false;
12054
+ continue;
12055
+ }
12056
+ if (c === "[") {
12057
+ inClass = true;
12058
+ continue;
12059
+ }
12060
+ if (c === "/") {
12061
+ const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
12062
+ return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
12063
+ }
12064
+ }
12065
+ return null;
12066
+ }
12067
+ var skipWs = (s, i2) => {
12068
+ while (i2 < s.length && /\s/.test(s[i2])) i2++;
12069
+ return i2;
12070
+ };
12071
+ function parseSteps(chainText) {
12072
+ const steps = [];
12073
+ const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
12074
+ let m;
12075
+ while ((m = callRe.exec(chainText)) !== null) {
12076
+ const kind = m[1];
12077
+ let i2 = skipWs(chainText, m.index + m[0].length);
12078
+ let pattern;
12079
+ const rx = readRegexLiteral(chainText, i2);
12080
+ if (rx) {
12081
+ if (rx.source.length > MAX_PATTERN_LENGTH) return null;
12082
+ try {
12083
+ pattern = new RegExp(rx.source, rx.flags);
12084
+ } catch {
12085
+ return null;
12086
+ }
12087
+ i2 = rx.end;
12088
+ } else {
12089
+ const str = readQuoted(chainText, i2);
12090
+ if (!str) return null;
12091
+ pattern = str.value;
12092
+ i2 = str.end;
12093
+ }
12094
+ i2 = skipWs(chainText, i2);
12095
+ if (chainText[i2] !== ",") return null;
12096
+ i2 = skipWs(chainText, i2 + 1);
12097
+ const rep = readQuoted(chainText, i2);
12098
+ if (!rep) return null;
12099
+ i2 = skipWs(chainText, rep.end);
12100
+ if (chainText[i2] !== ")") return null;
12101
+ steps.push({ kind, pattern, replacement: rep.value });
12102
+ callRe.lastIndex = i2 + 1;
12103
+ }
12104
+ return steps;
12105
+ }
12106
+ function run2(steps, input) {
12107
+ let s = input;
12108
+ try {
12109
+ for (const st of steps) {
12110
+ if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
12111
+ s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
12112
+ }
12113
+ } catch {
12114
+ return null;
12115
+ }
12116
+ return s;
12117
+ }
12118
+ function jsReplaceChainVerdict(chainText) {
12119
+ const steps = parseSteps(chainText);
12120
+ if (!steps || steps.length === 0) return "undecided";
12121
+ const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
12122
+ for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
12123
+ if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
12124
+ }
12125
+ const out2 = run2(steps, PROBE);
12126
+ if (out2 === null) return "undecided";
12127
+ return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
12128
+ }
12129
+ function jsChainTextAt(sourceLines, line, column) {
12130
+ const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
12131
+ let depth = 0;
12132
+ let prevSig = "";
12133
+ let i2 = 0;
12134
+ for (; i2 < text.length; i2++) {
12135
+ const c = text[i2];
12136
+ if (c === '"' || c === "'" || c === "`") {
12137
+ for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
12138
+ prevSig = c;
12139
+ continue;
12140
+ }
12141
+ if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
12142
+ const rx = readRegexLiteral(text, i2);
12143
+ if (rx) {
12144
+ i2 = rx.end - 1;
12145
+ prevSig = "/";
12146
+ continue;
12147
+ }
12148
+ }
12149
+ if (c === "(" || c === "[" || c === "{") {
12150
+ depth++;
12151
+ prevSig = c;
12152
+ continue;
12153
+ }
12154
+ if (c === ")" || c === "]" || c === "}") {
12155
+ if (depth === 0) break;
12156
+ depth--;
12157
+ prevSig = c;
12158
+ continue;
12159
+ }
12160
+ if (depth === 0 && !/[\w$.\s]/.test(c)) break;
12161
+ if (!/\s/.test(c)) prevSig = c;
12162
+ }
12163
+ return text.slice(0, i2);
12164
+ }
12165
+
11817
12166
  // src/analysis/passes/language-sources-pass.ts
11818
12167
  var JINJA_LITERAL_ARG_RE_FRAG = `(?:"[^"\\\\]*"|'[^'\\\\]*')`;
11819
12168
  var JINJA_SAFE_RTS_RE = new RegExp(
@@ -12597,6 +12946,10 @@ var DEFAULT_SOURCES = [
12597
12946
  { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12598
12947
  { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12599
12948
  { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12949
+ // Node redis clients are often bound to `cache` from `createClient()`; class
12950
+ // `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
12951
+ // filtered in sink-filter-pass (#818 src14).
12952
+ { method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
12600
12953
  { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
12601
12954
  { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
12602
12955
  { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
@@ -12776,7 +13129,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
12776
13129
  // on arg[1] (the *Cookie) catches struct-literal composite tainting.
12777
13130
  { method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
12778
13131
  // gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
12779
- { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
13132
+ { method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
13133
+ // --- Rust: actix-session / tower-sessions write -------------------------
13134
+ { method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
12780
13135
  ];
12781
13136
  var DEFAULT_SINKS = [
12782
13137
  // SQL Injection (CWE-89)
@@ -13437,7 +13792,9 @@ var DEFAULT_SINKS = [
13437
13792
  { method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13438
13793
  { method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13439
13794
  // Cron expression parsing (DoS/ReDoS)
13440
- { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13795
+ // cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
13796
+ // `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
13797
+ { method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
13441
13798
  { method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13442
13799
  // ff4j feature flags
13443
13800
  { method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
@@ -13534,9 +13891,13 @@ var DEFAULT_SINKS = [
13534
13891
  { method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13535
13892
  { method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13536
13893
  // XXE - XML External Entity (CWE-611)
13537
- { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13538
- { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13539
- { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13894
+ // cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
13895
+ // receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
13896
+ // `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
13897
+ // libxmljs, DOMParser) have their own entries.
13898
+ { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13899
+ { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13900
+ { method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
13540
13901
  { method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13541
13902
  { method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
13542
13903
  { method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
@@ -13791,6 +14152,46 @@ var DEFAULT_SINKS = [
13791
14152
  { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
13792
14153
  { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
13793
14154
  { method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14155
+ // DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
14156
+ // `duckdb` / `duckdb-async` bindings (Database / Connection) and
14157
+ // `react-native-duckdb` (Database / Transaction). The method names are
14158
+ // generic and the receivers come from factories (`await instance.connect()`,
14159
+ // `open({...})`), so the receiver cannot be typed: the import is the gate
14160
+ // (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
14161
+ // as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
14162
+ // `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
14163
+ // not reach it.
14164
+ { method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
14165
+ { method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async", "react-native-duckdb"] },
14166
+ { method: "stream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async", "react-native-duckdb"] },
14167
+ { method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14168
+ { method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14169
+ { method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14170
+ { method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14171
+ { method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14172
+ { method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14173
+ { method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14174
+ { method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14175
+ { method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
14176
+ { method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14177
+ { method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14178
+ { method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14179
+ { method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14180
+ { method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
14181
+ { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14182
+ { method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14183
+ { method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14184
+ { method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14185
+ { method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
14186
+ // RxDB (Mango queries, any storage incl. SQLite): a selector or query
14187
+ // object, a field name passed to the query builder, or a `$regex` pattern
14188
+ // under caller control is NoSQL operator injection (CWE-943). Gated on the
14189
+ // `rxdb` import for the same reason as DuckDB above. `find` is already a
14190
+ // classless JS NoSQL sink in the JavaScript plugin.
14191
+ { method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14192
+ { method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14193
+ { method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
14194
+ { method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
13794
14195
  // TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
13795
14196
  // escape hatch is already covered by the classless `query` sink in the JS
13796
14197
  // plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
@@ -13901,6 +14302,8 @@ var DEFAULT_SINKS = [
13901
14302
  { method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13902
14303
  { method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13903
14304
  { method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14305
+ // Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
14306
+ { method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
13904
14307
  // needle library (used in NodeGoat)
13905
14308
  { method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13906
14309
  { method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -14395,7 +14798,7 @@ var DEFAULT_SINKS = [
14395
14798
  // receiver-taint or operator-LHS-taint tracking — the format string is the
14396
14799
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
14397
14800
  // C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
14398
- { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
14801
+ { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
14399
14802
  { method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
14400
14803
  // Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
14401
14804
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
@@ -15464,198 +15867,6 @@ function getDefaultConfig() {
15464
15867
  };
15465
15868
  }
15466
15869
 
15467
- // src/analysis/js-crlf-strip.ts
15468
- var MAX_PATTERN_LENGTH = 200;
15469
- var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
15470
- "replace",
15471
- "replaceAll",
15472
- "trim",
15473
- "trimStart",
15474
- "trimEnd",
15475
- "toString",
15476
- "toLowerCase",
15477
- "toUpperCase",
15478
- "slice",
15479
- "substring"
15480
- ]);
15481
- var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
15482
- function readQuoted(s, i2) {
15483
- const q = s[i2];
15484
- if (q !== '"' && q !== "'" && q !== "`") return null;
15485
- let out2 = "";
15486
- for (let j = i2 + 1; j < s.length; j++) {
15487
- const c = s[j];
15488
- if (c === q) return { value: out2, end: j + 1 };
15489
- if (q === "`" && c === "$" && s[j + 1] === "{") return null;
15490
- if (c === "\\") {
15491
- const n = s[j + 1];
15492
- if (n === void 0) return null;
15493
- switch (n) {
15494
- case "n":
15495
- out2 += "\n";
15496
- j++;
15497
- continue;
15498
- case "r":
15499
- out2 += "\r";
15500
- j++;
15501
- continue;
15502
- case "t":
15503
- out2 += " ";
15504
- j++;
15505
- continue;
15506
- case "0":
15507
- out2 += "\0";
15508
- j++;
15509
- continue;
15510
- case "x": {
15511
- const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
15512
- if (!h) return null;
15513
- out2 += String.fromCharCode(parseInt(h[0], 16));
15514
- j += 3;
15515
- continue;
15516
- }
15517
- case "u": {
15518
- const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
15519
- if (!h) return null;
15520
- out2 += String.fromCharCode(parseInt(h[0], 16));
15521
- j += 5;
15522
- continue;
15523
- }
15524
- default:
15525
- out2 += n;
15526
- j++;
15527
- continue;
15528
- }
15529
- }
15530
- if (c === "\n" && q !== "`") return null;
15531
- out2 += c;
15532
- }
15533
- return null;
15534
- }
15535
- function readRegexLiteral(s, i2) {
15536
- if (s[i2] !== "/") return null;
15537
- let inClass = false;
15538
- for (let j = i2 + 1; j < s.length; j++) {
15539
- const c = s[j];
15540
- if (c === "\n") return null;
15541
- if (c === "\\") {
15542
- j++;
15543
- continue;
15544
- }
15545
- if (inClass) {
15546
- if (c === "]") inClass = false;
15547
- continue;
15548
- }
15549
- if (c === "[") {
15550
- inClass = true;
15551
- continue;
15552
- }
15553
- if (c === "/") {
15554
- const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
15555
- return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
15556
- }
15557
- }
15558
- return null;
15559
- }
15560
- var skipWs = (s, i2) => {
15561
- while (i2 < s.length && /\s/.test(s[i2])) i2++;
15562
- return i2;
15563
- };
15564
- function parseSteps(chainText) {
15565
- const steps = [];
15566
- const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
15567
- let m;
15568
- while ((m = callRe.exec(chainText)) !== null) {
15569
- const kind = m[1];
15570
- let i2 = skipWs(chainText, m.index + m[0].length);
15571
- let pattern;
15572
- const rx = readRegexLiteral(chainText, i2);
15573
- if (rx) {
15574
- if (rx.source.length > MAX_PATTERN_LENGTH) return null;
15575
- try {
15576
- pattern = new RegExp(rx.source, rx.flags);
15577
- } catch {
15578
- return null;
15579
- }
15580
- i2 = rx.end;
15581
- } else {
15582
- const str = readQuoted(chainText, i2);
15583
- if (!str) return null;
15584
- pattern = str.value;
15585
- i2 = str.end;
15586
- }
15587
- i2 = skipWs(chainText, i2);
15588
- if (chainText[i2] !== ",") return null;
15589
- i2 = skipWs(chainText, i2 + 1);
15590
- const rep = readQuoted(chainText, i2);
15591
- if (!rep) return null;
15592
- i2 = skipWs(chainText, rep.end);
15593
- if (chainText[i2] !== ")") return null;
15594
- steps.push({ kind, pattern, replacement: rep.value });
15595
- callRe.lastIndex = i2 + 1;
15596
- }
15597
- return steps;
15598
- }
15599
- function run2(steps, input) {
15600
- let s = input;
15601
- try {
15602
- for (const st of steps) {
15603
- if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
15604
- s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
15605
- }
15606
- } catch {
15607
- return null;
15608
- }
15609
- return s;
15610
- }
15611
- function jsReplaceChainVerdict(chainText) {
15612
- const steps = parseSteps(chainText);
15613
- if (!steps || steps.length === 0) return "undecided";
15614
- const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
15615
- for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
15616
- if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
15617
- }
15618
- const out2 = run2(steps, PROBE);
15619
- if (out2 === null) return "undecided";
15620
- return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
15621
- }
15622
- function jsChainTextAt(sourceLines, line, column) {
15623
- const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
15624
- let depth = 0;
15625
- let prevSig = "";
15626
- let i2 = 0;
15627
- for (; i2 < text.length; i2++) {
15628
- const c = text[i2];
15629
- if (c === '"' || c === "'" || c === "`") {
15630
- for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
15631
- prevSig = c;
15632
- continue;
15633
- }
15634
- if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
15635
- const rx = readRegexLiteral(text, i2);
15636
- if (rx) {
15637
- i2 = rx.end - 1;
15638
- prevSig = "/";
15639
- continue;
15640
- }
15641
- }
15642
- if (c === "(" || c === "[" || c === "{") {
15643
- depth++;
15644
- prevSig = c;
15645
- continue;
15646
- }
15647
- if (c === ")" || c === "]" || c === "}") {
15648
- if (depth === 0) break;
15649
- depth--;
15650
- prevSig = c;
15651
- continue;
15652
- }
15653
- if (depth === 0 && !/[\w$.\s]/.test(c)) break;
15654
- if (!/\s/.test(c)) prevSig = c;
15655
- }
15656
- return text.slice(0, i2);
15657
- }
15658
-
15659
15870
  // src/analysis/constant-propagation/patterns.ts
15660
15871
  var TAINT_PATTERNS = [
15661
15872
  // HTTP Sources (Servlet API)
@@ -16045,13 +16256,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
16045
16256
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
16046
16257
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
16047
16258
  const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
16048
- let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
16259
+ let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
16260
+ sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
16049
16261
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
16050
16262
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
16051
16263
  const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
16052
16264
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
16053
16265
  return { sources, sinks: gatedSinks, sanitizers };
16054
16266
  }
16267
+ function isBoundParameterCall(call, pattern, sourceLines) {
16268
+ if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
16269
+ const first = call.arguments[0]?.expression?.trim() ?? "";
16270
+ if (first.startsWith("[") || first.startsWith("{")) return true;
16271
+ const receiver = call.receiver;
16272
+ if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
16273
+ const assignedFromPrepare = new RegExp(
16274
+ `\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
16275
+ );
16276
+ return sourceLines.some((line) => assignedFromPrepare.test(line));
16277
+ }
16278
+ function sinkPatternImportApplies(pattern, imports) {
16279
+ const required = pattern.requires_import;
16280
+ if (!required || required.length === 0) return true;
16281
+ if (!imports || imports.length === 0) return false;
16282
+ return imports.some((imp) => {
16283
+ const from = imp.from_package;
16284
+ if (!from) return false;
16285
+ return required.some((m) => from === m || from.startsWith(m + "/"));
16286
+ });
16287
+ }
16055
16288
  function sinkPatternAppliesTo(pattern, language) {
16056
16289
  if (language === void 0) return true;
16057
16290
  if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
@@ -16469,6 +16702,46 @@ function isInterproceduralTaintableType(typeName, language) {
16469
16702
  const leaf = baseType.split(".").pop() ?? baseType;
16470
16703
  if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
16471
16704
  }
16705
+ if (language === "rust") {
16706
+ const trimmed = typeName.trim();
16707
+ const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
16708
+ const rustBase = unref.split("<")[0].trim();
16709
+ const rustScalars = [
16710
+ "String",
16711
+ "str",
16712
+ "bool",
16713
+ "char",
16714
+ "i8",
16715
+ "i16",
16716
+ "i32",
16717
+ "i64",
16718
+ "i128",
16719
+ "isize",
16720
+ "u8",
16721
+ "u16",
16722
+ "u32",
16723
+ "u64",
16724
+ "u128",
16725
+ "usize",
16726
+ "f32",
16727
+ "f64"
16728
+ ];
16729
+ if (rustScalars.includes(rustBase)) return true;
16730
+ if (rustBase === "()" || rustBase === "!") return false;
16731
+ if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
16732
+ return true;
16733
+ }
16734
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
16735
+ }
16736
+ if (language === "javascript" || language === "typescript") {
16737
+ const trimmed = typeName.trim();
16738
+ const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
16739
+ if (jsScalars.includes(baseType)) return true;
16740
+ if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
16741
+ if (/\bRecord\s*</.test(trimmed)) return true;
16742
+ if (["void", "undefined", "null", "never"].includes(baseType)) return false;
16743
+ if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
16744
+ }
16472
16745
  if (typeName.endsWith("[]")) {
16473
16746
  const elementType = typeName.slice(0, -2);
16474
16747
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -16583,6 +16856,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
16583
16856
  return false;
16584
16857
  }
16585
16858
  }
16859
+ if (program === "git") {
16860
+ const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
16861
+ const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
16862
+ let subcommand;
16863
+ if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
16864
+ subcommand = String(subcommandArg.literal);
16865
+ } else {
16866
+ const expr = (subcommandArg?.expression ?? "").trim();
16867
+ if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
16868
+ subcommand = expr.slice(1, -1);
16869
+ }
16870
+ }
16871
+ if (subcommand === "ls-remote") {
16872
+ const hasEndOfOptions = call.arguments.some((a) => {
16873
+ if (a.literal !== null && a.literal !== void 0) {
16874
+ return String(a.literal) === "--";
16875
+ }
16876
+ const expr = (a.expression ?? "").trim();
16877
+ return expr === '"--"' || expr === "'--'" || expr === "`--`";
16878
+ });
16879
+ if (!hasEndOfOptions) {
16880
+ return false;
16881
+ }
16882
+ }
16883
+ }
16586
16884
  return true;
16587
16885
  }
16588
16886
  function isSafeJSChildProcessCall(call, pattern, language) {
@@ -16986,6 +17284,29 @@ function isRegexReceiver(receiver, sourceLines) {
16986
17284
  const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
16987
17285
  return sourceLines.some((l) => decl.test(l));
16988
17286
  }
17287
+ function isNavigationReceiverName(receiver) {
17288
+ const r = (receiver ?? "").trim();
17289
+ if (!r) return false;
17290
+ const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
17291
+ return /router|history|navigat|^nav$/i.test(last);
17292
+ }
17293
+ function isInMemoryMapGetReceiver(receiver, sourceLines) {
17294
+ const raw = (receiver ?? "").trim();
17295
+ if (!raw || !sourceLines) return false;
17296
+ if (/\.properties$/i.test(raw)) return true;
17297
+ const lastDot = raw.lastIndexOf(".");
17298
+ const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
17299
+ if (/^(properties|memoryCache)$/i.test(r)) return true;
17300
+ if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
17301
+ const n = escapeRe(r);
17302
+ const mapCtor = new RegExp(
17303
+ `\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
17304
+ );
17305
+ const mapType = new RegExp(
17306
+ `\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
17307
+ );
17308
+ return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
17309
+ }
16989
17310
  function isDeclaredFunctionReference(arg, sourceLines) {
16990
17311
  if (arg.literal !== null && arg.literal !== void 0) return false;
16991
17312
  if (!sourceLines) return false;
@@ -17190,6 +17511,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17190
17511
  if (isParameterizedQueryCall(call, pattern)) {
17191
17512
  continue;
17192
17513
  }
17514
+ if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
17515
+ continue;
17516
+ }
17517
+ if (isBoundParameterCall(call, pattern, sourceLines)) {
17518
+ continue;
17519
+ }
17193
17520
  if (isSafePythonSubprocessCall(call, pattern, language)) {
17194
17521
  continue;
17195
17522
  }
@@ -17247,9 +17574,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17247
17574
  if (isArgumentlessJsResponseWriter(call, pattern, language)) {
17248
17575
  continue;
17249
17576
  }
17577
+ if (pattern.type === "open_redirect" && (language === "javascript" || language === "typescript") && (call.method_name === "push" || call.method_name === "replace") && (pattern.class ?? "").toLowerCase() === "router" && !call.receiver_type && !call.receiver_type_fqn && !isNavigationReceiverName(call.receiver)) {
17578
+ continue;
17579
+ }
17250
17580
  if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
17251
17581
  continue;
17252
17582
  }
17583
+ if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
17584
+ continue;
17585
+ }
17253
17586
  if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
17254
17587
  continue;
17255
17588
  }
@@ -17266,7 +17599,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17266
17599
  continue;
17267
17600
  }
17268
17601
  const location = formatCallLocation(call);
17269
- const key = `${location}:${call.location.line}:${pattern.cwe}`;
17602
+ const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
17270
17603
  const confidence = calculateSinkConfidence(call, pattern);
17271
17604
  const existing = sinkMap.get(key);
17272
17605
  if (!existing || confidence > existing.confidence) {
@@ -17277,6 +17610,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
17277
17610
  cwe: pattern.cwe,
17278
17611
  location,
17279
17612
  line: call.location.line,
17613
+ callColumn: call.location.column,
17280
17614
  confidence,
17281
17615
  method: call.method_name,
17282
17616
  argPositions: pattern.arg_positions,
@@ -17314,6 +17648,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
17314
17648
  return false;
17315
17649
  }
17316
17650
  }
17651
+ if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
17652
+ const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
17653
+ if (expressRequestAccessors.has(pattern.method)) {
17654
+ const recv = call.receiver?.toLowerCase() ?? "";
17655
+ if (recv !== "req" && recv !== "request" && recv !== "ctx") {
17656
+ return false;
17657
+ }
17658
+ }
17659
+ }
17317
17660
  return pattern.return_tainted === true;
17318
17661
  }
17319
17662
  return false;
@@ -17407,6 +17750,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
17407
17750
  "database",
17408
17751
  "sqlite",
17409
17752
  "conn",
17753
+ "con",
17410
17754
  "connection",
17411
17755
  "client",
17412
17756
  "pool",
@@ -17683,7 +18027,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
17683
18027
  }
17684
18028
  }
17685
18029
  }
17686
- if (!pattern.class && call.receiver) {
18030
+ if (!pattern.class && call.receiver && !pattern.requires_import) {
17687
18031
  if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
17688
18032
  return false;
17689
18033
  }
@@ -18416,6 +18760,8 @@ function javaReplaceChainCredit(chainText) {
18416
18760
  let lf = false;
18417
18761
  let lt = false;
18418
18762
  let gt = false;
18763
+ let openBrace = false;
18764
+ let closeBrace = false;
18419
18765
  for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
18420
18766
  const kind = m[1];
18421
18767
  const rawFrom = m[2] ?? m[3] ?? "";
@@ -18426,10 +18772,13 @@ function javaReplaceChainCredit(chainText) {
18426
18772
  if (cov.lf) lf = true;
18427
18773
  if (from === "<" && to.startsWith("&")) lt = true;
18428
18774
  if (from === ">" && to.startsWith("&")) gt = true;
18775
+ if (from === "{") openBrace = true;
18776
+ if (from === "}") closeBrace = true;
18429
18777
  }
18430
18778
  const out2 = [];
18431
18779
  if (cr && lf) out2.push("log_injection");
18432
18780
  if (lt && gt) out2.push("xss");
18781
+ if (openBrace && closeBrace) out2.push("code_injection");
18433
18782
  return out2;
18434
18783
  }
18435
18784
  function matchesSanitizerPattern(call, pattern) {
@@ -20543,6 +20892,9 @@ var ConstantPropagator = class _ConstantPropagator {
20543
20892
  case "assignment_statement":
20544
20893
  this.handleGoAssignment(node);
20545
20894
  return false;
20895
+ case "var_declaration":
20896
+ this.handleGoVarDeclaration(node);
20897
+ return false;
20546
20898
  case "update_expression":
20547
20899
  this.handleUpdateExpression(node);
20548
20900
  return false;
@@ -20950,6 +21302,64 @@ var ConstantPropagator = class _ConstantPropagator {
20950
21302
  * helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
20951
21303
  * assignments got before this handler existed: the DFG decides.
20952
21304
  */
21305
+ /** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
21306
+ handleGoVarDeclaration(node) {
21307
+ for (let i2 = 0; i2 < node.childCount; i2++) {
21308
+ const spec = node.child(i2);
21309
+ if (!spec || spec.type !== "var_spec") continue;
21310
+ const nameNode = spec.childForFieldName("name");
21311
+ let valueNode = spec.childForFieldName("value");
21312
+ if (!nameNode || !valueNode) continue;
21313
+ const names = [];
21314
+ if (nameNode.type === "identifier") {
21315
+ names.push(getNodeText2(nameNode, this.source));
21316
+ } else if (nameNode.type === "identifier_list") {
21317
+ for (const c of nameNode.children) {
21318
+ if (c.type === "identifier") names.push(getNodeText2(c, this.source));
21319
+ }
21320
+ }
21321
+ if (valueNode.type === "expression_list") {
21322
+ valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
21323
+ }
21324
+ if (names.length === 0) continue;
21325
+ const line = getNodeLine(node);
21326
+ for (const varName of names) {
21327
+ if (varName === "_") continue;
21328
+ const scopedName = this.getScopedName(varName);
21329
+ this.goScopedNames.add(scopedName);
21330
+ if (this.isTaintedExpression(valueNode)) {
21331
+ this.goUnknownVars.add(scopedName);
21332
+ this.tainted.add(scopedName);
21333
+ this.sanitizedVars.delete(scopedName);
21334
+ this.sanitizedVarUncovered.delete(scopedName);
21335
+ this.definitionNodes.set(scopedName, valueNode);
21336
+ this.symbols.set(scopedName, createUnknown(line));
21337
+ continue;
21338
+ }
21339
+ if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
21340
+ this.goUnknownVars.add(scopedName);
21341
+ this.symbols.delete(scopedName);
21342
+ this.sanitizedVars.delete(scopedName);
21343
+ this.sanitizedVarUncovered.delete(scopedName);
21344
+ if (!this.inConditionalBranch) {
21345
+ this.tainted.delete(scopedName);
21346
+ this.definitionNodes.delete(scopedName);
21347
+ }
21348
+ continue;
21349
+ }
21350
+ if (this.inConditionalBranch) {
21351
+ this.goUnknownVars.add(scopedName);
21352
+ this.symbols.delete(scopedName);
21353
+ continue;
21354
+ }
21355
+ this.tainted.delete(scopedName);
21356
+ this.definitionNodes.delete(scopedName);
21357
+ this.symbols.set(scopedName, this.evaluateExpression(valueNode));
21358
+ this.sanitizedVars.delete(scopedName);
21359
+ this.sanitizedVarUncovered.delete(scopedName);
21360
+ }
21361
+ }
21362
+ }
20953
21363
  handleGoAssignment(node) {
20954
21364
  const left = node.childForFieldName("left");
20955
21365
  const right = node.childForFieldName("right");
@@ -21766,6 +22176,24 @@ var ConstantPropagator = class _ConstantPropagator {
21766
22176
  cache.set(rootId, false);
21767
22177
  return false;
21768
22178
  }
22179
+ /** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
22180
+ argIsSanitizedTaint(call) {
22181
+ const argsNode = call.childForFieldName("arguments");
22182
+ if (!argsNode) return false;
22183
+ for (const arg of argsNode.children) {
22184
+ if (arg.type !== "method_invocation") continue;
22185
+ const name2 = arg.childForFieldName("name");
22186
+ if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
22187
+ const inner = arg.childForFieldName("arguments");
22188
+ if (!inner) continue;
22189
+ for (const a of inner.children) {
22190
+ if (a.type === "(" || a.type === ")" || a.type === ",") continue;
22191
+ if (this.isTaintedExpression(a)) return true;
22192
+ }
22193
+ if (this.argIsSanitizedTaint(arg)) return true;
22194
+ }
22195
+ return false;
22196
+ }
21769
22197
  isTaintedExpressionStep(node) {
21770
22198
  const text = getNodeText2(node, this.source);
21771
22199
  if (node.type === "method_invocation") {
@@ -21773,6 +22201,9 @@ var ConstantPropagator = class _ConstantPropagator {
21773
22201
  const objectNode = node.childForFieldName("object");
21774
22202
  if (nameNode) {
21775
22203
  const methodName = getNodeText2(nameNode, this.source);
22204
+ if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
22205
+ return true;
22206
+ }
21776
22207
  if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
21777
22208
  return false;
21778
22209
  }