circle-ir 4.16.1 → 4.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/dist/analysis/advisories.d.ts +186 -0
  2. package/dist/analysis/advisories.d.ts.map +1 -0
  3. package/dist/analysis/advisories.js +391 -0
  4. package/dist/analysis/advisories.js.map +1 -0
  5. package/dist/analysis/comment-lines.d.ts +14 -0
  6. package/dist/analysis/comment-lines.d.ts.map +1 -0
  7. package/dist/analysis/comment-lines.js +34 -0
  8. package/dist/analysis/comment-lines.js.map +1 -0
  9. package/dist/analysis/config-loader.d.ts.map +1 -1
  10. package/dist/analysis/config-loader.js +59 -5
  11. package/dist/analysis/config-loader.js.map +1 -1
  12. package/dist/analysis/constant-caller-gate.d.ts +2 -2
  13. package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
  14. package/dist/analysis/constant-caller-gate.js +205 -29
  15. package/dist/analysis/constant-caller-gate.js.map +1 -1
  16. package/dist/analysis/constant-propagation/index.d.ts +2 -0
  17. package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
  18. package/dist/analysis/constant-propagation/index.js +10 -0
  19. package/dist/analysis/constant-propagation/index.js.map +1 -1
  20. package/dist/analysis/constant-propagation/propagator.d.ts +15 -0
  21. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  22. package/dist/analysis/constant-propagation/propagator.js +224 -20
  23. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  24. package/dist/analysis/constant-propagation/types.d.ts +2 -0
  25. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  26. package/dist/analysis/constant-propagation.d.ts +1 -1
  27. package/dist/analysis/constant-propagation.d.ts.map +1 -1
  28. package/dist/analysis/constant-propagation.js +1 -1
  29. package/dist/analysis/constant-propagation.js.map +1 -1
  30. package/dist/analysis/entry-point-detection.d.ts.map +1 -1
  31. package/dist/analysis/entry-point-detection.js +72 -0
  32. package/dist/analysis/entry-point-detection.js.map +1 -1
  33. package/dist/analysis/interprocedural.d.ts.map +1 -1
  34. package/dist/analysis/interprocedural.js +75 -8
  35. package/dist/analysis/interprocedural.js.map +1 -1
  36. package/dist/analysis/maven-declared.d.ts +72 -0
  37. package/dist/analysis/maven-declared.d.ts.map +1 -0
  38. package/dist/analysis/maven-declared.js +216 -0
  39. package/dist/analysis/maven-declared.js.map +1 -0
  40. package/dist/analysis/maven-version.d.ts +28 -0
  41. package/dist/analysis/maven-version.d.ts.map +1 -0
  42. package/dist/analysis/maven-version.js +172 -0
  43. package/dist/analysis/maven-version.js.map +1 -0
  44. package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
  45. package/dist/analysis/passes/constant-propagation-pass.js +29 -1
  46. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
  47. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  48. package/dist/analysis/passes/cross-file-pass.js +84 -2
  49. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  50. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
  51. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  52. package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
  53. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  54. package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
  55. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  56. package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
  57. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  58. package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
  59. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  60. package/dist/analysis/passes/interprocedural-pass.js +562 -0
  61. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  62. package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
  63. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  64. package/dist/analysis/passes/language-sources-pass.js +1079 -14
  65. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  66. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  67. package/dist/analysis/passes/param-source-flow-gate-pass.js +82 -3
  68. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  69. package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
  70. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  71. package/dist/analysis/passes/sink-filter-pass.js +690 -12
  72. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  73. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  74. package/dist/analysis/passes/taint-propagation-pass.js +298 -19
  75. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  76. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
  77. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
  78. package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
  79. package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
  80. package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
  81. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  82. package/dist/analysis/passes/weak-random-pass.js +29 -0
  83. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  84. package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
  85. package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
  86. package/dist/analysis/promote-cross-file-flows.js +32 -0
  87. package/dist/analysis/promote-cross-file-flows.js.map +1 -0
  88. package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
  89. package/dist/analysis/rust-literal-guard.js +100 -1
  90. package/dist/analysis/rust-literal-guard.js.map +1 -1
  91. package/dist/analysis/sbom.d.ts +100 -0
  92. package/dist/analysis/sbom.d.ts.map +1 -1
  93. package/dist/analysis/sbom.js +514 -3
  94. package/dist/analysis/sbom.js.map +1 -1
  95. package/dist/analysis/semver-version.d.ts +30 -0
  96. package/dist/analysis/semver-version.d.ts.map +1 -0
  97. package/dist/analysis/semver-version.js +76 -0
  98. package/dist/analysis/semver-version.js.map +1 -0
  99. package/dist/analysis/taint-matcher.d.ts +5 -0
  100. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  101. package/dist/analysis/taint-matcher.js +226 -4
  102. package/dist/analysis/taint-matcher.js.map +1 -1
  103. package/dist/analyzer.d.ts +57 -0
  104. package/dist/analyzer.d.ts.map +1 -1
  105. package/dist/analyzer.js +69 -6
  106. package/dist/analyzer.js.map +1 -1
  107. package/dist/browser/circle-ir.js +3360 -314
  108. package/dist/core/circle-ir-core.cjs +783 -220
  109. package/dist/core/circle-ir-core.js +783 -220
  110. package/dist/core/extractors/dfg.js +205 -2
  111. package/dist/core/extractors/dfg.js.map +1 -1
  112. package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
  113. package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
  114. package/dist/core/extractors/ts-declaration-detail.js +266 -0
  115. package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
  116. package/dist/core/extractors/types.js +53 -9
  117. package/dist/core/extractors/types.js.map +1 -1
  118. package/dist/graph/analysis-pass.d.ts +4 -1
  119. package/dist/graph/analysis-pass.d.ts.map +1 -1
  120. package/dist/graph/analysis-pass.js +4 -1
  121. package/dist/graph/analysis-pass.js.map +1 -1
  122. package/dist/graph-facts/dead-code.d.ts +62 -0
  123. package/dist/graph-facts/dead-code.d.ts.map +1 -0
  124. package/dist/graph-facts/dead-code.js +90 -0
  125. package/dist/graph-facts/dead-code.js.map +1 -0
  126. package/dist/graph-facts/dependency-calls.d.ts +86 -0
  127. package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
  128. package/dist/graph-facts/dependency-calls.js +93 -0
  129. package/dist/graph-facts/dependency-calls.js.map +1 -0
  130. package/dist/graph-facts/index.d.ts +10 -0
  131. package/dist/graph-facts/index.d.ts.map +1 -0
  132. package/dist/graph-facts/index.js +10 -0
  133. package/dist/graph-facts/index.js.map +1 -0
  134. package/dist/graph-facts/maven-packages.d.ts +42 -0
  135. package/dist/graph-facts/maven-packages.d.ts.map +1 -0
  136. package/dist/graph-facts/maven-packages.js +133 -0
  137. package/dist/graph-facts/maven-packages.js.map +1 -0
  138. package/dist/graph-facts/reach.d.ts +203 -0
  139. package/dist/graph-facts/reach.d.ts.map +1 -0
  140. package/dist/graph-facts/reach.js +219 -0
  141. package/dist/graph-facts/reach.js.map +1 -0
  142. package/dist/graph-facts/removal-reach.d.ts +73 -0
  143. package/dist/graph-facts/removal-reach.d.ts.map +1 -0
  144. package/dist/graph-facts/removal-reach.js +64 -0
  145. package/dist/graph-facts/removal-reach.js.map +1 -0
  146. package/dist/index.d.ts +21 -6
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +20 -3
  149. package/dist/index.js.map +1 -1
  150. package/dist/navigation/go-graph.d.ts +68 -0
  151. package/dist/navigation/go-graph.d.ts.map +1 -0
  152. package/dist/navigation/go-graph.js +753 -0
  153. package/dist/navigation/go-graph.js.map +1 -0
  154. package/dist/navigation/index.d.ts +7 -1
  155. package/dist/navigation/index.d.ts.map +1 -1
  156. package/dist/navigation/index.js +6 -0
  157. package/dist/navigation/index.js.map +1 -1
  158. package/dist/navigation/java-entry-tables.d.ts +43 -0
  159. package/dist/navigation/java-entry-tables.d.ts.map +1 -0
  160. package/dist/navigation/java-entry-tables.js +56 -0
  161. package/dist/navigation/java-entry-tables.js.map +1 -0
  162. package/dist/navigation/java-graph.d.ts +64 -0
  163. package/dist/navigation/java-graph.d.ts.map +1 -0
  164. package/dist/navigation/java-graph.js +466 -0
  165. package/dist/navigation/java-graph.js.map +1 -0
  166. package/dist/navigation/navigation-index.d.ts +91 -2
  167. package/dist/navigation/navigation-index.d.ts.map +1 -1
  168. package/dist/navigation/navigation-index.js +372 -45
  169. package/dist/navigation/navigation-index.js.map +1 -1
  170. package/dist/navigation/python-graph.d.ts +73 -0
  171. package/dist/navigation/python-graph.d.ts.map +1 -0
  172. package/dist/navigation/python-graph.js +867 -0
  173. package/dist/navigation/python-graph.js.map +1 -0
  174. package/dist/navigation/ts-call-edges.d.ts +193 -0
  175. package/dist/navigation/ts-call-edges.d.ts.map +1 -0
  176. package/dist/navigation/ts-call-edges.js +2089 -0
  177. package/dist/navigation/ts-call-edges.js.map +1 -0
  178. package/dist/navigation/ts-entry-tables.d.ts +88 -0
  179. package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
  180. package/dist/navigation/ts-entry-tables.js +50 -0
  181. package/dist/navigation/ts-entry-tables.js.map +1 -0
  182. package/dist/path-set/branches.d.ts +52 -0
  183. package/dist/path-set/branches.d.ts.map +1 -0
  184. package/dist/path-set/branches.js +124 -0
  185. package/dist/path-set/branches.js.map +1 -0
  186. package/dist/path-set/finding-diff.d.ts +63 -0
  187. package/dist/path-set/finding-diff.d.ts.map +1 -0
  188. package/dist/path-set/finding-diff.js +150 -0
  189. package/dist/path-set/finding-diff.js.map +1 -0
  190. package/dist/path-set/index.d.ts +18 -0
  191. package/dist/path-set/index.d.ts.map +1 -0
  192. package/dist/path-set/index.js +12 -0
  193. package/dist/path-set/index.js.map +1 -0
  194. package/dist/path-set/line-diff.d.ts +19 -0
  195. package/dist/path-set/line-diff.d.ts.map +1 -0
  196. package/dist/path-set/line-diff.js +98 -0
  197. package/dist/path-set/line-diff.js.map +1 -0
  198. package/dist/path-set/path-set.d.ts +88 -0
  199. package/dist/path-set/path-set.d.ts.map +1 -0
  200. package/dist/path-set/path-set.js +716 -0
  201. package/dist/path-set/path-set.js.map +1 -0
  202. package/dist/path-set/templates.d.ts +42 -0
  203. package/dist/path-set/templates.d.ts.map +1 -0
  204. package/dist/path-set/templates.js +119 -0
  205. package/dist/path-set/templates.js.map +1 -0
  206. package/dist/path-set/types.d.ts +195 -0
  207. package/dist/path-set/types.d.ts.map +1 -0
  208. package/dist/path-set/types.js +8 -0
  209. package/dist/path-set/types.js.map +1 -0
  210. package/dist/remedy-class/index.d.ts +12 -0
  211. package/dist/remedy-class/index.d.ts.map +1 -0
  212. package/dist/remedy-class/index.js +9 -0
  213. package/dist/remedy-class/index.js.map +1 -0
  214. package/dist/remedy-class/match.d.ts +165 -0
  215. package/dist/remedy-class/match.d.ts.map +1 -0
  216. package/dist/remedy-class/match.js +705 -0
  217. package/dist/remedy-class/match.js.map +1 -0
  218. package/dist/remedy-class/output-context.d.ts +25 -0
  219. package/dist/remedy-class/output-context.d.ts.map +1 -0
  220. package/dist/remedy-class/output-context.js +150 -0
  221. package/dist/remedy-class/output-context.js.map +1 -0
  222. package/dist/remedy-class/sha256.d.ts +18 -0
  223. package/dist/remedy-class/sha256.d.ts.map +1 -0
  224. package/dist/remedy-class/sha256.js +89 -0
  225. package/dist/remedy-class/sha256.js.map +1 -0
  226. package/dist/remedy-class/table.d.ts +130 -0
  227. package/dist/remedy-class/table.d.ts.map +1 -0
  228. package/dist/remedy-class/table.js +457 -0
  229. package/dist/remedy-class/table.js.map +1 -0
  230. package/dist/resolution/cross-file.d.ts +18 -0
  231. package/dist/resolution/cross-file.d.ts.map +1 -1
  232. package/dist/resolution/cross-file.js +194 -5
  233. package/dist/resolution/cross-file.js.map +1 -1
  234. package/dist/stable-ids/builder.d.ts +20 -0
  235. package/dist/stable-ids/builder.d.ts.map +1 -0
  236. package/dist/stable-ids/builder.js +65 -0
  237. package/dist/stable-ids/builder.js.map +1 -0
  238. package/dist/stable-ids/diff.d.ts +18 -0
  239. package/dist/stable-ids/diff.d.ts.map +1 -0
  240. package/dist/stable-ids/diff.js +37 -0
  241. package/dist/stable-ids/diff.js.map +1 -0
  242. package/dist/stable-ids/go.d.ts +28 -0
  243. package/dist/stable-ids/go.d.ts.map +1 -0
  244. package/dist/stable-ids/go.js +160 -0
  245. package/dist/stable-ids/go.js.map +1 -0
  246. package/dist/stable-ids/hash.d.ts +22 -0
  247. package/dist/stable-ids/hash.d.ts.map +1 -0
  248. package/dist/stable-ids/hash.js +167 -0
  249. package/dist/stable-ids/hash.js.map +1 -0
  250. package/dist/stable-ids/index.d.ts +30 -0
  251. package/dist/stable-ids/index.d.ts.map +1 -0
  252. package/dist/stable-ids/index.js +59 -0
  253. package/dist/stable-ids/index.js.map +1 -0
  254. package/dist/stable-ids/java.d.ts +28 -0
  255. package/dist/stable-ids/java.d.ts.map +1 -0
  256. package/dist/stable-ids/java.js +224 -0
  257. package/dist/stable-ids/java.js.map +1 -0
  258. package/dist/stable-ids/moves.d.ts +87 -0
  259. package/dist/stable-ids/moves.d.ts.map +1 -0
  260. package/dist/stable-ids/moves.js +222 -0
  261. package/dist/stable-ids/moves.js.map +1 -0
  262. package/dist/stable-ids/nodes.d.ts +32 -0
  263. package/dist/stable-ids/nodes.d.ts.map +1 -0
  264. package/dist/stable-ids/nodes.js +92 -0
  265. package/dist/stable-ids/nodes.js.map +1 -0
  266. package/dist/stable-ids/normalize.d.ts +35 -0
  267. package/dist/stable-ids/normalize.d.ts.map +1 -0
  268. package/dist/stable-ids/normalize.js +174 -0
  269. package/dist/stable-ids/normalize.js.map +1 -0
  270. package/dist/stable-ids/parse-errors.d.ts +24 -0
  271. package/dist/stable-ids/parse-errors.d.ts.map +1 -0
  272. package/dist/stable-ids/parse-errors.js +46 -0
  273. package/dist/stable-ids/parse-errors.js.map +1 -0
  274. package/dist/stable-ids/python.d.ts +27 -0
  275. package/dist/stable-ids/python.d.ts.map +1 -0
  276. package/dist/stable-ids/python.js +212 -0
  277. package/dist/stable-ids/python.js.map +1 -0
  278. package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
  279. package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
  280. package/dist/stable-ids/ts-parse-errors.js +75 -0
  281. package/dist/stable-ids/ts-parse-errors.js.map +1 -0
  282. package/dist/stable-ids/types.d.ts +106 -0
  283. package/dist/stable-ids/types.d.ts.map +1 -0
  284. package/dist/stable-ids/types.js +16 -0
  285. package/dist/stable-ids/types.js.map +1 -0
  286. package/dist/stable-ids/typescript.d.ts +30 -0
  287. package/dist/stable-ids/typescript.d.ts.map +1 -0
  288. package/dist/stable-ids/typescript.js +362 -0
  289. package/dist/stable-ids/typescript.js.map +1 -0
  290. package/dist/types/config.d.ts +9 -0
  291. package/dist/types/config.d.ts.map +1 -1
  292. package/dist/types/index.d.ts +53 -0
  293. package/dist/types/index.d.ts.map +1 -1
  294. package/dist/types/index.js.map +1 -1
  295. package/package.json +3 -3
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { findJavaSpringReturnXSSSinks } from './language-sources-pass.js';
14
14
  import { expressionFullyWrappedBySanitizer, identifierOutsideSanitizerCalls, identifierOutsideSanitizerCallsInText, propagateTaint } from '../taint-propagation.js';
15
- import { isFalsePositive, isCorrelatedPredicateFP } from '../constant-propagation.js';
15
+ import { isFalsePositive, isCorrelatedPredicateFP, constPropScopedVar } from '../constant-propagation.js';
16
16
  import { buildJavaTaintedVars, buildPythonTaintedVars, buildRustTaintedVars, findPythonTrustBoundaryViolations, findPythonReturnXSSSinks, findJavaWikiPluginReturnXSSSinks } from './language-sources-pass.js';
17
17
  import { parseJavaArrayAccessExpression } from '../java-array-element-keys.js';
18
18
  import { canSourceReachSink, sourceSemanticsAllowed } from '../findings.js';
@@ -20,6 +20,7 @@ import { walkBackwardDefs } from '../dfg-walk.js';
20
20
  import { sanitizerCoversSink } from '../sanitizer-index.js';
21
21
  import { HTML_CONTEXT_ENCODER_METHODS } from '../constant-propagation/patterns.js';
22
22
  import { javaCanonicalStartsWithAllBare } from '../java-canonical-prefix.js';
23
+ import { shouldGateInterproceduralParam } from '../entry-point-detection.js';
23
24
  /**
24
25
  * cognium-dev#328 — the const-prop per-step veto, with two scoped exemptions.
25
26
  *
@@ -385,6 +386,36 @@ function javaReceiverChainDerivedVar(rhsByLocal, localVar, seedVar) {
385
386
  * over every line for every (source, derived local) pair, which made large
386
387
  * files take seconds. Same per-line matching, done once.
387
388
  */
389
+ /** Line whose block scope owns `varName` as read on `useLine` (#476 / #597). */
390
+ function defLineForUse(graph, varName, useLine) {
391
+ const own = (graph.defsByLine.get(useLine) ?? []).find(d => d.variable === varName);
392
+ if (own)
393
+ return own.line;
394
+ const use = (graph.usesByLine.get(useLine) ?? []).find(u => u.variable === varName && u.def_id != null);
395
+ const d = use ? graph.defById.get(use.def_id) : undefined;
396
+ return d?.line ?? useLine;
397
+ }
398
+ /** Const-prop key for a flow step: block locals vs method-body locals (#476 / #597). */
399
+ function constPropFlowLookup(language, method, varName, blockLine, constProp, preferMethodScoped = false) {
400
+ if (language === 'go') {
401
+ return constPropScopedVar(method, varName, blockLine, constProp);
402
+ }
403
+ if (!method || varName.includes(':'))
404
+ return varName;
405
+ const blockScoped = constPropScopedVar(method, varName, blockLine, constProp);
406
+ const hasTaintBinding = (name) => constProp.tainted.has(name) || constProp.sanitizedVars.has(name);
407
+ if (blockScoped.includes(':b') && hasTaintBinding(blockScoped)) {
408
+ return blockScoped;
409
+ }
410
+ const methodScoped = `${method}:${varName}`;
411
+ if (hasTaintBinding(methodScoped)) {
412
+ return methodScoped;
413
+ }
414
+ if (preferMethodScoped && method) {
415
+ return methodScoped;
416
+ }
417
+ return constProp.lineBlockScopes?.has(blockLine) ? blockScoped : varName;
418
+ }
388
419
  function indexJavaLocalAssignments(code) {
389
420
  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*$/;
390
421
  const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
@@ -408,6 +439,40 @@ function indexJavaLocalAssignments(code) {
408
439
  }
409
440
  return index;
410
441
  }
442
+ function goCalleeBaseName(callee) {
443
+ const dot = callee.lastIndexOf('.');
444
+ return dot === -1 ? callee : callee.slice(dot + 1);
445
+ }
446
+ /** cognium-dev#783: callee from a Go `:=` or `var x =` assignment line (unqualified or dotted). */
447
+ function goConstantHelperCalleeOnLine(line) {
448
+ const m = line.match(/:=\s*([\w.]+)\s*\(/) ?? line.match(/\bvar\s+\w+\s*=\s*([\w.]+)\s*\(/);
449
+ if (!m)
450
+ return null;
451
+ return goCalleeBaseName(m[1]);
452
+ }
453
+ /** Same as {@link goConstantHelperCalleeOnLine} when the call spans the next lines after `:=` / `var`. */
454
+ function goConstantHelperCalleeFromLines(lines, lineNumber) {
455
+ const direct = goConstantHelperCalleeOnLine(lines[lineNumber - 1] ?? '');
456
+ if (direct)
457
+ return direct;
458
+ const cur = lines[lineNumber - 1] ?? '';
459
+ const partial = cur.match(/:=\s*([\w.]+)\s*$/) ?? cur.match(/\bvar\s+\w+\s*=\s*([\w.]+)\s*$/);
460
+ if (partial) {
461
+ const next = (lines[lineNumber] ?? '').trimStart();
462
+ if (next.startsWith('('))
463
+ return goCalleeBaseName(partial[1]);
464
+ }
465
+ if (!cur.includes(':=') && !/\bvar\s+\w+\s*=/.test(cur))
466
+ return null;
467
+ if (cur.includes('('))
468
+ return null;
469
+ for (let i = lineNumber + 1; i <= Math.min(lines.length, lineNumber + 4); i++) {
470
+ const m = (lines[i - 1] ?? '').match(/^\s*([\w.]+)\s*\(/);
471
+ if (m)
472
+ return goCalleeBaseName(m[1]);
473
+ }
474
+ return null;
475
+ }
411
476
  function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
412
477
  const lines = typeof code === 'string' ? code.split('\n') : [];
413
478
  const selfReassign = new Map();
@@ -442,15 +507,26 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
442
507
  };
443
508
  return (step, sourceLine, sinkLine, sinkType) => {
444
509
  const method = methodAtLine(graph, step.line);
445
- const scopedVar = method ? `${method}:${step.variable}` : step.variable;
446
- // cognium-dev#751: Go locals are recorded method-scoped only (no unscoped
447
- // alias) by handleGoAssignment, so Go looks up the scoped name. Other
448
- // languages keep the unscoped lookup (#614).
449
- const r = isFalsePositive(constProp, step.line, language === 'go' ? scopedVar : step.variable);
510
+ const blockLine = defLineForUse(graph, step.variable, step.line);
511
+ const fpLookup = constPropFlowLookup(language, method, step.variable, blockLine, constProp);
512
+ const r = isFalsePositive(constProp, step.line, fpLookup);
450
513
  if (!r.isFalsePositive)
451
514
  return false;
452
515
  if (r.reason !== 'variable_not_tainted')
453
516
  return true;
517
+ if (r.reason === 'variable_not_tainted' &&
518
+ language === 'java' &&
519
+ method &&
520
+ fpLookup === step.variable &&
521
+ !step.variable.includes('.')) {
522
+ const v = step.variable;
523
+ if (constProp.tainted.has(`${method}:${v}`))
524
+ return false;
525
+ for (const t of constProp.tainted) {
526
+ if (t.startsWith(`${method}:b`) && t.endsWith(`:${v}`))
527
+ return false;
528
+ }
529
+ }
454
530
  // #589: the step variable came from a context-specific encoder
455
531
  // (`escapeHtml`, `encodeForHTML`, …), which does not clean `trust_boundary`
456
532
  // (a `session.setAttribute` sink). It is "not tainted" only for the other
@@ -466,8 +542,9 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
466
542
  else {
467
543
  const method = methodAtLine(graph, step.line);
468
544
  if (method) {
469
- if (unc.get(`${method}:${step.variable}`)?.has(sinkType))
545
+ if (unc.get(fpLookup)?.has(sinkType)) {
470
546
  return false;
547
+ }
471
548
  }
472
549
  else if (unc.get(step.variable)?.has(sinkType)) {
473
550
  return false;
@@ -543,10 +620,11 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
543
620
  // cognium-dev#751: sink use after `uid := constantHelper(tainted)` must veto
544
621
  // when the helper is in `constantReturningMethods`, even if the flow's
545
622
  // source_line is the assignment line (rule E would otherwise keep it).
546
- if (step.line === sinkLine && !constProp.tainted.has(scopedVar)) {
623
+ if (step.line === sinkLine &&
624
+ !constProp.tainted.has(fpLookup)) {
547
625
  for (let ln = sourceLine; ln <= sinkLine; ln++) {
548
- const calleeMatch = (lines[ln - 1] ?? '').match(/:=\s*(\w+)\s*\(/);
549
- if (calleeMatch && constProp.constantReturningMethods?.has(calleeMatch[1])) {
626
+ const callee = goConstantHelperCalleeFromLines(lines, ln);
627
+ if (callee && constProp.constantReturningMethods?.has(callee)) {
550
628
  return true;
551
629
  }
552
630
  }
@@ -841,7 +919,7 @@ export class TaintPropagationPass {
841
919
  pushIfNew(f);
842
920
  }
843
921
  // Supplement: collection/iterator flows — with FP filtering
844
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
922
+ const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp, ctx.code, types, graph) ?? [];
845
923
  for (const f of collectionFlows) {
846
924
  if (flowKeys.has(flowKey(f)))
847
925
  continue;
@@ -865,7 +943,7 @@ export class TaintPropagationPass {
865
943
  }
866
944
  // Supplement: direct parameter-to-sink flows
867
945
  // Sprint 82 (#189) — sink-type-aware dedup (see arrayFlows comment).
868
- const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
946
+ const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language, graph.ir.meta.file) ?? [];
869
947
  for (const f of paramFlows) {
870
948
  pushIfNew(f);
871
949
  }
@@ -923,6 +1001,15 @@ export class TaintPropagationPass {
923
1001
  pushIfNew(f);
924
1002
  }
925
1003
  }
1004
+ // cognium-dev#686 — JS/TS module-scope `let`/`var` store-then-read across
1005
+ // route handlers (variant xss stored). Mirrors #243 Go package globals.
1006
+ if ((ctx.language === 'javascript' || ctx.language === 'typescript') &&
1007
+ typeof ctx.code === 'string') {
1008
+ const jsModuleFlows = detectJsModuleScopeFlows(ctx.code, sources, sinks, constProp.unreachableLines) ?? [];
1009
+ for (const f of jsModuleFlows) {
1010
+ pushIfNew(f);
1011
+ }
1012
+ }
926
1013
  // Sprint 9 #58.1 — final pass: drop any flow whose source variable was
927
1014
  // explicitly marked sanitized by a guard (e.g. regex-allowlist).
928
1015
  // Applied to ALL flow generators (DFG-built and the four supplements)
@@ -970,6 +1057,14 @@ export class TaintPropagationPass {
970
1057
  return false;
971
1058
  return true;
972
1059
  });
1060
+ if (ctx.language === 'csharp') {
1061
+ finalFlows = finalFlows.filter((f) => !shouldGateCSharpPatPermsLibraryParamFlow(f, sources, sinks, types, {
1062
+ language: ctx.language,
1063
+ types,
1064
+ filePath: graph.ir.meta.file,
1065
+ calls,
1066
+ }));
1067
+ }
973
1068
  // Sprint 24 (cognium-dev #102) — uniform line-keyed sanitizer
974
1069
  // suppression. Drop any flow whose sink_line carries a registered
975
1070
  // sanitizer that covers the flow's sink_type. Several supplementary
@@ -1647,6 +1742,102 @@ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLin
1647
1742
  }
1648
1743
  return flows;
1649
1744
  }
1745
+ /**
1746
+ * cognium-dev#686 — module-level `let`/`var` written in one handler and read
1747
+ * in another. Per-function DFG cannot link these; text-scan supplement.
1748
+ */
1749
+ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
1750
+ const flows = [];
1751
+ if (!code || sinks.length === 0)
1752
+ return flows;
1753
+ const lines = code.split('\n');
1754
+ const moduleVars = new Set();
1755
+ for (const line of lines) {
1756
+ const stripped = line.replace(/\s+$/, '');
1757
+ if (!stripped || /^[ \t]/.test(line))
1758
+ continue;
1759
+ const m = stripped.match(/^(?:export\s+)?(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
1760
+ if (m)
1761
+ moduleVars.add(m[1]);
1762
+ }
1763
+ if (moduleVars.size === 0)
1764
+ return flows;
1765
+ const sourceVarNames = new Set();
1766
+ for (const s of sources) {
1767
+ if (typeof s.variable === 'string' && s.variable.length > 0) {
1768
+ sourceVarNames.add(s.variable);
1769
+ }
1770
+ }
1771
+ const JS_HTTP_SOURCE_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies|get)\b|\breq\.(?:param|query|body)\(/;
1772
+ const writes = [];
1773
+ for (let i = 0; i < lines.length; i++) {
1774
+ const line = lines[i];
1775
+ if (unreachableLines.has(i + 1))
1776
+ continue;
1777
+ for (const lhs of moduleVars) {
1778
+ const declRe = new RegExp(`\\b(?:export\\s+)?(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
1779
+ if (declRe.test(line))
1780
+ continue;
1781
+ const m = line.match(new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=\\s*([^;]+)`));
1782
+ if (!m)
1783
+ continue;
1784
+ const rhs = m[1];
1785
+ let taintedBy = null;
1786
+ const onLine = sources.filter(s => s.line === i + 1);
1787
+ if (onLine.length > 0) {
1788
+ taintedBy = { line: onLine[0].line, type: onLine[0].type };
1789
+ }
1790
+ else if (JS_HTTP_SOURCE_RE.test(rhs)) {
1791
+ taintedBy = { line: i + 1, type: 'http_body' };
1792
+ }
1793
+ else {
1794
+ for (const v of sourceVarNames) {
1795
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9_$])`);
1796
+ if (re.test(rhs)) {
1797
+ const src = sources.find(s => s.variable === v);
1798
+ if (src) {
1799
+ taintedBy = { line: src.line, type: src.type };
1800
+ break;
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+ if (!taintedBy)
1806
+ continue;
1807
+ writes.push({ varName: lhs, line: i + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
1808
+ }
1809
+ }
1810
+ if (writes.length === 0)
1811
+ return flows;
1812
+ for (const sink of sinks) {
1813
+ if (unreachableLines.has(sink.line))
1814
+ continue;
1815
+ const sinkLineText = lines[sink.line - 1] ?? '';
1816
+ if (!sinkLineText)
1817
+ continue;
1818
+ for (const w of writes) {
1819
+ if (w.line >= sink.line)
1820
+ continue;
1821
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9_$])`);
1822
+ if (!re.test(sinkLineText))
1823
+ continue;
1824
+ flows.push({
1825
+ source_line: w.sourceLine,
1826
+ sink_line: sink.line,
1827
+ source_type: w.sourceType,
1828
+ sink_type: sink.type,
1829
+ path: [
1830
+ { variable: w.varName, line: w.sourceLine, type: 'source' },
1831
+ { variable: w.varName, line: w.line, type: 'assignment' },
1832
+ { variable: w.varName, line: sink.line, type: 'sink' },
1833
+ ],
1834
+ confidence: 0.9,
1835
+ sanitized: false,
1836
+ });
1837
+ }
1838
+ }
1839
+ return flows;
1840
+ }
1650
1841
  /**
1651
1842
  * #614 — is `varName` tainted in the sink's own method?
1652
1843
  *
@@ -1753,7 +1944,21 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
1753
1944
  }
1754
1945
  return true;
1755
1946
  }
1756
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
1947
+ function mapGetLiteralKeyFromExpr(expr) {
1948
+ const m = expr.match(/\b([A-Za-z_]\w*)\s*\.\s*get\s*\(\s*"([^"]*)"\s*\)/);
1949
+ return m ? { map: m[1], key: m[2] } : null;
1950
+ }
1951
+ function isMapLiteralKeyTainted(constProp, mapName, key, inMethod) {
1952
+ const scoped = inMethod ? `${inMethod}:${mapName}` : mapName;
1953
+ const keys = constProp.taintedCollections.get(mapName) ??
1954
+ constProp.taintedCollections.get(scoped);
1955
+ if (!keys || keys.size === 0)
1956
+ return false;
1957
+ return keys.has(key) || keys.has('*');
1958
+ }
1959
+ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
1960
+ const taintedVars = constProp.tainted;
1961
+ const unreachableLines = constProp.unreachableLines;
1757
1962
  const flows = [];
1758
1963
  const fieldNames = new Set();
1759
1964
  for (const t of types ?? [])
@@ -1778,7 +1983,13 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
1778
1983
  }
1779
1984
  if (arg.variable) {
1780
1985
  const varName = arg.variable;
1781
- const scopedName = call.in_method ? `${call.in_method}:${varName}` : varName;
1986
+ let scopedName = varName;
1987
+ if (call.in_method) {
1988
+ const blockLine = graph
1989
+ ? defLineForUse(graph, varName, call.location.line)
1990
+ : call.location.line;
1991
+ scopedName = constPropFlowLookup('java', call.in_method, varName, blockLine, constProp, true);
1992
+ }
1782
1993
  // Bare `name` that is a field read (no local of that name in this
1783
1994
  // method) still counts as a field use — Inter4-style. A shadowing
1784
1995
  // local declaration in the method does not.
@@ -1788,7 +1999,12 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
1788
1999
  && expr === varName
1789
2000
  && !methodHasLocalDecl(code, call.in_method, varName, call.location.line);
1790
2001
  const isFieldUse = explicitField || bareFieldRead;
1791
- if (isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isFieldUse)) {
2002
+ const mapKeyRead = mapGetLiteralKeyFromExpr(expr);
2003
+ let argTainted = isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isFieldUse);
2004
+ if (mapKeyRead && mapKeyRead.map === varName) {
2005
+ argTainted = isMapLiteralKeyTainted(constProp, varName, mapKeyRead.key, call.in_method);
2006
+ }
2007
+ if (argTainted) {
1792
2008
  // Sprint 13 #70 — pick a source in the same method scope as the
1793
2009
  // sink, not blanket sources[0]. The varName-based match is
1794
2010
  // primary; method-scope is the tiebreaker; sources[0] is the
@@ -1843,12 +2059,19 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
1843
2059
  { method: 'peek', re: /(\w+)\.peek\(/ },
1844
2060
  { method: 'toArray', re: /(\w+)\.toArray\(/ },
1845
2061
  ];
1846
- for (const { re } of collectionPatterns) {
2062
+ for (const { method, re } of collectionPatterns) {
1847
2063
  const match = expr.match(re);
1848
2064
  if (match) {
1849
2065
  const collectionVar = match[1];
1850
2066
  const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
1851
- if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
2067
+ const mapKey = method === 'get' ? mapGetLiteralKeyFromExpr(expr) : null;
2068
+ let collectionTainted = taintedVars.has(collectionVar) || taintedVars.has(scopedCollection);
2069
+ if (mapKey && mapKey.map === collectionVar) {
2070
+ collectionTainted = isMapLiteralKeyTainted(constProp, collectionVar, mapKey.key, call.in_method);
2071
+ }
2072
+ if (!collectionTainted)
2073
+ continue;
2074
+ {
1852
2075
  // Sprint 13 #70 — same method-scoped picker as above.
1853
2076
  const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
1854
2077
  if (source) {
@@ -1934,6 +2157,40 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
1934
2157
  }
1935
2158
  return flows;
1936
2159
  }
2160
+ /**
2161
+ * cognium-dev #816 — PAT-PERMS-01: suppress only direct library-parameter →
2162
+ * FileStream path_traversal on non-entry methods. Derived paths (Path.Combine,
2163
+ * GetFullPath chains) and other sinks stay gated only by sanitizer / DFG logic.
2164
+ */
2165
+ function csharpPatPermsDirectParamSink(sink, paramVar) {
2166
+ const method = (sink.method ?? '').replace(/^.*\./, '');
2167
+ if (method !== 'FileStream')
2168
+ return false;
2169
+ const code = sink.code ?? '';
2170
+ if (!code)
2171
+ return true;
2172
+ const escaped = paramVar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2173
+ return new RegExp(`\\bFileStream\\s*\\(\\s*${escaped}\\b`).test(code);
2174
+ }
2175
+ function shouldGateCSharpPatPermsLibraryParamFlow(flow, sources, sinks, types, ctx) {
2176
+ if (flow.source_type !== 'interprocedural_param' || flow.sink_type !== 'path_traversal') {
2177
+ return false;
2178
+ }
2179
+ const srcVar = flow.path?.[0]?.variable;
2180
+ const sinkVar = flow.path?.[flow.path.length - 1]?.variable;
2181
+ if (!srcVar || srcVar !== sinkVar)
2182
+ return false;
2183
+ const sink = sinks.find((s) => s.line === flow.sink_line && s.type === 'path_traversal');
2184
+ if (!sink || !csharpPatPermsDirectParamSink(sink, srcVar))
2185
+ return false;
2186
+ const src = sources.find((s) => s.line === flow.source_line && s.type === 'interprocedural_param');
2187
+ const methodName = src?.in_method;
2188
+ if (!methodName)
2189
+ return false;
2190
+ const enclosingType = types.find((t) => t.methods.some((m) => m.name === methodName));
2191
+ const enclosingMethod = enclosingType?.methods.find((m) => m.name === methodName);
2192
+ return shouldGateInterproceduralParam(flow.source_type, enclosingMethod, enclosingType, ctx);
2193
+ }
1937
2194
  /** Parameter names referenced as identifiers inside a compound sink argument (#685). */
1938
2195
  function compoundArgParamNames(expression, paramNames) {
1939
2196
  const stripped = expression.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, (m) => ' '.repeat(m.length));
@@ -1948,7 +2205,7 @@ function compoundArgParamNames(expression, paramNames) {
1948
2205
  }
1949
2206
  return matched;
1950
2207
  }
1951
- function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
2208
+ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code, language, filePath) {
1952
2209
  const flows = [];
1953
2210
  const paramSourcesByMethod = new Map();
1954
2211
  for (const source of sources) {
@@ -1998,6 +2255,16 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
1998
2255
  for (const varName of varNames) {
1999
2256
  const paramSource = methodParamSources.get(varName);
2000
2257
  if (paramSource) {
2258
+ if (language === 'csharp' &&
2259
+ paramSource.type === 'interprocedural_param' &&
2260
+ sink.type === 'path_traversal' &&
2261
+ csharpPatPermsDirectParamSink(sink, varName)) {
2262
+ const enclosingType = types.find((t) => t.methods.some((m) => m.name === methodName));
2263
+ const enclosingMethod = enclosingType?.methods.find((m) => m.name === methodName);
2264
+ if (shouldGateInterproceduralParam(paramSource.type, enclosingMethod, enclosingType, { language, types, filePath, calls })) {
2265
+ continue;
2266
+ }
2267
+ }
2001
2268
  // Sprint 82 (#189) — sink-type-aware dedup so a sink registered
2002
2269
  // under multiple types at the same call site (e.g. `redirect` as
2003
2270
  // both `open_redirect` and `crlf`) emits a flow for each type.
@@ -3504,10 +3771,22 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
3504
3771
  realSourceByVar.set(s.variable, s);
3505
3772
  }
3506
3773
  for (const [varName, derivedLine] of derived) {
3507
- if (!varName || existingVars.has(varName))
3774
+ if (!varName)
3508
3775
  continue;
3509
3776
  const owner = methodAt(derivedLine);
3510
3777
  const fieldAlias = isDeclaredField(derivedLine, varName);
3778
+ if (existingVars.has(varName)) {
3779
+ if (!fieldAlias)
3780
+ continue;
3781
+ // cognium-dev#626: a bean setter's `interprocedural_param` stub must not
3782
+ // block the declared-field alias derived from a tainted `setFoo(...)` /
3783
+ // `this.foo =` write — otherwise sinks in other methods never see `foo`.
3784
+ const stubIdx = sourcesWithVar.findIndex(s => s.variable === varName && s.type === 'interprocedural_param');
3785
+ if (stubIdx < 0)
3786
+ continue;
3787
+ sourcesWithVar.splice(stubIdx, 1);
3788
+ existingVars.delete(varName);
3789
+ }
3511
3790
  let scopedAnchor = anchor;
3512
3791
  if (owner) {
3513
3792
  let best;