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
@@ -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
  }
@@ -10410,6 +10410,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10410
10410
  carryBranchDefsOut(popped, scopeStack, alwaysRuns);
10411
10411
  break;
10412
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
+ }
10413
10486
  case "else_clause": {
10414
10487
  for (let i2 = 0; i2 < stmt.childCount; i2++) {
10415
10488
  const arm = stmt.child(i2);
@@ -11025,6 +11098,42 @@ function buildBashDFG(tree) {
11025
11098
  defs.push(def);
11026
11099
  currentScope(scopeStack).set(p, def.id);
11027
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
+ };
11028
11137
  walkTree(tree.rootNode, (node) => {
11029
11138
  if (node.type === "variable_assignment") {
11030
11139
  const nameNode = node.childForFieldName("name");
@@ -11077,7 +11186,7 @@ function buildBashDFG(tree) {
11077
11186
  if (varNameNode) {
11078
11187
  const varName = getNodeText(varNameNode);
11079
11188
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
11080
- let reachingDef = findReachingDef(varName, scopeStack);
11189
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11081
11190
  if (reachingDef === null && !positionalParams.includes(varName)) {
11082
11191
  const def = {
11083
11192
  id: defIdCounter++,
@@ -11101,7 +11210,7 @@ function buildBashDFG(tree) {
11101
11210
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
11102
11211
  if (varNameNode && varNameNode.type === "variable_name") {
11103
11212
  const varName = getNodeText(varNameNode);
11104
- let reachingDef = findReachingDef(varName, scopeStack);
11213
+ let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
11105
11214
  if (reachingDef === null && !positionalParams.includes(varName)) {
11106
11215
  const def = {
11107
11216
  id: defIdCounter++,
@@ -11123,8 +11232,56 @@ function buildBashDFG(tree) {
11123
11232
  }
11124
11233
  });
11125
11234
  const chains = computeChains(defs, uses);
11235
+ chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
11126
11236
  return { defs, uses, chains };
11127
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
+ }
11128
11285
  function buildRustDFG(tree, cache) {
11129
11286
  const defs = [];
11130
11287
  const uses = [];
@@ -12360,6 +12517,195 @@ function parseDistributedSliceAttribute(attrItem) {
12360
12517
  };
12361
12518
  }
12362
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
+
12363
12709
  // src/analysis/config-loader.ts
12364
12710
  var DEFAULT_SOURCES = [
12365
12711
  // HTTP Sources (Servlet API)
@@ -13061,6 +13407,10 @@ var DEFAULT_SOURCES = [
13061
13407
  { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13062
13408
  { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
13063
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"] },
13064
13414
  { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
13065
13415
  { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
13066
13416
  { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
@@ -13240,7 +13590,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
13240
13590
  // on arg[1] (the *Cookie) catches struct-literal composite tainting.
13241
13591
  { method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
13242
13592
  // gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
13243
- { 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"] }
13244
13596
  ];
13245
13597
  var DEFAULT_SINKS = [
13246
13598
  // SQL Injection (CWE-89)
@@ -13901,7 +14253,9 @@ var DEFAULT_SINKS = [
13901
14253
  { method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13902
14254
  { method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
13903
14255
  // Cron expression parsing (DoS/ReDoS)
13904
- { 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"] },
13905
14259
  { method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
13906
14260
  // ff4j feature flags
13907
14261
  { method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
@@ -13998,9 +14352,13 @@ var DEFAULT_SINKS = [
13998
14352
  { method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
13999
14353
  { method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
14000
14354
  // XXE - XML External Entity (CWE-611)
14001
- { method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
14002
- { method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
14003
- { 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"] },
14004
14362
  { method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
14005
14363
  { method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
14006
14364
  { method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
@@ -14255,6 +14613,46 @@ var DEFAULT_SINKS = [
14255
14613
  { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14256
14614
  { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
14257
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"] },
14258
14656
  // TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
14259
14657
  // escape hatch is already covered by the classless `query` sink in the JS
14260
14658
  // plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
@@ -14365,6 +14763,8 @@ var DEFAULT_SINKS = [
14365
14763
  { method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14366
14764
  { method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14367
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"] },
14368
14768
  // needle library (used in NodeGoat)
14369
14769
  { method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
14370
14770
  { method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -14859,7 +15259,7 @@ var DEFAULT_SINKS = [
14859
15259
  // receiver-taint or operator-LHS-taint tracking — the format string is the
14860
15260
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
14861
15261
  // C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
14862
- { 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"] },
14863
15263
  { method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
14864
15264
  // Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
14865
15265
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
@@ -17117,6 +17517,198 @@ function constTableLookupDefIds(defs, code, language) {
17117
17517
  return out2;
17118
17518
  }
17119
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
+
17120
17712
  // src/analysis/passes/language-sources-pass.ts
17121
17713
  var JS_DOM_XSS_SINKS = [
17122
17714
  { pattern: /\.innerHTML\s*=/, type: "xss", cwe: "CWE-79", severity: "critical" },
@@ -17368,6 +17960,7 @@ var LanguageSourcesPass = class {
17368
17960
  }
17369
17961
  additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
17370
17962
  additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
17963
+ additionalSanitizers.push(...findBashVerifiedPackageDownloadSanitizers(code));
17371
17964
  additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
17372
17965
  }
17373
17966
  if (language === "go") {
@@ -17483,8 +18076,13 @@ var LanguageSourcesPass = class {
17483
18076
  }
17484
18077
  }
17485
18078
  if (language === "rust") {
18079
+ additionalSanitizers.push(...findRustConstSliceMembershipGuardSanitizers(code));
17486
18080
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
17487
18081
  additionalSanitizers.push(...findRustCanonicalizeGuardSanitizers(code));
18082
+ additionalSanitizers.push(
18083
+ ...findRustNumericRangeCheckGuardSanitizers(code)
18084
+ );
18085
+ additionalSanitizers.push(...findRustPatInfoHttpOnlyResponseSanitizers(code));
17488
18086
  additionalSanitizers.push(...findRustLiteralEqualityGuardSanitizers(code));
17489
18087
  additionalSanitizers.push(...findRustArgvCommandSanitizers(code));
17490
18088
  const rustMisconfigFindings = findRustPatternFindings(code, graph.ir.meta.file);
@@ -17542,6 +18140,9 @@ var LanguageSourcesPass = class {
17542
18140
  additionalSanitizers.push(
17543
18141
  ...findJsCommandAllowlistGuardSanitizers(code)
17544
18142
  );
18143
+ additionalSanitizers.push(
18144
+ ...findJsNumericRangeCheckGuardSanitizers(code)
18145
+ );
17545
18146
  for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
17546
18147
  ctx.addFinding(finding);
17547
18148
  }
@@ -17575,6 +18176,12 @@ var LanguageSourcesPass = class {
17575
18176
  )) {
17576
18177
  ctx.addFinding(finding);
17577
18178
  }
18179
+ for (const finding of findJsZipHeaderFailopenFindings(
18180
+ code,
18181
+ graph.ir.meta.file
18182
+ )) {
18183
+ ctx.addFinding(finding);
18184
+ }
17578
18185
  for (const finding of findJsJsonParseBodyFindings(
17579
18186
  code,
17580
18187
  graph.ir.meta.file
@@ -17624,6 +18231,7 @@ var LanguageSourcesPass = class {
17624
18231
  }
17625
18232
  additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
17626
18233
  additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, "java"));
18234
+ additionalSanitizers.push(...findJavaTemplateDelimiterWrapperSanitizers(code));
17627
18235
  additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
17628
18236
  for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
17629
18237
  ctx.addFinding(finding);
@@ -18833,9 +19441,9 @@ function findGraphqlResolverArgSources(sourceCode) {
18833
19441
  maps.push(open + inline.index + inline[0].length - 1);
18834
19442
  continue;
18835
19443
  }
18836
- const named = opts.match(/\bresolvers\s*(?::\s*(\w+))?\s*[,}]/);
18837
- if (!named) continue;
18838
- 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";
18839
19447
  const decl = new RegExp(
18840
19448
  `\\b(?:const|let|var)\\s+${ident}\\s*(?::\\s*[A-Za-z_$][\\w$]*(?:\\s*\\.\\s*[A-Za-z_$][\\w$]*)*(?:\\s*<[^<>=]*(?:<[^<>=]*>[^<>=]*)*>)?\\s*)?=\\s*\\{`
18841
19449
  ).exec(sourceCode);
@@ -18998,15 +19606,15 @@ function evaluatePythonConstExpression(expr, consts) {
18998
19606
  if (parts2.length !== 1) return void 0;
18999
19607
  const matched = expr.match(/\d+|[A-Za-z_][A-Za-z0-9_]*|\/\/|[-+*/%()]|\S/g);
19000
19608
  if (!matched) return void 0;
19001
- const tokens = matched;
19609
+ const tokens2 = matched;
19002
19610
  let pos = 0;
19003
- const peek = () => tokens[pos];
19611
+ const peek = () => tokens2[pos];
19004
19612
  const parseFactor = () => {
19005
- const tok = tokens[pos++];
19613
+ const tok = tokens2[pos++];
19006
19614
  if (tok === void 0) return void 0;
19007
19615
  if (tok === "(") {
19008
19616
  const inner = parseSum();
19009
- if (tokens[pos++] !== ")") return void 0;
19617
+ if (tokens2[pos++] !== ")") return void 0;
19010
19618
  return inner;
19011
19619
  }
19012
19620
  if (tok === "-") {
@@ -19021,7 +19629,7 @@ function evaluatePythonConstExpression(expr, consts) {
19021
19629
  let value = parseFactor();
19022
19630
  if (value === void 0) return void 0;
19023
19631
  while (peek() === "*" || peek() === "/" || peek() === "//" || peek() === "%") {
19024
- const op = tokens[pos++];
19632
+ const op = tokens2[pos++];
19025
19633
  const rhs = parseFactor();
19026
19634
  if (rhs === void 0) return void 0;
19027
19635
  if ((op === "/" || op === "//" || op === "%") && rhs === 0) return void 0;
@@ -19036,7 +19644,7 @@ function evaluatePythonConstExpression(expr, consts) {
19036
19644
  let value = parseTerm();
19037
19645
  if (value === void 0) return void 0;
19038
19646
  while (peek() === "+" || peek() === "-") {
19039
- const op = tokens[pos++];
19647
+ const op = tokens2[pos++];
19040
19648
  const rhs = parseTerm();
19041
19649
  if (rhs === void 0) return void 0;
19042
19650
  value = op === "+" ? value + rhs : value - rhs;
@@ -19044,7 +19652,7 @@ function evaluatePythonConstExpression(expr, consts) {
19044
19652
  return value;
19045
19653
  }
19046
19654
  const result = parseSum();
19047
- return pos === tokens.length ? result : void 0;
19655
+ return pos === tokens2.length ? result : void 0;
19048
19656
  }
19049
19657
  function normalizeListIndex(index, length) {
19050
19658
  const resolved = index < 0 ? length + index : index;
@@ -19092,6 +19700,140 @@ function computeDeadPythonBranchLines(lines) {
19092
19700
  }
19093
19701
  return dead;
19094
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
+ }
19095
19837
  function buildPythonTaintedVars(sourceCode) {
19096
19838
  const tainted = /* @__PURE__ */ new Map();
19097
19839
  const containerTainted = /* @__PURE__ */ new Map();
@@ -19811,6 +20553,82 @@ function buildRustTaintedVars(sourceCode, seedVars) {
19811
20553
  }
19812
20554
  return derived;
19813
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
+ }
19814
20632
  function joinWrappedJavaStatement(lines, i2) {
19815
20633
  const strip = (l) => l.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)'/g, '""').replace(/\/\/.*$/, "");
19816
20634
  const first = strip(lines[i2]);
@@ -19832,12 +20650,19 @@ function joinWrappedJavaStatement(lines, i2) {
19832
20650
  }
19833
20651
  return null;
19834
20652
  }
19835
- function buildJavaTaintedVars(sourceCode, seedVars) {
20653
+ function buildJavaTaintedVars(sourceCode, seedVars, deadBranchLines, options) {
19836
20654
  const derived = /* @__PURE__ */ new Map();
19837
20655
  const knownTainted = new Set(seedVars);
19838
- 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();
19839
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*$/;
19840
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*$/;
19841
20666
  const JAVA_KEYWORDS = /* @__PURE__ */ new Set([
19842
20667
  "if",
19843
20668
  "else",
@@ -19873,10 +20698,23 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
19873
20698
  while (changed && guard < lines.length + 2) {
19874
20699
  changed = false;
19875
20700
  guard++;
20701
+ listElems.clear();
20702
+ mapSlots.clear();
20703
+ intConsts.clear();
19876
20704
  for (let i2 = 0; i2 < lines.length; i2++) {
20705
+ if (deadBranchLines !== void 0 && dead.has(i2)) continue;
19877
20706
  const line = lines[i2];
19878
20707
  const trimmed = line.trimStart();
19879
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
+ }
19880
20718
  const arrayCopyRe = /\bSystem\.arraycopy\s*\(\s*([A-Za-z_]\w*)\s*,\s*[^,]+\s*,\s*([A-Za-z_]\w*)\s*,/;
19881
20719
  const acMatch = arrayCopyRe.exec(line);
19882
20720
  if (acMatch) {
@@ -19906,17 +20744,79 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
19906
20744
  let assignMatch = !declMatch ? assignRe.exec(statement) : null;
19907
20745
  if (!declMatch && !assignMatch) {
19908
20746
  const joined = joinWrappedJavaStatement(lines, i2);
19909
- if (joined === null) continue;
19910
- statement = joined;
19911
- declMatch = declRe.exec(statement);
19912
- 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;
19913
20796
  }
19914
20797
  const m = declMatch ?? assignMatch;
19915
20798
  if (!m) continue;
19916
20799
  const lhs = m[1];
19917
- const rhs = m[2];
20800
+ let rhs = m[2];
19918
20801
  if (JAVA_KEYWORDS.has(lhs)) continue;
19919
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
+ }
19920
20820
  const rhsCode = rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
19921
20821
  const rhsIdents = rhsCode.match(/[\p{L}\p{N}_]+/gu);
19922
20822
  const ref = rhsIdents !== null && rhsIdents.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode));
@@ -19968,8 +20868,11 @@ function findBashTaintSources(sourceCode, dfg) {
19968
20868
  const insideFunction = braceDepth > 0;
19969
20869
  if (!insideFunction) {
19970
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, "''");
19971
20874
  let m;
19972
- while ((m = positionalRe.exec(line)) !== null) {
20875
+ while ((m = positionalRe.exec(topLevelText)) !== null) {
19973
20876
  const param = m[1] ?? m[2];
19974
20877
  const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
19975
20878
  if (!alreadyExists) {
@@ -20025,10 +20928,10 @@ function findBashTaintSources(sourceCode, dfg) {
20025
20928
  if (readMatch) {
20026
20929
  const argStr = readMatch[1].trim();
20027
20930
  const noRedirect = argStr.replace(/\s*<[^<].*$/, "").trim();
20028
- const tokens = noRedirect.split(/\s+/);
20931
+ const tokens2 = noRedirect.split(/\s+/);
20029
20932
  const varNames = [];
20030
- for (let ti = 0; ti < tokens.length; ti++) {
20031
- const tok = tokens[ti];
20933
+ for (let ti = 0; ti < tokens2.length; ti++) {
20934
+ const tok = tokens2[ti];
20032
20935
  if (!tok) continue;
20033
20936
  if (tok.startsWith("-")) {
20034
20937
  if (/^-[antpidu]$|^-N$/.test(tok)) ti++;
@@ -20053,10 +20956,10 @@ function findBashTaintSources(sourceCode, dfg) {
20053
20956
  const mapfileMatch = /^(?:mapfile|readarray)\s*\(/.test(trimmed) ? null : trimmed.match(/^(?:mapfile|readarray)\b([^#(]*)$/);
20054
20957
  if (mapfileMatch) {
20055
20958
  const argStr = mapfileMatch[1].trim();
20056
- const tokens = argStr.split(/\s+/);
20959
+ const tokens2 = argStr.split(/\s+/);
20057
20960
  const varNames = [];
20058
- for (let ti = 0; ti < tokens.length; ti++) {
20059
- const tok = tokens[ti];
20961
+ for (let ti = 0; ti < tokens2.length; ti++) {
20962
+ const tok = tokens2[ti];
20060
20963
  if (!tok) continue;
20061
20964
  if (tok.startsWith("-")) {
20062
20965
  if (/^-[cCnOsud]$/.test(tok)) ti++;
@@ -20441,6 +21344,30 @@ function findBashRealpathPrefixGuardSanitizers(code) {
20441
21344
  }
20442
21345
  return sanitizers;
20443
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
+ }
20444
21371
  function findGoMapAllowlistGuardSanitizers(code) {
20445
21372
  const sanitizers = [];
20446
21373
  const lines = code.split("\n");
@@ -20596,6 +21523,160 @@ function findPythonNetlocAllowlistGuardSanitizers(code) {
20596
21523
  }
20597
21524
  return sanitizers;
20598
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
+ }
20599
21680
  function findPythonRangeCheckGuardSanitizers(code) {
20600
21681
  const sanitizers = [];
20601
21682
  const lines = code.split("\n");
@@ -21182,6 +22263,127 @@ function findPythonDefusedXmlSanitizers(code) {
21182
22263
  }
21183
22264
  return sanitizers;
21184
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
+ }
21185
22387
  function findRustSetAllowlistGuardSanitizers(code) {
21186
22388
  const sanitizers = [];
21187
22389
  const lines = code.split("\n");
@@ -21338,6 +22540,13 @@ function findJsCsvFormulaPrefixSanitizers(code) {
21338
22540
  }
21339
22541
  return sanitizers;
21340
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
+ }
21341
22550
  function findJsWrapperFunctionSanitizers(code) {
21342
22551
  const sanitizers = [];
21343
22552
  const lines = code.split("\n");
@@ -21357,8 +22566,12 @@ function findJsWrapperFunctionSanitizers(code) {
21357
22566
  kinds.add("external_taint_escape");
21358
22567
  }
21359
22568
  if (crlfCharClass.test(body2)) {
21360
- kinds.add("log_injection");
21361
- 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
+ }
21362
22575
  }
21363
22576
  if (kinds.size > 0) wrappers.set(name2, kinds);
21364
22577
  }
@@ -22160,6 +23373,74 @@ function javaBodyStripsCrAndLf(body2) {
22160
23373
  }
22161
23374
  return cr && lf;
22162
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
+ }
22163
23444
  function findCrlfWrapperFunctionSanitizers(code, language) {
22164
23445
  const sanitizers = [];
22165
23446
  const lines = code.split("\n");
@@ -24575,6 +25856,79 @@ function findJsLdapInjectionFindings(code, file) {
24575
25856
  snippet: line.trim()
24576
25857
  });
24577
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
+ }
24578
25932
  return findings;
24579
25933
  }
24580
25934
  function findGoLdapInjectionFindings(code, file) {
@@ -25077,6 +26431,40 @@ function findGoGobDeserializationFindings(code, file) {
25077
26431
  }
25078
26432
  return findings;
25079
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
+ }
25080
26468
  function findJsJsonParseBodyFindings(code, file) {
25081
26469
  const findings = [];
25082
26470
  if (typeof code !== "string" || code.length === 0) return findings;
@@ -25110,12 +26498,13 @@ function findJsJsonParseBodyFindings(code, file) {
25110
26498
  }
25111
26499
  const callRe = /\bJSON\s*\.\s*parse\s*\(\s*([^)]+?)\s*\)/g;
25112
26500
  const seen = /* @__PURE__ */ new Set();
26501
+ const codeLines = maskJsLiteralsAndComments(code).split("\n");
25113
26502
  for (let i2 = 0; i2 < lines.length; i2++) {
25114
26503
  const line = lines[i2];
25115
- if (line.trim().startsWith("//")) continue;
26504
+ const codeLine = codeLines[i2] ?? "";
25116
26505
  callRe.lastIndex = 0;
25117
26506
  let m;
25118
- while ((m = callRe.exec(line)) !== null) {
26507
+ while ((m = callRe.exec(codeLine)) !== null) {
25119
26508
  const arg = m[1];
25120
26509
  let tainted = reqBodyRe.test(arg);
25121
26510
  if (!tainted) {
@@ -25535,198 +26924,6 @@ function findJavaSpringReturnXSSSinks(code, types, sources, xssSanitizerLines) {
25535
26924
  return out2;
25536
26925
  }
25537
26926
 
25538
- // src/analysis/js-crlf-strip.ts
25539
- var MAX_PATTERN_LENGTH = 200;
25540
- var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
25541
- "replace",
25542
- "replaceAll",
25543
- "trim",
25544
- "trimStart",
25545
- "trimEnd",
25546
- "toString",
25547
- "toLowerCase",
25548
- "toUpperCase",
25549
- "slice",
25550
- "substring"
25551
- ]);
25552
- var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
25553
- function readQuoted(s, i2) {
25554
- const q = s[i2];
25555
- if (q !== '"' && q !== "'" && q !== "`") return null;
25556
- let out2 = "";
25557
- for (let j = i2 + 1; j < s.length; j++) {
25558
- const c = s[j];
25559
- if (c === q) return { value: out2, end: j + 1 };
25560
- if (q === "`" && c === "$" && s[j + 1] === "{") return null;
25561
- if (c === "\\") {
25562
- const n = s[j + 1];
25563
- if (n === void 0) return null;
25564
- switch (n) {
25565
- case "n":
25566
- out2 += "\n";
25567
- j++;
25568
- continue;
25569
- case "r":
25570
- out2 += "\r";
25571
- j++;
25572
- continue;
25573
- case "t":
25574
- out2 += " ";
25575
- j++;
25576
- continue;
25577
- case "0":
25578
- out2 += "\0";
25579
- j++;
25580
- continue;
25581
- case "x": {
25582
- const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
25583
- if (!h) return null;
25584
- out2 += String.fromCharCode(parseInt(h[0], 16));
25585
- j += 3;
25586
- continue;
25587
- }
25588
- case "u": {
25589
- const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
25590
- if (!h) return null;
25591
- out2 += String.fromCharCode(parseInt(h[0], 16));
25592
- j += 5;
25593
- continue;
25594
- }
25595
- default:
25596
- out2 += n;
25597
- j++;
25598
- continue;
25599
- }
25600
- }
25601
- if (c === "\n" && q !== "`") return null;
25602
- out2 += c;
25603
- }
25604
- return null;
25605
- }
25606
- function readRegexLiteral(s, i2) {
25607
- if (s[i2] !== "/") return null;
25608
- let inClass = false;
25609
- for (let j = i2 + 1; j < s.length; j++) {
25610
- const c = s[j];
25611
- if (c === "\n") return null;
25612
- if (c === "\\") {
25613
- j++;
25614
- continue;
25615
- }
25616
- if (inClass) {
25617
- if (c === "]") inClass = false;
25618
- continue;
25619
- }
25620
- if (c === "[") {
25621
- inClass = true;
25622
- continue;
25623
- }
25624
- if (c === "/") {
25625
- const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
25626
- return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
25627
- }
25628
- }
25629
- return null;
25630
- }
25631
- var skipWs = (s, i2) => {
25632
- while (i2 < s.length && /\s/.test(s[i2])) i2++;
25633
- return i2;
25634
- };
25635
- function parseSteps(chainText) {
25636
- const steps = [];
25637
- const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
25638
- let m;
25639
- while ((m = callRe.exec(chainText)) !== null) {
25640
- const kind = m[1];
25641
- let i2 = skipWs(chainText, m.index + m[0].length);
25642
- let pattern;
25643
- const rx = readRegexLiteral(chainText, i2);
25644
- if (rx) {
25645
- if (rx.source.length > MAX_PATTERN_LENGTH) return null;
25646
- try {
25647
- pattern = new RegExp(rx.source, rx.flags);
25648
- } catch {
25649
- return null;
25650
- }
25651
- i2 = rx.end;
25652
- } else {
25653
- const str = readQuoted(chainText, i2);
25654
- if (!str) return null;
25655
- pattern = str.value;
25656
- i2 = str.end;
25657
- }
25658
- i2 = skipWs(chainText, i2);
25659
- if (chainText[i2] !== ",") return null;
25660
- i2 = skipWs(chainText, i2 + 1);
25661
- const rep = readQuoted(chainText, i2);
25662
- if (!rep) return null;
25663
- i2 = skipWs(chainText, rep.end);
25664
- if (chainText[i2] !== ")") return null;
25665
- steps.push({ kind, pattern, replacement: rep.value });
25666
- callRe.lastIndex = i2 + 1;
25667
- }
25668
- return steps;
25669
- }
25670
- function run2(steps, input) {
25671
- let s = input;
25672
- try {
25673
- for (const st of steps) {
25674
- if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
25675
- s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
25676
- }
25677
- } catch {
25678
- return null;
25679
- }
25680
- return s;
25681
- }
25682
- function jsReplaceChainVerdict(chainText) {
25683
- const steps = parseSteps(chainText);
25684
- if (!steps || steps.length === 0) return "undecided";
25685
- const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
25686
- for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
25687
- if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
25688
- }
25689
- const out2 = run2(steps, PROBE);
25690
- if (out2 === null) return "undecided";
25691
- return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
25692
- }
25693
- function jsChainTextAt(sourceLines, line, column) {
25694
- const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
25695
- let depth = 0;
25696
- let prevSig = "";
25697
- let i2 = 0;
25698
- for (; i2 < text.length; i2++) {
25699
- const c = text[i2];
25700
- if (c === '"' || c === "'" || c === "`") {
25701
- for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
25702
- prevSig = c;
25703
- continue;
25704
- }
25705
- if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
25706
- const rx = readRegexLiteral(text, i2);
25707
- if (rx) {
25708
- i2 = rx.end - 1;
25709
- prevSig = "/";
25710
- continue;
25711
- }
25712
- }
25713
- if (c === "(" || c === "[" || c === "{") {
25714
- depth++;
25715
- prevSig = c;
25716
- continue;
25717
- }
25718
- if (c === ")" || c === "]" || c === "}") {
25719
- if (depth === 0) break;
25720
- depth--;
25721
- prevSig = c;
25722
- continue;
25723
- }
25724
- if (depth === 0 && !/[\w$.\s]/.test(c)) break;
25725
- if (!/\s/.test(c)) prevSig = c;
25726
- }
25727
- return text.slice(0, i2);
25728
- }
25729
-
25730
26927
  // src/analysis/constant-propagation/patterns.ts
25731
26928
  var TAINT_PATTERNS = [
25732
26929
  // HTTP Sources (Servlet API)
@@ -26124,13 +27321,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
26124
27321
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
26125
27322
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
26126
27323
  const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
26127
- let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
27324
+ let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
27325
+ sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
26128
27326
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
26129
27327
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
26130
27328
  const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
26131
27329
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
26132
27330
  return { sources, sinks: gatedSinks, sanitizers };
26133
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
+ }
26134
27353
  function sinkPatternAppliesTo(pattern, language) {
26135
27354
  if (language === void 0) return true;
26136
27355
  if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
@@ -26548,6 +27767,46 @@ function isInterproceduralTaintableType(typeName, language) {
26548
27767
  const leaf = baseType.split(".").pop() ?? baseType;
26549
27768
  if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
26550
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
+ }
26551
27810
  if (typeName.endsWith("[]")) {
26552
27811
  const elementType = typeName.slice(0, -2);
26553
27812
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -26662,6 +27921,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
26662
27921
  return false;
26663
27922
  }
26664
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
+ }
26665
27949
  return true;
26666
27950
  }
26667
27951
  function isSafeJSChildProcessCall(call, pattern, language) {
@@ -27065,6 +28349,29 @@ function isRegexReceiver(receiver, sourceLines) {
27065
28349
  const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
27066
28350
  return sourceLines.some((l) => decl.test(l));
27067
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
+ }
27068
28375
  function isDeclaredFunctionReference(arg, sourceLines) {
27069
28376
  if (arg.literal !== null && arg.literal !== void 0) return false;
27070
28377
  if (!sourceLines) return false;
@@ -27269,6 +28576,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27269
28576
  if (isParameterizedQueryCall(call, pattern)) {
27270
28577
  continue;
27271
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
+ }
27272
28585
  if (isSafePythonSubprocessCall(call, pattern, language)) {
27273
28586
  continue;
27274
28587
  }
@@ -27326,9 +28639,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27326
28639
  if (isArgumentlessJsResponseWriter(call, pattern, language)) {
27327
28640
  continue;
27328
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
+ }
27329
28645
  if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
27330
28646
  continue;
27331
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
+ }
27332
28651
  if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
27333
28652
  continue;
27334
28653
  }
@@ -27345,7 +28664,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27345
28664
  continue;
27346
28665
  }
27347
28666
  const location = formatCallLocation(call);
27348
- const key = `${location}:${call.location.line}:${pattern.cwe}`;
28667
+ const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
27349
28668
  const confidence = calculateSinkConfidence(call, pattern);
27350
28669
  const existing = sinkMap.get(key);
27351
28670
  if (!existing || confidence > existing.confidence) {
@@ -27356,6 +28675,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
27356
28675
  cwe: pattern.cwe,
27357
28676
  location,
27358
28677
  line: call.location.line,
28678
+ callColumn: call.location.column,
27359
28679
  confidence,
27360
28680
  method: call.method_name,
27361
28681
  argPositions: pattern.arg_positions,
@@ -27393,6 +28713,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
27393
28713
  return false;
27394
28714
  }
27395
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
+ }
27396
28725
  return pattern.return_tainted === true;
27397
28726
  }
27398
28727
  return false;
@@ -27486,6 +28815,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
27486
28815
  "database",
27487
28816
  "sqlite",
27488
28817
  "conn",
28818
+ "con",
27489
28819
  "connection",
27490
28820
  "client",
27491
28821
  "pool",
@@ -27762,7 +29092,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
27762
29092
  }
27763
29093
  }
27764
29094
  }
27765
- if (!pattern.class && call.receiver) {
29095
+ if (!pattern.class && call.receiver && !pattern.requires_import) {
27766
29096
  if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
27767
29097
  return false;
27768
29098
  }
@@ -28495,6 +29825,8 @@ function javaReplaceChainCredit(chainText) {
28495
29825
  let lf = false;
28496
29826
  let lt = false;
28497
29827
  let gt = false;
29828
+ let openBrace = false;
29829
+ let closeBrace = false;
28498
29830
  for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
28499
29831
  const kind = m[1];
28500
29832
  const rawFrom = m[2] ?? m[3] ?? "";
@@ -28505,10 +29837,13 @@ function javaReplaceChainCredit(chainText) {
28505
29837
  if (cov.lf) lf = true;
28506
29838
  if (from === "<" && to.startsWith("&")) lt = true;
28507
29839
  if (from === ">" && to.startsWith("&")) gt = true;
29840
+ if (from === "{") openBrace = true;
29841
+ if (from === "}") closeBrace = true;
28508
29842
  }
28509
29843
  const out2 = [];
28510
29844
  if (cr && lf) out2.push("log_injection");
28511
29845
  if (lt && gt) out2.push("xss");
29846
+ if (openBrace && closeBrace) out2.push("code_injection");
28512
29847
  return out2;
28513
29848
  }
28514
29849
  function matchesSanitizerPattern(call, pattern) {
@@ -30220,7 +31555,7 @@ var AnalysisPipeline = class {
30220
31555
  this.passes.push(pass);
30221
31556
  return this;
30222
31557
  }
30223
- run(graph, code, language, config, resources) {
31558
+ run(graph, code, language, config, resources, limit) {
30224
31559
  const results = /* @__PURE__ */ new Map();
30225
31560
  const findings = [];
30226
31561
  const context = {
@@ -30249,6 +31584,7 @@ var AnalysisPipeline = class {
30249
31584
  };
30250
31585
  const timingEnabled = globalThis.__circleIrPassTiming === true;
30251
31586
  for (const pass of this.passes) {
31587
+ if (limit && Date.now() > limit.deadline) throw limit.onExpired(pass.name);
30252
31588
  if (timingEnabled) {
30253
31589
  console.error(`[pass-start] ${pass.name} file=${graph.ir.meta.file}`);
30254
31590
  }
@@ -31111,10 +32447,49 @@ var JAVA_LANG_EXCEPTION_TYPES = /* @__PURE__ */ new Set([
31111
32447
  "Error",
31112
32448
  "AssertionError"
31113
32449
  ]);
32450
+ var JAVA_LANG_STRING_ACCUMULATOR_TYPES = /* @__PURE__ */ new Set(["StringBuilder", "StringBuffer"]);
31114
32451
  var JAVA_FORMATTER_CLASSES = /* @__PURE__ */ new Map([
31115
32452
  ["String", "java.lang"],
31116
32453
  ["MessageFormat", "java.text"]
31117
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
+ ]);
31118
32493
  function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
31119
32494
  if (call.method_name !== "format" || !call.receiver) return false;
31120
32495
  const needle = `${call.receiver}.format(`;
@@ -31124,6 +32499,10 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
31124
32499
  }
31125
32500
  return isJdkClassReceiver(call.receiver, JAVA_FORMATTER_CLASSES, declaredTypeNames, imports);
31126
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
+ }
31127
32506
  function isJdkClassReceiver(receiver, classes, declaredTypeNames, imports) {
31128
32507
  const dot = receiver.lastIndexOf(".");
31129
32508
  const simple = dot < 0 ? receiver : receiver.slice(dot + 1);
@@ -31166,12 +32545,18 @@ var JAVA_CHARACTER_PREDICATES = /* @__PURE__ */ new Set([
31166
32545
  "isBmpCodePoint",
31167
32546
  "isSupplementaryCodePoint"
31168
32547
  ]);
32548
+ var JAVA_CHARACTER_METADATA = /* @__PURE__ */ new Set(["getName", "getType", "getNumericValue"]);
31169
32549
  function isJavaCharacterPredicateCall(call, declaredTypeNames, imports) {
31170
- 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
+ }
31171
32554
  return isJdkClassReceiver(call.receiver, JAVA_CHARACTER_CLASS, declaredTypeNames, imports);
31172
32555
  }
31173
32556
  function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
31174
32557
  if (isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls)) return true;
32558
+ if (isJavaObjectsNullCheckCall(call, declaredTypeNames, imports)) return true;
32559
+ if (isJavaLoggerCall(call)) return true;
31175
32560
  if (isJavaCharacterPredicateCall(call, declaredTypeNames, imports)) return true;
31176
32561
  if (call.receiver) return false;
31177
32562
  const isCtor = call.is_constructor === true || (call.resolution?.target ?? "").endsWith(".<init>");
@@ -31179,11 +32564,11 @@ function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
31179
32564
  const written = call.method_name;
31180
32565
  const simple = written.split(".").pop() ?? written;
31181
32566
  if (written.includes(".")) {
31182
- 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));
31183
32568
  }
31184
32569
  if (call.receiver_type_fqn && !call.receiver_type_fqn.startsWith("java.lang.")) return false;
31185
32570
  if (declaredTypeNames.has(simple)) return false;
31186
- return JAVA_LANG_EXCEPTION_TYPES.has(simple);
32571
+ return JAVA_LANG_EXCEPTION_TYPES.has(simple) || JAVA_LANG_STRING_ACCUMULATOR_TYPES.has(simple);
31187
32572
  }
31188
32573
  function isGoConstRegexMatchCall(call, graph, calls) {
31189
32574
  if (call.method_name !== "MatchString" || call.arguments.length !== 1) return false;
@@ -31444,6 +32829,14 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31444
32829
  sanitizerMethods.add(match[1]);
31445
32830
  }
31446
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
+ }
31447
32840
  for (const call of calls) {
31448
32841
  const taintedArgPositions = [];
31449
32842
  const taintedArgVars = [];
@@ -31467,7 +32860,8 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31467
32860
  }
31468
32861
  }
31469
32862
  }
31470
- 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);
31471
32865
  if (!targetMethod) {
31472
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)) {
31473
32867
  const isBash = graph.ir.meta.language === "bash";
@@ -31501,6 +32895,9 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31501
32895
  if (isBash && bashSafeBuiltins.has(call.method_name)) {
31502
32896
  continue;
31503
32897
  }
32898
+ if (isBash && bashScriptFunctions.has(call.method_name)) {
32899
+ continue;
32900
+ }
31504
32901
  if (isBash && taintedArgPositions.length > 0) {
31505
32902
  const terminatorPos = call.arguments.filter((a) => a.expression === "--").map((a) => a.position).reduce((min, p) => min === null || p < min ? p : min, null);
31506
32903
  const earliestTainted = Math.min(...taintedArgPositions);
@@ -31558,6 +32955,12 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
31558
32955
  if (pos < targetMethod.parameters.length) {
31559
32956
  targetMethod.parameters[pos].isTainted = true;
31560
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
+ }
31561
32964
  }
31562
32965
  }
31563
32966
  taintedMethods.add(targetMethod.fqn);
@@ -32942,6 +34345,9 @@ var ConstantPropagator = class _ConstantPropagator {
32942
34345
  case "assignment_statement":
32943
34346
  this.handleGoAssignment(node);
32944
34347
  return false;
34348
+ case "var_declaration":
34349
+ this.handleGoVarDeclaration(node);
34350
+ return false;
32945
34351
  case "update_expression":
32946
34352
  this.handleUpdateExpression(node);
32947
34353
  return false;
@@ -33349,6 +34755,64 @@ var ConstantPropagator = class _ConstantPropagator {
33349
34755
  * helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
33350
34756
  * assignments got before this handler existed: the DFG decides.
33351
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
+ }
33352
34816
  handleGoAssignment(node) {
33353
34817
  const left = node.childForFieldName("left");
33354
34818
  const right = node.childForFieldName("right");
@@ -34165,6 +35629,24 @@ var ConstantPropagator = class _ConstantPropagator {
34165
35629
  cache.set(rootId, false);
34166
35630
  return false;
34167
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
+ }
34168
35650
  isTaintedExpressionStep(node) {
34169
35651
  const text = getNodeText2(node, this.source);
34170
35652
  if (node.type === "method_invocation") {
@@ -34172,6 +35654,9 @@ var ConstantPropagator = class _ConstantPropagator {
34172
35654
  const objectNode = node.childForFieldName("object");
34173
35655
  if (nameNode) {
34174
35656
  const methodName = getNodeText2(nameNode, this.source);
35657
+ if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
35658
+ return true;
35659
+ }
34175
35660
  if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
34176
35661
  return false;
34177
35662
  }
@@ -35147,6 +36632,8 @@ function classifyEntryPointTier(method, enclosingType, ctx) {
35147
36632
  return classifyJsTsEntryPoint(method, enclosingType, ctx);
35148
36633
  case "go":
35149
36634
  return classifyGoEntryPoint(method, enclosingType, ctx);
36635
+ case "csharp":
36636
+ return classifyCSharpEntryPoint(method, enclosingType, ctx);
35150
36637
  case "bash":
35151
36638
  case "shell":
35152
36639
  return classifyBashEntryPoint(method, enclosingType, ctx);
@@ -35399,6 +36886,84 @@ function fileHasJsTsFrameworkRegistration(regs) {
35399
36886
  }
35400
36887
  return false;
35401
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
+ }
35402
36967
  var GO_HTTP_REGISTRAR_METHODS = /* @__PURE__ */ new Set([
35403
36968
  // net/http
35404
36969
  "HandleFunc",
@@ -39007,6 +40572,10 @@ var CSharpCredit = class {
39007
40572
  if (type === "path_traversal") {
39008
40573
  if (/\b([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\([^)]*\)\s*\?\s*\1\s*:\s*null\b/.test(text)) return true;
39009
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
+ }
39010
40579
  return false;
39011
40580
  }
39012
40581
  /**
@@ -39020,6 +40589,10 @@ var CSharpCredit = class {
39020
40589
  const set = /* @__PURE__ */ new Set();
39021
40590
  this.helperCache.set(type, set);
39022
40591
  for (const m of this.methods) {
40592
+ if (type === "open_redirect" && this.openRedirectHelperMethod(m)) {
40593
+ set.add(m.name);
40594
+ continue;
40595
+ }
39023
40596
  const returns = this.returnExprs(m);
39024
40597
  if (returns.length === 0) continue;
39025
40598
  const ok = returns.every((r) => {
@@ -39031,6 +40604,23 @@ var CSharpCredit = class {
39031
40604
  }
39032
40605
  return set;
39033
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
+ }
39034
40624
  /** Every returned expression of a method: `return e;` and expression bodies. */
39035
40625
  returnExprs(m) {
39036
40626
  const out2 = [];
@@ -39263,6 +40853,14 @@ var CSharpCredit = class {
39263
40853
  }
39264
40854
  return out2;
39265
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
+ }
39266
40864
  /** Variables whose host a condition compares against a constant or an allowlist. */
39267
40865
  hostVars(cond, hostOf) {
39268
40866
  const out2 = [];
@@ -39304,7 +40902,7 @@ var CSharpCredit = class {
39304
40902
  return map;
39305
40903
  }
39306
40904
  provenBy(kind, cond, parsedFrom, hostOf, withHelpers) {
39307
- 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);
39308
40906
  if (withHelpers) {
39309
40907
  for (const [name2, pos] of this.guardHelpers(kind)) {
39310
40908
  for (const call of cond.matchAll(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`, "g"))) {
@@ -39334,6 +40932,7 @@ var CSharpCredit = class {
39334
40932
  const kinds = [];
39335
40933
  if (INERT_WHEN_ALPHANUMERIC.has(type)) kinds.push("allowlist", "numeric");
39336
40934
  if (type === "ssrf") kinds.push("host");
40935
+ if (type === "open_redirect") kinds.push("localUrl");
39337
40936
  const proven = /* @__PURE__ */ new Set();
39338
40937
  if (kinds.length === 0) return proven;
39339
40938
  const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
@@ -39576,6 +41175,15 @@ var InterproceduralPass = class {
39576
41175
  }
39577
41176
  }
39578
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
+ );
39579
41187
  const taintBridges = findTaintBridges(interProc);
39580
41188
  interprocedural = {
39581
41189
  tainted_methods: Array.from(interProc.taintedMethods),
@@ -39701,6 +41309,401 @@ var InterproceduralPass = class {
39701
41309
  return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
39702
41310
  }
39703
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
+ }
39704
41707
  function csharpParamReachesSink(code, method, taintedArgs, sink, credit) {
39705
41708
  const lines = code.split(/\r?\n/);
39706
41709
  const params = taintedArgs.length > 0 ? method.parameters.filter((p) => taintedArgs.includes(p.position)) : method.parameters;
@@ -40441,12 +42444,26 @@ var ConstantPropagationPass = class {
40441
42444
  }
40442
42445
  }
40443
42446
  }
40444
- return analyzeConstantPropagation(this.tree, code, {
42447
+ const result = analyzeConstantPropagation(this.tree, code, {
40445
42448
  sanitizerMethods: taintMatcher.sanitizerMethods,
40446
42449
  taintedParameters
40447
42450
  });
42451
+ if (ctx.language === "java") pruneDeadChains(ctx, result.unreachableLines);
42452
+ return result;
40448
42453
  }
40449
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
+ }
40450
42467
 
40451
42468
  // src/analysis/passes/source-semantics-pass.ts
40452
42469
  var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
@@ -40933,13 +42950,123 @@ function argvFirstLiteralIsShell(lineText) {
40933
42950
  const base = m[1].split(/[\\/]/).pop().replace(/\.exe$/i, "").toLowerCase();
40934
42951
  return SHELL_PROGRAMS.has(base);
40935
42952
  }
40936
- 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) {
40937
43043
  if (!methodName) return false;
40938
43044
  for (const t of types) {
40939
43045
  const m = t.methods.find((mm) => mm.name === methodName && sinkLine >= mm.start_line && sinkLine <= mm.end_line);
40940
43046
  if (!m) continue;
40941
43047
  const pos = m.parameters.findIndex((p) => p.name === paramName);
40942
- 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
+ }
40943
43070
  return calls.some((c) => {
40944
43071
  if (c.method_name !== methodName) return false;
40945
43072
  if (c.receiver != null && c.receiver !== "this") return false;
@@ -40947,8 +43074,30 @@ function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, c
40947
43074
  if (c.arguments.length !== m.parameters.length) return false;
40948
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));
40949
43076
  if (shadowed) return false;
40950
- const expr = c.arguments[pos]?.expression;
40951
- 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;
40952
43101
  });
40953
43102
  }
40954
43103
  return false;
@@ -41003,12 +43152,42 @@ var FETCH_GLOBAL_RECEIVER_RE = /(?:^|[^.\w])(?:window|globalThis|self|global)\s*
41003
43152
  var FETCH_MEMBER_RECEIVER_RE = /[\w$)\]]\s*\.\s*fetch\s*\(/;
41004
43153
  var NOSQL_ARRAY_METHODS = /* @__PURE__ */ new Set(["find", "filter", "some", "every", "findIndex", "findLast"]);
41005
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
+ }
41006
43171
  var ORM_BUILDER_KEY_RE = /[{,]\s*(?:where|select|populate|include|orderBy|relations|attributes)\s*:/;
41007
43172
  var BROWSER_COMPONENT_EXT_RE = /\.(?:jsx|tsx|vue|svelte)$/;
41008
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*=/;
41009
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*\(/;
41010
43175
  var FIXED_HOST_TEMPLATE_RE = /`https?:\/\/[^/${}\s`]+[/`]/;
41011
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
+ }
41012
43191
  function literalEndsInitializer(code, end) {
41013
43192
  let i2 = end;
41014
43193
  let crossedNewline = false;
@@ -41180,7 +43359,9 @@ function isUnwrittenLocalMapKeyRead(code, source) {
41180
43359
  if (!read) return false;
41181
43360
  const [, recv, key] = read;
41182
43361
  const ctorRe = new RegExp(
41183
- `\\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*\\(`
41184
43365
  );
41185
43366
  if (!lines.some((l) => ctorRe.test(l))) return false;
41186
43367
  const escapeRe2 = new RegExp(`\\w\\s*\\([^)]*(?<![\\w.])${recv}(?![\\w.])[^)]*\\)`);
@@ -41190,26 +43371,46 @@ function isUnwrittenLocalMapKeyRead(code, source) {
41190
43371
  if (escapeRe2.test(stripped)) return false;
41191
43372
  }
41192
43373
  const putRe = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`);
41193
- if (lines.some((l) => putRe.test(l))) return isLiteralOnlyLocalMap(lines, recv);
43374
+ if (lines.some((l) => putRe.test(l))) return isLiteralOnlyLocalMapKey(lines, recv, key);
41194
43375
  return true;
41195
43376
  }
41196
- function isLiteralOnlyLocalMap(lines, recv) {
41197
- const anyPut = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(`, "g");
41198
- const literalPut = new RegExp(
41199
- `\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*(?:"(?:[^"\\\\]|\\\\.)*"|[A-Za-z_][\\w.]*)\\s*,\\s*"(?:[^"\\\\]|\\\\.)*"\\s*\\)`,
41200
- "g"
41201
- );
43377
+ function isLiteralOnlyLocalMapKey(lines, recv, key) {
43378
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
41202
43379
  const otherMutator = new RegExp(
41203
43380
  `\\b${recv}\\s*\\.\\s*(?:putAll|putIfAbsent|compute|computeIfAbsent|computeIfPresent|merge|replace|replaceAll|entrySet|values)\\s*\\(`
41204
43381
  );
41205
- let puts = 0;
41206
- 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;
41207
43389
  for (const l of lines) {
41208
- if (otherMutator.test(l)) return false;
41209
- puts += (l.match(anyPut) ?? []).length;
41210
- literalPuts += (l.match(literalPut) ?? []).length;
43390
+ putsForKey += (l.match(anyKeyPut) ?? []).length;
43391
+ literalPutsForKey += (l.match(keyPut) ?? []).length;
41211
43392
  }
41212
- 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));
41213
43414
  }
41214
43415
  function csharpContentServesHtml(call) {
41215
43416
  if (call.receiver !== null && call.receiver !== "this" && call.receiver !== "base") return false;
@@ -41217,8 +43418,8 @@ function csharpContentServesHtml(call) {
41217
43418
  if (contentType === void 0) return true;
41218
43419
  const literal = /^@?"([^"]*)"$/.exec(contentType);
41219
43420
  if (literal) return /^\s*(?:text\/html|text\/xml|application\/xml|[\w.-]+\/[\w.-]+\+xml)\b/i.test(literal[1]);
41220
- const named = /^(?:System\s*\.\s*Net\s*\.\s*Mime\s*\.\s*)?MediaTypeNames\s*\.\s*(Text|Application)\s*\.\s*(\w+)$/.exec(contentType);
41221
- 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";
41222
43423
  return true;
41223
43424
  }
41224
43425
  function csharpContentCallInheritsHelper(call, types, sourceLines) {
@@ -41385,7 +43586,7 @@ function findEnclosingMethodFromIr(types, sinkLine) {
41385
43586
  return null;
41386
43587
  }
41387
43588
  function splitJavaConcatTokens(rhs) {
41388
- const tokens = [];
43589
+ const tokens2 = [];
41389
43590
  let cur = "";
41390
43591
  let depth = 0;
41391
43592
  let inString = false;
@@ -41422,7 +43623,7 @@ function splitJavaConcatTokens(rhs) {
41422
43623
  continue;
41423
43624
  }
41424
43625
  if (ch === "+" && depth === 0) {
41425
- tokens.push(cur.trim());
43626
+ tokens2.push(cur.trim());
41426
43627
  cur = "";
41427
43628
  i2++;
41428
43629
  continue;
@@ -41430,8 +43631,8 @@ function splitJavaConcatTokens(rhs) {
41430
43631
  cur += ch;
41431
43632
  i2++;
41432
43633
  }
41433
- if (cur.trim()) tokens.push(cur.trim());
41434
- return tokens;
43634
+ if (cur.trim()) tokens2.push(cur.trim());
43635
+ return tokens2;
41435
43636
  }
41436
43637
  function isImplicitlyAnchoredAllowlistRegex(re) {
41437
43638
  if (re === "") return false;
@@ -41536,7 +43737,7 @@ var SinkFilterPass = class {
41536
43737
  const executableSources = ctx.code && ctx.language ? mergedSources.filter(
41537
43738
  (s) => !isNonExecutableSourceLine(ctx.code, s.line, ctx.language)
41538
43739
  ) : mergedSources;
41539
- 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;
41540
43741
  const sinks = [...taintMatcher.sinks];
41541
43742
  for (const s of langSources.additionalSinks) {
41542
43743
  const springReturn = s.type === "xss" && typeof s.location === "string" && s.location.startsWith("Spring handler returns HTML");
@@ -41844,7 +44045,7 @@ var SinkFilterPass = class {
41844
44045
  const varName = arg0.variable ?? "";
41845
44046
  if (varName !== "" && argExpr === varName && !/[(.]/.test(argExpr)) {
41846
44047
  const t = resolveIdentifierType(varName, ctorCall.in_method, irTypes);
41847
- 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)) {
41848
44049
  return false;
41849
44050
  }
41850
44051
  }
@@ -42027,11 +44228,11 @@ var SinkFilterPass = class {
42027
44228
  rhs = sqlVar;
42028
44229
  }
42029
44230
  if (!rhs) return true;
42030
- const tokens = splitJavaConcatTokens(rhs);
42031
- if (tokens.length < 2) return true;
44231
+ const tokens2 = splitJavaConcatTokens(rhs);
44232
+ if (tokens2.length < 2) return true;
42032
44233
  let hasPlaceholder = false;
42033
44234
  const methodCallNames = [];
42034
- for (const tk of tokens) {
44235
+ for (const tk of tokens2) {
42035
44236
  if (/^"(?:[^"\\]|\\.)*"$/.test(tk)) {
42036
44237
  if (tk.includes("?")) hasPlaceholder = true;
42037
44238
  continue;
@@ -42094,7 +44295,15 @@ var SinkFilterPass = class {
42094
44295
  if (sink.type !== "nosql_injection") return true;
42095
44296
  if (!NOSQL_ARRAY_METHODS.has(sink.method ?? "")) return true;
42096
44297
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
42097
- 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;
42098
44307
  });
42099
44308
  }
42100
44309
  if (["javascript", "typescript"].includes(language)) {
@@ -42211,6 +44420,12 @@ var SinkFilterPass = class {
42211
44420
  return bodyCalls.length === 0 || !bodyCalls.every(rustBodyDeclaresNonHtmlType);
42212
44421
  });
42213
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
+ }
42214
44429
  if (language === "bash") {
42215
44430
  const sourceLines = ctx.code.split("\n");
42216
44431
  filtered = filtered.filter((sink) => {
@@ -42733,7 +44948,9 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
42733
44948
  return sinks.filter((sink) => {
42734
44949
  const callsAtSink = callsByLine.get(sink.line) ?? [];
42735
44950
  const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
42736
- 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;
42737
44954
  const trustArgPositions = language !== "bash" && language !== "shell";
42738
44955
  for (const call of relevantCalls) {
42739
44956
  let allArgsAreClean = true;
@@ -43498,19 +45715,59 @@ function rustAttributesAbove(lines, startLine) {
43498
45715
  continue;
43499
45716
  }
43500
45717
  if (t === "" || t.startsWith("//")) continue;
45718
+ if (out2.length > 0) break;
43501
45719
  break;
43502
45720
  }
43503
45721
  return out2;
43504
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
+ }
43505
45759
  function isLambdaEntryPoint(method, language, lines) {
43506
45760
  const names = method.parameters.map((p) => p.name);
43507
45761
  const first = lines[method.start_line - 1] ?? "";
43508
- if (/^\s/.test(first)) return false;
45762
+ if (!isModuleLevelFunction(method, lines)) return false;
43509
45763
  if (language === "python") {
43510
45764
  return method.name === "lambda_handler" && names.length === 2 && names[0] === "event" && names[1] === "context";
43511
45765
  }
43512
45766
  if (language !== "javascript" && language !== "typescript") return false;
43513
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
+ }
43514
45771
  if (names.length < 2 || names.length > 3 || names[0] !== "event" || names[1] !== "context") return false;
43515
45772
  if (names.length === 3 && names[2] !== "callback") return false;
43516
45773
  if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+handler\b)/.test(first)) return true;
@@ -43520,6 +45777,7 @@ function isLambdaEntryPoint(method, language, lines) {
43520
45777
  function isFrameworkHandler(method, language, lines) {
43521
45778
  if (method.annotations.some((a) => ROUTE_ANNOTATION.test(a))) return true;
43522
45779
  if (isLambdaEntryPoint(method, language, lines)) return true;
45780
+ if (isChannelExportedHandler(method, language, lines)) return true;
43523
45781
  if (language === "rust") {
43524
45782
  if (rustAttributesAbove(lines, method.start_line).some((a) => RUST_ROUTE_ATTR.test(a))) return true;
43525
45783
  if (method.return_type && RUST_HANDLER_RETURN.test(method.return_type)) return true;
@@ -43535,6 +45793,11 @@ function isReactHtmlComponent(m, f, lines) {
43535
45793
  const sig = lines[m.start_line - 1] ?? "";
43536
45794
  return /\bfunction\s+[A-Z][A-Za-z0-9]*\s*\(\s*\{/.test(sig);
43537
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
+ }
43538
45801
  function isPythonYamlLoadParamDeser(f, lines) {
43539
45802
  if (f.source_type !== "interprocedural_param" || f.sink_type !== "deserialization") return false;
43540
45803
  const head = f.path[0]?.variable;
@@ -43664,8 +45927,8 @@ var ParamSourceFlowGatePass = class {
43664
45927
  const viaReceiver = !isCtor && !!owner && owner.name !== "<module>";
43665
45928
  const feeds = (pos) => fed.params.has(`${viaReceiver ? "m" : "f"}:${callee}:${pos}`);
43666
45929
  const head = f.path[0]?.variable;
43667
- const named = m.parameters.findIndex((p) => p.name === head);
43668
- if (named >= 0) return feeds(named);
45930
+ const named2 = m.parameters.findIndex((p) => p.name === head);
45931
+ if (named2 >= 0) return feeds(named2);
43669
45932
  for (const s of sources) {
43670
45933
  if (s.line !== f.source_line || s.type !== "interprocedural_param") continue;
43671
45934
  if (FIELD_CARRIED_SOURCE.test(s.location ?? "")) {
@@ -43683,7 +45946,7 @@ var ParamSourceFlowGatePass = class {
43683
45946
  let keep = true;
43684
45947
  if (f.source_type === "interprocedural_param" && !realSourceLines.has(f.source_line)) {
43685
45948
  const m = enclosingMethod(methods, f.source_line);
43686
- keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines) || ctx.language === "python" && isPythonYamlLoadParamDeser(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));
43687
45950
  }
43688
45951
  if (keep) flows[w++] = f;
43689
45952
  else {
@@ -44359,6 +46622,32 @@ function indexJavaLocalAssignments(code) {
44359
46622
  }
44360
46623
  return index;
44361
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
+ }
44362
46651
  function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
44363
46652
  const lines = typeof code === "string" ? code.split("\n") : [];
44364
46653
  const selfReassign = /* @__PURE__ */ new Map();
@@ -44454,8 +46743,8 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44454
46743
  if (d.line === sourceLine && sources.some((s) => s.line === sourceLine && !CONTAINER_READ_SOURCES.has(s.type))) {
44455
46744
  if (step.line === sinkLine && !constProp.tainted.has(fpLookup)) {
44456
46745
  for (let ln = sourceLine; ln <= sinkLine; ln++) {
44457
- const calleeMatch = (lines[ln - 1] ?? "").match(/:=\s*(\w+)\s*\(/);
44458
- if (calleeMatch && constProp.constantReturningMethods?.has(calleeMatch[1])) {
46746
+ const callee = goConstantHelperCalleeFromLines(lines, ln);
46747
+ if (callee && constProp.constantReturningMethods?.has(callee)) {
44459
46748
  return true;
44460
46749
  }
44461
46750
  }
@@ -44658,7 +46947,17 @@ var TaintPropagationPass = class {
44658
46947
  if (isFP) continue;
44659
46948
  pushIfNew(f);
44660
46949
  }
44661
- 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
+ ) ?? [];
44662
46961
  for (const f of paramFlows) {
44663
46962
  pushIfNew(f);
44664
46963
  }
@@ -44733,6 +47032,22 @@ var TaintPropagationPass = class {
44733
47032
  if (sanitizedNames.has(sourceVar) && coversFlow(sourceVar)) return false;
44734
47033
  return true;
44735
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
+ }
44736
47051
  if (sanitizers && sanitizers.length > 0) {
44737
47052
  const sanitizersByLine = /* @__PURE__ */ new Map();
44738
47053
  for (const san of sanitizers) {
@@ -45125,7 +47440,7 @@ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
45125
47440
  for (const line of lines) {
45126
47441
  const stripped = line.replace(/\s+$/, "");
45127
47442
  if (!stripped || /^[ \t]/.test(line)) continue;
45128
- const m = stripped.match(/^(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
47443
+ const m = stripped.match(/^(?:export\s+)?(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
45129
47444
  if (m) moduleVars.add(m[1]);
45130
47445
  }
45131
47446
  if (moduleVars.size === 0) return flows;
@@ -45141,7 +47456,9 @@ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
45141
47456
  const line = lines[i2];
45142
47457
  if (unreachableLines.has(i2 + 1)) continue;
45143
47458
  for (const lhs of moduleVars) {
45144
- const declRe = new RegExp(`\\b(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
47459
+ const declRe = new RegExp(
47460
+ `\\b(?:export\\s+)?(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
47461
+ );
45145
47462
  if (declRe.test(line)) continue;
45146
47463
  const m = line.match(
45147
47464
  new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*([^;]+)`)
@@ -45263,6 +47580,16 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
45263
47580
  }
45264
47581
  return true;
45265
47582
  }
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
+ }
45266
47593
  function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
45267
47594
  const taintedVars = constProp.tainted;
45268
47595
  const unreachableLines = constProp.unreachableLines;
@@ -45305,7 +47632,23 @@ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, gr
45305
47632
  );
45306
47633
  const bareFieldRead = fieldNames.has(varName) && expr === varName && !methodHasLocalDecl(code, call.in_method, varName, call.location.line);
45307
47634
  const isFieldUse = explicitField || bareFieldRead;
45308
- 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) {
45309
47652
  const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, varName);
45310
47653
  if (source) {
45311
47654
  if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
@@ -45340,12 +47683,23 @@ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, gr
45340
47683
  { method: "peek", re: /(\w+)\.peek\(/ },
45341
47684
  { method: "toArray", re: /(\w+)\.toArray\(/ }
45342
47685
  ];
45343
- for (const { re } of collectionPatterns) {
47686
+ for (const { method, re } of collectionPatterns) {
45344
47687
  const match = expr.match(re);
45345
47688
  if (match) {
45346
47689
  const collectionVar = match[1];
45347
47690
  const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
45348
- 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
+ {
45349
47703
  const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
45350
47704
  if (source) {
45351
47705
  if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
@@ -45422,6 +47776,39 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
45422
47776
  }
45423
47777
  return flows;
45424
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
+ }
45425
47812
  function compoundArgParamNames(expression, paramNames) {
45426
47813
  const stripped = expression.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, (m) => " ".repeat(m.length));
45427
47814
  const matched = [];
@@ -45435,7 +47822,7 @@ function compoundArgParamNames(expression, paramNames) {
45435
47822
  }
45436
47823
  return matched;
45437
47824
  }
45438
- function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
47825
+ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code, language, filePath) {
45439
47826
  const flows = [];
45440
47827
  const paramSourcesByMethod = /* @__PURE__ */ new Map();
45441
47828
  for (const source of sources) {
@@ -45476,6 +47863,20 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
45476
47863
  for (const varName of varNames) {
45477
47864
  const paramSource = methodParamSources.get(varName);
45478
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
+ }
45479
47880
  const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
45480
47881
  if (!exists) {
45481
47882
  if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, paramSource.line, sink.line, unreachableLines)) {
@@ -46470,9 +48871,18 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
46470
48871
  if (!prev || s.line < prev.line) realSourceByVar.set(s.variable, s);
46471
48872
  }
46472
48873
  for (const [varName, derivedLine] of derived) {
46473
- if (!varName || existingVars.has(varName)) continue;
48874
+ if (!varName) continue;
46474
48875
  const owner = methodAt(derivedLine);
46475
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
+ }
46476
48886
  let scopedAnchor = anchor;
46477
48887
  if (owner) {
46478
48888
  let best;
@@ -51831,10 +54241,28 @@ var InsecureCookiePass = class {
51831
54241
  * unassigned `new Cookie(...)` only looks at that statement, so another
51832
54242
  * cookie's setter cannot cover it.
51833
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
+ }
51834
54262
  javaCookieSetterTrue(code, call, setter) {
51835
- const line = code.split("\n")[(call.location.line ?? 1) - 1] ?? "";
51836
- const assign = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(line);
51837
- 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);
51838
54266
  const re = varName ? new RegExp(`\\b${varName}\\.${setter}\\s*\\(\\s*true\\s*\\)`) : new RegExp(`\\.${setter}\\s*\\(\\s*true\\s*\\)`);
51839
54267
  return re.test(varName ? code : line);
51840
54268
  }
@@ -52917,6 +55345,9 @@ var WeakRandomPass = class {
52917
55345
  if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
52918
55346
  return "Math.random";
52919
55347
  }
55348
+ if (this.jsIsModuleScopedCryptoRandomUuid(call, ctx)) {
55349
+ return "crypto.randomUUID (module scope)";
55350
+ }
52920
55351
  return null;
52921
55352
  }
52922
55353
  if (language === "go") {
@@ -52954,6 +55385,26 @@ var WeakRandomPass = class {
52954
55385
  * file (i.e. `math/rand` imported unaliased; `crypto/rand` is either not
52955
55386
  * imported or imported under an alias).
52956
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
+ }
52957
55408
  goMathRandIsActive(ctx) {
52958
55409
  const imports = ctx.graph.ir.imports ?? [];
52959
55410
  let mathRandUnaliased = false;
@@ -54265,6 +56716,13 @@ var PY_HTTP_RECEIVERS = /* @__PURE__ */ new Set([
54265
56716
  var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
54266
56717
  var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
54267
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
+ }
54268
56726
  var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
54269
56727
  var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
54270
56728
  var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
@@ -54273,6 +56731,14 @@ var ALLOW_ALL_HOSTNAME_VERIFIERS = /* @__PURE__ */ new Set([
54273
56731
  "new AllowAllHostnameVerifier()",
54274
56732
  "new NoopHostnameVerifier()"
54275
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/;
54276
56742
  var TlsVerifyDisabledPass = class {
54277
56743
  name = "tls-verify-disabled";
54278
56744
  category = "security";
@@ -54387,6 +56853,18 @@ var TlsVerifyDisabledPass = class {
54387
56853
  });
54388
56854
  }
54389
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
+ }
54390
56868
  }
54391
56869
  if (language === "python") {
54392
56870
  for (let i2 = 0; i2 < lines.length; i2++) {
@@ -54410,6 +56888,35 @@ var TlsVerifyDisabledPass = class {
54410
56888
  api: "process.env"
54411
56889
  });
54412
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
+ }
54413
56920
  }
54414
56921
  }
54415
56922
  if (language === "csharp") {
@@ -54429,16 +56936,90 @@ var TlsVerifyDisabledPass = class {
54429
56936
  }
54430
56937
  return out2;
54431
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
+ }
54432
57007
  fixFor(language, pattern) {
54433
57008
  if (pattern.includes("InsecureSkipVerify")) {
54434
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.";
54435
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
+ }
54436
57014
  if (pattern.includes("verify=False")) {
54437
57015
  return "Remove `verify=False`. To trust a private CA, pass `verify='/path/to/ca.pem'`.";
54438
57016
  }
54439
57017
  if (pattern.includes("rejectUnauthorized")) {
54440
57018
  return "Remove `rejectUnauthorized: false`. To trust a private CA, set the `ca` option to the CA cert(s). Never disable TLS verification globally.";
54441
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
+ }
54442
57023
  if (pattern.includes("NODE_TLS_REJECT_UNAUTHORIZED")) {
54443
57024
  return "Remove the `NODE_TLS_REJECT_UNAUTHORIZED=0` assignment \u2014 it globally disables TLS verification for every outbound HTTPS request.";
54444
57025
  }
@@ -56410,6 +58991,36 @@ function boundedMakeDefIds(defs, code, language) {
56410
58991
  return out2;
56411
58992
  }
56412
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
+
56413
59024
  // src/analyzer.ts
56414
59025
  var initialized = false;
56415
59026
  async function initAnalyzer(options = {}) {
@@ -56633,10 +59244,24 @@ function neutralizeOverlongLines(code) {
56633
59244
  }
56634
59245
  return { code: out2, dropped };
56635
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
+ };
56636
59259
  async function analyze(code, filePath, language, options = {}) {
56637
59260
  if (!initialized) {
56638
59261
  await initAnalyzer(options);
56639
59262
  }
59263
+ const fileTimeoutMs = options.fileTimeoutMs ?? 0;
59264
+ const fileDeadline = fileTimeoutMs > 0 ? Date.now() + fileTimeoutMs : 0;
56640
59265
  if (language === "html" || language === "vue") {
56641
59266
  return analyzeMarkupFile(code, filePath, options, language);
56642
59267
  }
@@ -56671,6 +59296,9 @@ async function analyze(code, filePath, language, options = {}) {
56671
59296
  const tree = await parse(code, parseGrammar);
56672
59297
  phaseEnd("parse", tParse);
56673
59298
  try {
59299
+ if (fileDeadline > 0 && Date.now() > fileDeadline) {
59300
+ throw new AnalysisTimeoutError(filePath, Date.now() - (fileDeadline - fileTimeoutMs), "parse");
59301
+ }
56674
59302
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
56675
59303
  const tParseStatus = phaseStart();
56676
59304
  const parseStatus = extractParseStatus(tree);
@@ -56723,7 +59351,8 @@ async function analyze(code, filePath, language, options = {}) {
56723
59351
  const imports = extractImports(tree, language, nodeCache);
56724
59352
  phaseEnd("extractImports", tImports);
56725
59353
  const tExports = phaseStart();
56726
- 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);
56727
59356
  phaseEnd("extractExports", tExports);
56728
59357
  const tCFG = phaseStart();
56729
59358
  const cfg = buildCFG(tree, language, nodeCache);
@@ -56844,7 +59473,14 @@ async function analyze(code, filePath, language, options = {}) {
56844
59473
  if (!disabledPasses.has("unrestricted-file-upload")) pipeline.add(new UnrestrictedFileUploadPass());
56845
59474
  if (!disabledPasses.has("missing-sanitizer-gate")) pipeline.add(new MissingSanitizerGatePass());
56846
59475
  if (!disabledPasses.has("insecure-deserialization-config")) pipeline.add(new InsecureDeserializationConfigPass());
56847
- 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
+ );
56848
59484
  const sinkFilter = results.get("sink-filter");
56849
59485
  const interProc = results.get("interprocedural");
56850
59486
  const taint = {
@@ -56865,6 +59501,12 @@ async function analyze(code, filePath, language, options = {}) {
56865
59501
  taint.flows = [...taint.flows ?? [], ...constructionFlows];
56866
59502
  }
56867
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
+ }
56868
59510
  if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
56869
59511
  const sinkTagsByKey = /* @__PURE__ */ new Map();
56870
59512
  for (const s of taint.sinks) {
@@ -56893,7 +59535,10 @@ async function analyze(code, filePath, language, options = {}) {
56893
59535
  unresolvedItems: unresolved.length
56894
59536
  });
56895
59537
  emitFindingsInstrumentation(filePath, findings, taint);
56896
- 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);
56897
59542
  const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
56898
59543
  const profiledFindings = applyProjectProfileTransform(
56899
59544
  downgradedFindings,