circle-ir 4.14.1 → 4.15.0

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 (95) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +9 -2
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/const-table-lookup.d.ts +29 -0
  5. package/dist/analysis/const-table-lookup.d.ts.map +1 -0
  6. package/dist/analysis/const-table-lookup.js +145 -0
  7. package/dist/analysis/const-table-lookup.js.map +1 -0
  8. package/dist/analysis/findings.d.ts.map +1 -1
  9. package/dist/analysis/findings.js +15 -4
  10. package/dist/analysis/findings.js.map +1 -1
  11. package/dist/analysis/go-strings-replacer.d.ts +21 -0
  12. package/dist/analysis/go-strings-replacer.d.ts.map +1 -0
  13. package/dist/analysis/go-strings-replacer.js +145 -0
  14. package/dist/analysis/go-strings-replacer.js.map +1 -0
  15. package/dist/analysis/interprocedural.d.ts.map +1 -1
  16. package/dist/analysis/interprocedural.js +53 -0
  17. package/dist/analysis/interprocedural.js.map +1 -1
  18. package/dist/analysis/java-canonical-prefix.d.ts +42 -0
  19. package/dist/analysis/java-canonical-prefix.d.ts.map +1 -0
  20. package/dist/analysis/java-canonical-prefix.js +145 -0
  21. package/dist/analysis/java-canonical-prefix.js.map +1 -0
  22. package/dist/analysis/java-crlf-strip.d.ts +30 -0
  23. package/dist/analysis/java-crlf-strip.d.ts.map +1 -0
  24. package/dist/analysis/java-crlf-strip.js +195 -0
  25. package/dist/analysis/java-crlf-strip.js.map +1 -0
  26. package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +2 -0
  27. package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -1
  28. package/dist/analysis/passes/cleartext-credential-transport-pass.js +64 -12
  29. package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -1
  30. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  31. package/dist/analysis/passes/cross-file-pass.js +43 -0
  32. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  33. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  34. package/dist/analysis/passes/csharp-sanitizer-credit.js +18 -0
  35. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  36. package/dist/analysis/passes/insecure-cookie-pass.d.ts +11 -4
  37. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  38. package/dist/analysis/passes/insecure-cookie-pass.js +30 -15
  39. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  40. package/dist/analysis/passes/interprocedural-pass.d.ts +28 -0
  41. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  42. package/dist/analysis/passes/interprocedural-pass.js +31 -3
  43. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  44. package/dist/analysis/passes/language-sources-pass.d.ts +41 -2
  45. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  46. package/dist/analysis/passes/language-sources-pass.js +894 -10
  47. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  48. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts +8 -0
  49. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  50. package/dist/analysis/passes/param-source-flow-gate-pass.js +162 -6
  51. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  52. package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
  53. package/dist/analysis/passes/scan-secrets-pass.js +24 -3
  54. package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
  55. package/dist/analysis/passes/sink-filter-pass.d.ts +1 -1
  56. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  57. package/dist/analysis/passes/sink-filter-pass.js +255 -25
  58. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  59. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  60. package/dist/analysis/passes/taint-propagation-pass.js +268 -5
  61. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  62. package/dist/analysis/scalar-derivation.d.ts +25 -0
  63. package/dist/analysis/scalar-derivation.d.ts.map +1 -0
  64. package/dist/analysis/scalar-derivation.js +196 -0
  65. package/dist/analysis/scalar-derivation.js.map +1 -0
  66. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  67. package/dist/analysis/taint-matcher.js +172 -8
  68. package/dist/analysis/taint-matcher.js.map +1 -1
  69. package/dist/analysis/taint-propagation.d.ts +9 -0
  70. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  71. package/dist/analysis/taint-propagation.js +260 -11
  72. package/dist/analysis/taint-propagation.js.map +1 -1
  73. package/dist/analyzer.d.ts.map +1 -1
  74. package/dist/analyzer.js +8 -1
  75. package/dist/analyzer.js.map +1 -1
  76. package/dist/browser/circle-ir.js +3403 -1225
  77. package/dist/core/circle-ir-core.cjs +598 -26
  78. package/dist/core/circle-ir-core.js +598 -26
  79. package/dist/core/extractors/calls.js +19 -0
  80. package/dist/core/extractors/calls.js.map +1 -1
  81. package/dist/core/extractors/dfg.js +240 -6
  82. package/dist/core/extractors/dfg.js.map +1 -1
  83. package/dist/graph/code-graph.d.ts +9 -0
  84. package/dist/graph/code-graph.d.ts.map +1 -1
  85. package/dist/graph/code-graph.js +11 -0
  86. package/dist/graph/code-graph.js.map +1 -1
  87. package/dist/languages/plugins/go.d.ts.map +1 -1
  88. package/dist/languages/plugins/go.js +19 -0
  89. package/dist/languages/plugins/go.js.map +1 -1
  90. package/dist/resolution/cross-file.d.ts +7 -0
  91. package/dist/resolution/cross-file.d.ts.map +1 -1
  92. package/dist/resolution/cross-file.js +10 -1
  93. package/dist/resolution/cross-file.js.map +1 -1
  94. package/dist/wasm/web-tree-sitter.wasm +0 -0
  95. package/package.json +3 -3
@@ -8080,6 +8080,17 @@ function extractGoCallInfo(node) {
8080
8080
  if (operand.type === "identifier") {
8081
8081
  const resolved = resolveGoLocalReceiverType(opText, node);
8082
8082
  receiver = resolved !== null ? resolved : opText;
8083
+ } else if (operand.type === "selector_expression" && methodName === "Get") {
8084
+ const fieldNode = operand.childForFieldName("field");
8085
+ const inner = operand.childForFieldName("operand");
8086
+ const fieldName = fieldNode ? getNodeText(fieldNode) : "";
8087
+ const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
8088
+ const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
8089
+ if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
8090
+ receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
8091
+ } else {
8092
+ receiver = opText;
8093
+ }
8083
8094
  } else {
8084
8095
  receiver = opText;
8085
8096
  }
@@ -9896,6 +9907,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
9896
9907
  };
9897
9908
  defs.push(def);
9898
9909
  currentScope(scopeStack).set(name2, def.id);
9910
+ markDeclared(currentScope(scopeStack), name2);
9899
9911
  } else if (isJavaScript) {
9900
9912
  const destructDefs = extractDestructuringDefs(nameNode, defId);
9901
9913
  for (const def of destructDefs) {
@@ -9942,21 +9954,31 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
9942
9954
  useId = condUses.nextId;
9943
9955
  }
9944
9956
  const consequence = stmt.childForFieldName("consequence");
9957
+ const alternative = stmt.childForFieldName("alternative");
9958
+ const topAlts = alternateDefs.get(currentScope(scopeStack));
9959
+ const before = /* @__PURE__ */ new Map();
9960
+ if (topAlts) for (const [name2, ids] of topAlts) before.set(name2, [...ids]);
9961
+ let armA = null;
9962
+ let armB = null;
9945
9963
  if (consequence) {
9946
- const result = processStatement2(consequence, defId, useId, scopeStack, isJavaScript);
9964
+ const result = processArm(consequence, defId, useId, scopeStack, isJavaScript);
9965
+ armA = result.popped;
9947
9966
  defs.push(...result.defs);
9948
9967
  uses.push(...result.uses);
9949
9968
  defId = result.nextDefId;
9950
9969
  useId = result.nextUseId;
9951
9970
  }
9952
- const alternative = stmt.childForFieldName("alternative");
9953
9971
  if (alternative) {
9954
- const result = processStatement2(alternative, defId, useId, scopeStack, isJavaScript);
9972
+ const result = processArm(alternative, defId, useId, scopeStack, isJavaScript);
9973
+ armB = result.popped;
9955
9974
  defs.push(...result.defs);
9956
9975
  uses.push(...result.uses);
9957
9976
  defId = result.nextDefId;
9958
9977
  useId = result.nextUseId;
9959
9978
  }
9979
+ if (armA && armB) {
9980
+ mergeIfElseDefs(armA, armB, before, scopeStack);
9981
+ }
9960
9982
  break;
9961
9983
  }
9962
9984
  case "for_statement":
@@ -10027,7 +10049,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10027
10049
  }
10028
10050
  const body2 = stmt.childForFieldName("body");
10029
10051
  if (body2) {
10030
- const result = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
10052
+ const result = processArm(body2, defId, useId, scopeStack, isJavaScript);
10031
10053
  defs.push(...result.defs);
10032
10054
  uses.push(...result.uses);
10033
10055
  defId = result.nextDefId;
@@ -10055,7 +10077,8 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10055
10077
  defId = result.nextDefId;
10056
10078
  useId = result.nextUseId;
10057
10079
  }
10058
- scopeStack.pop();
10080
+ const poppedSwitch = scopeStack.pop();
10081
+ if (!isJavaScript) carryBranchDefsOut(poppedSwitch, scopeStack, false, true);
10059
10082
  }
10060
10083
  break;
10061
10084
  }
@@ -10067,7 +10090,10 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10067
10090
  uses.push(...result.uses);
10068
10091
  defId = result.nextDefId;
10069
10092
  useId = result.nextUseId;
10070
- scopeStack.pop();
10093
+ const popped = scopeStack.pop();
10094
+ const alwaysRuns = stmt.parent?.type === "block" || stmt.parent?.type === "synchronized_statement";
10095
+ lastPoppedBlock = popped;
10096
+ if (!isJavaScript) carryBranchDefsOut(popped, scopeStack, alwaysRuns);
10071
10097
  break;
10072
10098
  }
10073
10099
  default: {
@@ -10109,6 +10135,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
10109
10135
  };
10110
10136
  defs.push(def);
10111
10137
  currentScope(scopeStack).set(name2, def.id);
10138
+ alternateDefs.get(currentScope(scopeStack))?.delete(name2);
10112
10139
  } else if (left) {
10113
10140
  const leftUses = extractUses(left, useId, scopeStack, isJavaScript);
10114
10141
  uses.push(...leftUses.uses);
@@ -10192,6 +10219,10 @@ function extractUses(node, startId, scopeStack, isJavaScript) {
10192
10219
  line: n.startPosition.row + 1,
10193
10220
  def_id: reachingDef
10194
10221
  });
10222
+ for (const alt of findAlternateDefs(name2, scopeStack)) {
10223
+ if (alt === reachingDef) continue;
10224
+ uses.push({ id: id++, variable: name2, line: n.startPosition.row + 1, def_id: alt });
10225
+ }
10195
10226
  }
10196
10227
  });
10197
10228
  return { uses, nextId: id };
@@ -10444,6 +10475,134 @@ function extractDestructuringDefs(node, startId) {
10444
10475
  function currentScope(scopeStack) {
10445
10476
  return scopeStack[scopeStack.length - 1];
10446
10477
  }
10478
+ var lastPoppedBlock = null;
10479
+ function inAlwaysRunBlock(arm) {
10480
+ let n = arm.parent;
10481
+ while (n && (n.type === "if_statement" || n.type === "for_statement" || n.type === "enhanced_for_statement" || n.type === "while_statement" || n.type === "do_statement")) n = n.parent;
10482
+ return !!n && n.type === "block" && !!n.parent && (n.parent.type === "block" || n.parent.type === "synchronized_statement");
10483
+ }
10484
+ function inNestedBlock(arm) {
10485
+ let n = arm.parent;
10486
+ while (n && (n.type === "if_statement" || n.type === "for_statement" || n.type === "enhanced_for_statement" || n.type === "while_statement" || n.type === "do_statement")) n = n.parent;
10487
+ if (!n || n.type !== "block" || !n.parent) return false;
10488
+ switch (n.parent.type) {
10489
+ case "block":
10490
+ case "synchronized_statement":
10491
+ case "if_statement":
10492
+ case "for_statement":
10493
+ case "enhanced_for_statement":
10494
+ case "while_statement":
10495
+ case "do_statement":
10496
+ case "try_statement":
10497
+ case "try_with_resources_statement":
10498
+ case "catch_clause":
10499
+ case "finally_clause":
10500
+ case "labeled_statement":
10501
+ return true;
10502
+ default:
10503
+ return false;
10504
+ }
10505
+ }
10506
+ function processArm(node, defId, useId, scopeStack, isJavaScript) {
10507
+ const isBlock = node.type === "block" || node.type === "statement_block";
10508
+ if (isJavaScript || (isBlock ? !inAlwaysRunBlock(node) : !inNestedBlock(node))) {
10509
+ lastPoppedBlock = null;
10510
+ const result2 = processStatement2(node, defId, useId, scopeStack, isJavaScript);
10511
+ return { ...result2, popped: null };
10512
+ }
10513
+ if (isBlock) {
10514
+ lastPoppedBlock = null;
10515
+ const result2 = processStatement2(node, defId, useId, scopeStack, isJavaScript);
10516
+ return { ...result2, popped: lastPoppedBlock };
10517
+ }
10518
+ scopeStack.push(/* @__PURE__ */ new Map());
10519
+ const result = processStatement2(node, defId, useId, scopeStack, isJavaScript);
10520
+ const popped = scopeStack.pop();
10521
+ carryBranchDefsOut(popped, scopeStack, false);
10522
+ return { ...result, popped };
10523
+ }
10524
+ function mergeIfElseDefs(armA, armB, before, scopeStack) {
10525
+ const top = scopeStack[scopeStack.length - 1];
10526
+ if (!top) return;
10527
+ const alts = alternateDefs.get(top);
10528
+ for (const [name2, idA] of armA) {
10529
+ const idB = armB.get(name2);
10530
+ if (idB === void 0) continue;
10531
+ if (declaredNames.get(armA)?.has(name2) || declaredNames.get(armB)?.has(name2)) continue;
10532
+ if (!scopeStack.some((scope) => scope.has(name2))) continue;
10533
+ const prior = new Set(before.get(name2) ?? []);
10534
+ const arms = (alts?.get(name2) ?? []).filter((id) => !prior.has(id) && id !== idB);
10535
+ if (!arms.includes(idA)) arms.push(idA);
10536
+ top.set(name2, idB);
10537
+ let map = alts;
10538
+ if (!map) {
10539
+ map = /* @__PURE__ */ new Map();
10540
+ alternateDefs.set(top, map);
10541
+ }
10542
+ map.set(name2, arms);
10543
+ }
10544
+ }
10545
+ var declaredNames = /* @__PURE__ */ new WeakMap();
10546
+ var alternateDefs = /* @__PURE__ */ new WeakMap();
10547
+ function markDeclared(scope, name2) {
10548
+ let set = declaredNames.get(scope);
10549
+ if (!set) {
10550
+ set = /* @__PURE__ */ new Set();
10551
+ declaredNames.set(scope, set);
10552
+ }
10553
+ set.add(name2);
10554
+ alternateDefs.get(scope)?.delete(name2);
10555
+ }
10556
+ function carryBranchDefsOut(popped, scopeStack, alwaysRuns = false, altsOnly = false) {
10557
+ const declared = declaredNames.get(popped);
10558
+ const poppedAlts = alternateDefs.get(popped);
10559
+ const top = scopeStack[scopeStack.length - 1];
10560
+ if (!top) return;
10561
+ if (alwaysRuns) {
10562
+ for (const [name2, id] of popped) {
10563
+ if (declared?.has(name2)) continue;
10564
+ if (!scopeStack.some((scope) => scope.has(name2))) continue;
10565
+ top.set(name2, id);
10566
+ let alts = alternateDefs.get(top);
10567
+ if (!alts) {
10568
+ alts = /* @__PURE__ */ new Map();
10569
+ alternateDefs.set(top, alts);
10570
+ }
10571
+ alts.delete(name2);
10572
+ const kept = poppedAlts?.get(name2);
10573
+ if (kept && kept.length > 0) alts.set(name2, [...kept]);
10574
+ }
10575
+ if (poppedAlts) {
10576
+ for (const [name2, ids] of poppedAlts) if (!popped.has(name2)) carry(name2, ids);
10577
+ }
10578
+ return;
10579
+ }
10580
+ function carry(name2, ids) {
10581
+ if (declared?.has(name2)) return;
10582
+ if (!scopeStack.some((scope) => scope.has(name2))) return;
10583
+ let alts = alternateDefs.get(top);
10584
+ if (!alts) {
10585
+ alts = /* @__PURE__ */ new Map();
10586
+ alternateDefs.set(top, alts);
10587
+ }
10588
+ const list = alts.get(name2) ?? [];
10589
+ for (const id of ids) if (!list.includes(id)) list.push(id);
10590
+ alts.set(name2, list);
10591
+ }
10592
+ if (!altsOnly) for (const [name2, id] of popped) carry(name2, [id]);
10593
+ if (poppedAlts) for (const [name2, ids] of poppedAlts) carry(name2, ids);
10594
+ }
10595
+ function findAlternateDefs(name2, scopeStack) {
10596
+ const out2 = [];
10597
+ for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
10598
+ const alts = alternateDefs.get(scopeStack[i2])?.get(name2);
10599
+ if (alts) {
10600
+ for (const id of alts) if (!out2.includes(id)) out2.push(id);
10601
+ }
10602
+ if (scopeStack[i2].has(name2)) break;
10603
+ }
10604
+ return out2;
10605
+ }
10447
10606
  function findReachingDef(name2, scopeStack) {
10448
10607
  for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
10449
10608
  const defId = scopeStack[i2].get(name2);
@@ -12448,8 +12607,15 @@ var DEFAULT_SINKS = [
12448
12607
  { method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12449
12608
  { method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12450
12609
  { method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12451
- // Wildfly/Undertow servlet resource manager
12452
- { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12610
+ // Wildfly/Undertow servlet resource manager.
12611
+ // ResourceManager / PathResourceManager / FileResourceManager /
12612
+ // ClassPathResourceManager are the interface and implementations WildFly
12613
+ // calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
12614
+ { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12615
+ { method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12616
+ { method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12617
+ { method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12618
+ { method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12453
12619
  // Yamcs file system bucket
12454
12620
  { method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12455
12621
  // RocketMQ validators
@@ -14793,6 +14959,157 @@ function getDefaultConfig() {
14793
14959
  };
14794
14960
  }
14795
14961
 
14962
+ // src/analysis/java-crlf-strip.ts
14963
+ function decodeJavaLiteral(raw) {
14964
+ return raw.replace(/\\(?:u+([0-9a-fA-F]{4})|([0-3][0-7]{0,2}|[4-7][0-7]?)|([btnfrs"'\\]))/g, (_m, hex, oct, simple) => {
14965
+ if (hex !== void 0) return String.fromCharCode(parseInt(hex, 16));
14966
+ if (oct !== void 0) return String.fromCharCode(parseInt(oct, 8));
14967
+ switch (simple) {
14968
+ case "b":
14969
+ return "\b";
14970
+ case "t":
14971
+ return " ";
14972
+ case "n":
14973
+ return "\n";
14974
+ case "f":
14975
+ return "\f";
14976
+ case "r":
14977
+ return "\r";
14978
+ case "s":
14979
+ return " ";
14980
+ default:
14981
+ return simple;
14982
+ }
14983
+ });
14984
+ }
14985
+ function readAtom(s, i2) {
14986
+ const c = s[i2];
14987
+ if (c === "\\" && i2 + 1 < s.length) {
14988
+ const n = s[i2 + 1];
14989
+ return { atom: n === "r" ? "cr" : n === "n" ? "lf" : "other", end: i2 + 2 };
14990
+ }
14991
+ return { atom: c === "\r" ? "cr" : c === "\n" ? "lf" : "other", end: i2 + 1 };
14992
+ }
14993
+ function quantifierAllowsOne(q) {
14994
+ if (q === "") return true;
14995
+ const m = /^(?:(\*)|(\+)|(\?)|\{(\d+)(?:(,)(\d*))?\})[+?]?$/.exec(q);
14996
+ if (!m) return false;
14997
+ if (m[1] || m[2] || m[3]) return true;
14998
+ const min = Number(m[4]);
14999
+ const max = m[5] ? m[6] === "" ? Infinity : Number(m[6]) : min;
15000
+ return min <= 1 && max >= 1;
15001
+ }
15002
+ var NONE = { cr: false, lf: false };
15003
+ function alternativeCoverage(alt) {
15004
+ if (alt === "") return NONE;
15005
+ let cr = false;
15006
+ let lf = false;
15007
+ let i2;
15008
+ if (alt[0] === "[") {
15009
+ if (alt[1] === "^") return NONE;
15010
+ i2 = 1;
15011
+ let closed = false;
15012
+ while (i2 < alt.length) {
15013
+ if (alt[i2] === "]" && i2 > 1) {
15014
+ closed = true;
15015
+ i2++;
15016
+ break;
15017
+ }
15018
+ if (alt[i2] === "[" || alt[i2] === "&" && alt[i2 + 1] === "&") return NONE;
15019
+ const { atom, end } = readAtom(alt, i2);
15020
+ if (atom === "cr") cr = true;
15021
+ else if (atom === "lf") lf = true;
15022
+ i2 = end;
15023
+ }
15024
+ if (!closed) return NONE;
15025
+ } else {
15026
+ const { atom, end } = readAtom(alt, 0);
15027
+ if (atom === "other") return NONE;
15028
+ cr = atom === "cr";
15029
+ lf = atom === "lf";
15030
+ i2 = end;
15031
+ }
15032
+ return quantifierAllowsOne(alt.slice(i2)) ? { cr, lf } : NONE;
15033
+ }
15034
+ function splitAlternatives(s) {
15035
+ const alts = [];
15036
+ let depth = 0;
15037
+ let inClass = false;
15038
+ let cur = "";
15039
+ for (let i2 = 0; i2 < s.length; i2++) {
15040
+ const c = s[i2];
15041
+ if (c === "\\") {
15042
+ cur += c + (s[i2 + 1] ?? "");
15043
+ i2++;
15044
+ continue;
15045
+ }
15046
+ if (inClass) {
15047
+ if (c === "]") inClass = false;
15048
+ cur += c;
15049
+ continue;
15050
+ }
15051
+ if (c === "[") {
15052
+ inClass = true;
15053
+ cur += c;
15054
+ continue;
15055
+ }
15056
+ if (c === "(") depth++;
15057
+ else if (c === ")") depth--;
15058
+ if (c === "|" && depth === 0) {
15059
+ alts.push(cur);
15060
+ cur = "";
15061
+ continue;
15062
+ }
15063
+ cur += c;
15064
+ }
15065
+ alts.push(cur);
15066
+ return alts;
15067
+ }
15068
+ function unwrapGroup(s) {
15069
+ const open = /^\((?:\?:)?/.exec(s);
15070
+ if (!open) return null;
15071
+ let depth = 0;
15072
+ let inClass = false;
15073
+ for (let i2 = 0; i2 < s.length; i2++) {
15074
+ const c = s[i2];
15075
+ if (c === "\\") {
15076
+ i2++;
15077
+ continue;
15078
+ }
15079
+ if (inClass) {
15080
+ if (c === "]") inClass = false;
15081
+ continue;
15082
+ }
15083
+ if (c === "[") {
15084
+ inClass = true;
15085
+ continue;
15086
+ }
15087
+ if (c === "(") depth++;
15088
+ else if (c === ")" && --depth === 0) {
15089
+ return { body: s.slice(open[0].length, i2), quantifier: s.slice(i2 + 1) };
15090
+ }
15091
+ }
15092
+ return null;
15093
+ }
15094
+ function javaReplaceCrLfCoverage(method, rawFrom) {
15095
+ const value = decodeJavaLiteral(rawFrom);
15096
+ if (method === "replace") return { cr: value === "\r", lf: value === "\n" };
15097
+ let body2 = value;
15098
+ const group = unwrapGroup(value);
15099
+ if (group) {
15100
+ if (!quantifierAllowsOne(group.quantifier)) return NONE;
15101
+ body2 = group.body;
15102
+ }
15103
+ let cr = false;
15104
+ let lf = false;
15105
+ for (const alt of splitAlternatives(body2)) {
15106
+ const c = alternativeCoverage(alt);
15107
+ if (c.cr) cr = true;
15108
+ if (c.lf) lf = true;
15109
+ }
15110
+ return { cr, lf };
15111
+ }
15112
+
14796
15113
  // src/analysis/arg-type-resolver.ts
14797
15114
  function findEnclosingType(callInMethod, types) {
14798
15115
  if (!callInMethod) return null;
@@ -14893,7 +15210,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
14893
15210
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
14894
15211
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
14895
15212
  const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
14896
- const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
15213
+ const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
14897
15214
  return { sources, sinks: gatedSinks, sanitizers };
14898
15215
  }
14899
15216
  function sinkPatternAppliesTo(pattern, language) {
@@ -15320,7 +15637,8 @@ function isInterproceduralTaintableType(typeName, language) {
15320
15637
  }
15321
15638
  function isParameterizedQueryCall(call, pattern) {
15322
15639
  if (pattern.type !== "sql_injection") return false;
15323
- const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
15640
+ const positions = pattern.arg_positions ?? [];
15641
+ const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
15324
15642
  const queryArg = call.arguments.find((a) => a.position === queryPos);
15325
15643
  if (queryArg) {
15326
15644
  const queryText = queryArg.literal ?? queryArg.expression ?? "";
@@ -15911,6 +16229,25 @@ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
15911
16229
  }
15912
16230
  return true;
15913
16231
  }
16232
+ var JAVA_OUTBOUND_STREAM_OWNERS = "HttpURLConnection|HttpsURLConnection|URLConnection|JarURLConnection|Socket|SSLSocket|Process";
16233
+ function isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines) {
16234
+ if (language !== "java" || pattern.type !== "xss") return false;
16235
+ const receiver = (call.receiver ?? "").replace(/\s+/g, "");
16236
+ if (!receiver.endsWith(".getOutputStream()")) return false;
16237
+ if (/\.openConnection\(\)\)?\.getOutputStream\(\)$/.test(receiver)) return true;
16238
+ const m = receiver.match(/^\(?([A-Za-z_]\w*)\)?\.getOutputStream\(\)$/);
16239
+ if (!m || !sourceLines) return false;
16240
+ const declRe = new RegExp(
16241
+ `\\b(?:[\\w.]+\\.)?(?:${JAVA_OUTBOUND_STREAM_OWNERS})\\s+${m[1]}\\s*[=;,)]`
16242
+ );
16243
+ const anyDeclRe = new RegExp(`\\b(?:[A-Z][\\w.<>,\\[\\]]*|var)\\s+${m[1]}\\s*[=;,)]`);
16244
+ for (let i2 = call.location.line - 1; i2 >= 0; i2--) {
16245
+ const line = (sourceLines[i2] ?? "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/, "");
16246
+ if (declRe.test(line)) return true;
16247
+ if (anyDeclRe.test(line)) return false;
16248
+ }
16249
+ return false;
16250
+ }
15914
16251
  function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
15915
16252
  if (language !== "go") return false;
15916
16253
  if (pattern.type !== "deserialization") return false;
@@ -16060,6 +16397,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
16060
16397
  if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
16061
16398
  continue;
16062
16399
  }
16400
+ if (isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines)) {
16401
+ continue;
16402
+ }
16063
16403
  if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
16064
16404
  continue;
16065
16405
  }
@@ -16467,6 +16807,13 @@ function receiverMightBeClassImpl(receiver, className) {
16467
16807
  if (receiver === className) {
16468
16808
  return true;
16469
16809
  }
16810
+ const inlineRequire = receiver.match(/^require\(\s*['"`](?:node:)?([\w./-]+)['"`]\s*\)$/);
16811
+ if (inlineRequire && inlineRequire[1] === className) {
16812
+ return true;
16813
+ }
16814
+ if (className === "ClassLoader" && /(?:^|\.)(?:getContextClassLoader|getClassLoader|getSystemClassLoader)\(\s*\)$/.test(receiver.replace(/\s+/g, ""))) {
16815
+ return true;
16816
+ }
16470
16817
  if (receiver.endsWith(")")) {
16471
16818
  const ctorMatch = receiver.match(/^(\w+)\(/);
16472
16819
  if (ctorMatch) {
@@ -16755,8 +17102,9 @@ function calculateSinkConfidence(call, pattern) {
16755
17102
  }
16756
17103
  return Math.min(confidence, 1);
16757
17104
  }
16758
- function findSanitizers(calls, types, patterns, sourceLines) {
17105
+ function findSanitizers(calls, types, patterns, sourceLines, language) {
16759
17106
  const sanitizers = [];
17107
+ const javaReplace = (call) => language === "java" && sourceLines && (call.method_name === "replace" || call.method_name === "replaceAll") ? javaReplaceChainCredit(javaChainTextAt(sourceLines, call.location.line, call.location.column)) : null;
16760
17108
  const sanitizerMethods = /* @__PURE__ */ new Set();
16761
17109
  for (const type of types) {
16762
17110
  for (const method of type.methods) {
@@ -16781,10 +17129,15 @@ function findSanitizers(calls, types, patterns, sourceLines) {
16781
17129
  if (inside.length !== 1) continue;
16782
17130
  const innerCall = inside[0];
16783
17131
  let matched;
16784
- for (const pattern of patterns) {
16785
- if (matchesSanitizerPattern(innerCall, pattern)) {
16786
- matched = pattern;
16787
- break;
17132
+ const replaceCredit = javaReplace(innerCall);
17133
+ if (replaceCredit) {
17134
+ matched = replaceCredit.length > 0 ? { method: innerCall.method_name, removes: replaceCredit } : void 0;
17135
+ } else {
17136
+ for (const pattern of patterns) {
17137
+ if (matchesSanitizerPattern(innerCall, pattern)) {
17138
+ matched = pattern;
17139
+ break;
17140
+ }
16788
17141
  }
16789
17142
  }
16790
17143
  if (!matched || !matched.removes || matched.removes.length === 0) continue;
@@ -16836,6 +17189,18 @@ function findSanitizers(calls, types, patterns, sourceLines) {
16836
17189
  });
16837
17190
  continue;
16838
17191
  }
17192
+ const replaceCredit = javaReplace(call);
17193
+ if (replaceCredit) {
17194
+ if (replaceCredit.length > 0) {
17195
+ sanitizers.push({
17196
+ type: "method_call",
17197
+ method: formatSanitizerMethod(call),
17198
+ line: call.location.line,
17199
+ sanitizes: replaceCredit
17200
+ });
17201
+ }
17202
+ continue;
17203
+ }
16839
17204
  for (const pattern of patterns) {
16840
17205
  if (matchesSanitizerPattern(call, pattern)) {
16841
17206
  sanitizers.push({
@@ -16865,6 +17230,52 @@ function findSanitizers(calls, types, patterns, sourceLines) {
16865
17230
  }
16866
17231
  return sanitizers;
16867
17232
  }
17233
+ var JAVA_LIT = String.raw`(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)')`;
17234
+ var JAVA_REPLACE_ARGS_RE = new RegExp(String.raw`\.(replace|replaceAll)\(\s*${JAVA_LIT}\s*,\s*${JAVA_LIT}\s*\)`, "g");
17235
+ function javaChainTextAt(sourceLines, line, column) {
17236
+ const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
17237
+ let depth = 0;
17238
+ let i2 = 0;
17239
+ for (; i2 < text.length; i2++) {
17240
+ const c = text[i2];
17241
+ if (c === '"' || c === "'") {
17242
+ for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
17243
+ continue;
17244
+ }
17245
+ if (c === "(" || c === "[") {
17246
+ depth++;
17247
+ continue;
17248
+ }
17249
+ if (c === ")" || c === "]") {
17250
+ if (depth === 0) break;
17251
+ depth--;
17252
+ continue;
17253
+ }
17254
+ if (depth === 0 && !/[\w$.\s]/.test(c)) break;
17255
+ }
17256
+ return text.slice(0, i2);
17257
+ }
17258
+ function javaReplaceChainCredit(chainText) {
17259
+ let cr = false;
17260
+ let lf = false;
17261
+ let lt = false;
17262
+ let gt = false;
17263
+ for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
17264
+ const kind = m[1];
17265
+ const rawFrom = m[2] ?? m[3] ?? "";
17266
+ const from = rawFrom.replace(/\\\\/g, "\\");
17267
+ const to = m[4] ?? m[5] ?? "";
17268
+ const cov = javaReplaceCrLfCoverage(kind, rawFrom);
17269
+ if (cov.cr) cr = true;
17270
+ if (cov.lf) lf = true;
17271
+ if (from === "<" && to.startsWith("&")) lt = true;
17272
+ if (from === ">" && to.startsWith("&")) gt = true;
17273
+ }
17274
+ const out2 = [];
17275
+ if (cr && lf) out2.push("log_injection");
17276
+ if (lt && gt) out2.push("xss");
17277
+ return out2;
17278
+ }
16868
17279
  function matchesSanitizerPattern(call, pattern) {
16869
17280
  if (pattern.method) {
16870
17281
  if (call.method_name !== pattern.method) {
@@ -16905,6 +17316,15 @@ function formatSanitizerMethod(call) {
16905
17316
  // src/graph/code-graph.ts
16906
17317
  var CodeGraph = class {
16907
17318
  ir;
17319
+ /**
17320
+ * Defs whose value cannot carry the content of the values they read, so
17321
+ * taint does not flow into them. Set by `analyze()`; empty for a bare graph.
17322
+ * - an int or bool measure or test (`len(x)`, `x.length`,
17323
+ * `re.MatchString(x)`) (#568)
17324
+ * - a lookup in a constant table the file never writes (`T[req.query.k]`)
17325
+ * (#667)
17326
+ */
17327
+ taintBarrierDefIds = /* @__PURE__ */ new Set();
16908
17328
  constructor(ir) {
16909
17329
  this.ir = ir;
16910
17330
  }
@@ -17159,6 +17579,7 @@ var CodeGraph = class {
17159
17579
  for (const [fromDef, chains] of this.chainsByFromDef) {
17160
17580
  if (!result.has(fromDef)) continue;
17161
17581
  for (const chain of chains) {
17582
+ if (this.taintBarrierDefIds.has(chain.to_def)) continue;
17162
17583
  if (!result.has(chain.to_def)) {
17163
17584
  result.add(chain.to_def);
17164
17585
  changed = true;
@@ -17244,6 +17665,10 @@ function buildSanitizersByLine(sanitizers) {
17244
17665
  }
17245
17666
  return out2;
17246
17667
  }
17668
+ function templateInterpolationCount(expression) {
17669
+ if (!expression.trimStart().startsWith("`")) return 0;
17670
+ return (expression.match(/\$\{/g) ?? []).length;
17671
+ }
17247
17672
  function concatOutsideStrings(expression) {
17248
17673
  return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
17249
17674
  }
@@ -17279,31 +17704,38 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17279
17704
  const taintedVars = [];
17280
17705
  const flows = [];
17281
17706
  const javaConcat = graph.ir.meta.language === "java";
17707
+ const jsLike = graph.ir.meta.language === "javascript" || graph.ir.meta.language === "typescript";
17282
17708
  const reachableSinks = /* @__PURE__ */ new Map();
17283
17709
  const defsByLine = graph.defsByLine;
17284
17710
  const usesByLine = graph.usesByLine;
17285
17711
  const callsByLine = graph.callsByLine;
17286
17712
  const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
17287
17713
  const defById = graph.defById;
17288
- const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
17289
- const initialTaint = rawInitialTaint.filter((tv) => {
17714
+ const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine, graph.chainsByFromDef, jsLike);
17715
+ const filteredInitialTaint = rawInitialTaint.filter((tv) => {
17290
17716
  if (tv.line === tv.sourceLine) return true;
17717
+ if (graph.taintBarrierDefIds.has(tv.defId)) return false;
17291
17718
  const sanCheck = checkSanitized(tv.sourceLine, tv.line, tv.sourceType, sanitizersByLine);
17292
17719
  return !sanCheck.sanitized;
17293
17720
  });
17721
+ const initialTaint = [
17722
+ ...filteredInitialTaint.filter((tv) => tv.sourceType !== "interprocedural_param"),
17723
+ ...filteredInitialTaint.filter((tv) => tv.sourceType === "interprocedural_param")
17724
+ ];
17294
17725
  taintedVars.push(...initialTaint);
17295
17726
  const propagatedTaint = propagateThroughChains(
17296
17727
  initialTaint,
17297
17728
  graph.chainsByFromDef,
17298
17729
  defById,
17299
- sanitizersByLine
17730
+ sanitizersByLine,
17731
+ graph.taintBarrierDefIds
17300
17732
  );
17301
17733
  taintedVars.push(...propagatedTaint);
17302
17734
  const allTaintedDefIds = /* @__PURE__ */ new Set();
17303
17735
  const taintByDefId = /* @__PURE__ */ new Map();
17304
17736
  for (const tv of taintedVars) {
17305
17737
  allTaintedDefIds.add(tv.defId);
17306
- taintByDefId.set(tv.defId, tv);
17738
+ if (!yieldsToRealSource(taintByDefId.get(tv.defId), tv)) taintByDefId.set(tv.defId, tv);
17307
17739
  }
17308
17740
  for (const sink of sinks) {
17309
17741
  const usesAtSink = usesByLine.get(sink.line) ?? [];
@@ -17316,7 +17748,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17316
17748
  }
17317
17749
  }
17318
17750
  const expr = typeof arg.expression === "string" ? arg.expression : "";
17319
- const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
17751
+ const compoundConcat = !!arg.variable && expr !== arg.variable && (javaConcat && concatOutsideStrings(expr) || jsLike && templateInterpolationCount(expr) > 1);
17320
17752
  const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17321
17753
  {
17322
17754
  for (const use of candidateUses) {
@@ -17329,11 +17761,26 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17329
17761
  }
17330
17762
  const taintInfo = taintByDefId.get(use.def_id);
17331
17763
  if (taintInfo) {
17764
+ let sanMap = sanitizersByLine;
17765
+ if (identifierOutsideSanitizerCalls(
17766
+ use.variable,
17767
+ callsAtSink,
17768
+ sanitizersByLine.get(sink.line) ?? [],
17769
+ sink.type
17770
+ )) {
17771
+ const stripped = new Map(sanitizersByLine);
17772
+ const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
17773
+ (s) => !sanitizerCoversSink(s, sink.type)
17774
+ );
17775
+ if (kept.length === 0) stripped.delete(sink.line);
17776
+ else stripped.set(sink.line, kept);
17777
+ sanMap = stripped;
17778
+ }
17332
17779
  const isSanitized = checkSanitized(
17333
17780
  taintInfo.line,
17334
17781
  sink.line,
17335
17782
  sink.type,
17336
- sanitizersByLine,
17783
+ sanMap,
17337
17784
  {
17338
17785
  startDefId: use.def_id,
17339
17786
  chainsByToDef: graph.chainsByToDef,
@@ -17374,12 +17821,46 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17374
17821
  }
17375
17822
  return { taintedVars, flows: deduped, reachableSinks };
17376
17823
  }
17377
- function findInitialTaint(sources, callsByLine, defsByLine) {
17824
+ var PARAM_SCOPED_SOURCE_RE = /^(?:GraphQL \w+\.\w+ resolver args|Express handler destructured request) /;
17825
+ function isCompleteConditionHeader(code) {
17826
+ if (!code) return false;
17827
+ const m = /^(?:\}\s*)?(?:else\s+)?(?:if|while)\s*\(/.exec(code);
17828
+ if (!m) return false;
17829
+ const text = code.slice(m[0].length - 1).replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
17830
+ let depth = 0;
17831
+ for (const ch of text) {
17832
+ if (ch === "(") depth++;
17833
+ else if (ch === ")" && --depth === 0) return true;
17834
+ }
17835
+ return false;
17836
+ }
17837
+ function hasSecondStatement(code) {
17838
+ if (!code || /^for\b/.test(code)) return false;
17839
+ const text = code.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
17840
+ return /;\s*(?!\/\/|\/\*|#|\}|\)|$)\S/.test(text);
17841
+ }
17842
+ function findInitialTaint(sources, callsByLine, defsByLine, chainsByFromDef, jsLike = false) {
17378
17843
  const tainted = [];
17379
17844
  for (const source of sources) {
17380
17845
  const defsOnLine = defsByLine.get(source.line) ?? [];
17846
+ let seedable = null;
17847
+ if (source.variable && hasSecondStatement(source.code)) {
17848
+ const own = defsOnLine.filter((d) => d.variable === source.variable);
17849
+ if (own.length > 0) {
17850
+ seedable = new Set(own.map((d) => d.id));
17851
+ for (const d of own) {
17852
+ for (const c of chainsByFromDef.get(d.id) ?? []) {
17853
+ if (defsOnLine.some((o) => o.id === c.to_def)) seedable.add(c.to_def);
17854
+ }
17855
+ }
17856
+ }
17857
+ }
17381
17858
  for (const def of defsOnLine) {
17382
- if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
17859
+ if (seedable && !seedable.has(def.id)) continue;
17860
+ if ((source.type === "interprocedural_param" || PARAM_SCOPED_SOURCE_RE.test(source.location ?? "")) && source.variable && def.kind === "param" && def.variable !== source.variable) {
17861
+ continue;
17862
+ }
17863
+ if (source.type === "interprocedural_param" && def.kind !== "param") {
17383
17864
  continue;
17384
17865
  }
17385
17866
  tainted.push({
@@ -17391,7 +17872,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
17391
17872
  confidence: source.confidence
17392
17873
  });
17393
17874
  }
17394
- const defsNextLine = defsByLine.get(source.line + 1) ?? [];
17875
+ const closesOwnStatement = jsLike && defsOnLine.length > 0 && /;\s*(?:\/\/.*)?$/.test(source.code ?? "");
17876
+ const defsNextLine = isCompleteConditionHeader(source.code) ? [] : (defsByLine.get(source.line + 1) ?? []).filter((d) => !(closesOwnStatement && d.kind === "local"));
17395
17877
  for (const def of defsNextLine) {
17396
17878
  const callsOnSourceLine = callsByLine.get(source.line) ?? [];
17397
17879
  if (callsOnSourceLine.length > 0) {
@@ -17425,12 +17907,15 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
17425
17907
  }
17426
17908
  return tainted;
17427
17909
  }
17428
- function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitizersByLine) {
17910
+ function yieldsToRealSource(existing, next) {
17911
+ return existing !== void 0 && next.sourceType === "interprocedural_param" && existing.sourceType !== "interprocedural_param";
17912
+ }
17913
+ function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitizersByLine, taintBarrierDefIds) {
17429
17914
  const propagated = [];
17430
17915
  const taintedDefIds = new Set(initialTaint.map((t) => t.defId));
17431
17916
  const taintInfoByDefId = /* @__PURE__ */ new Map();
17432
17917
  for (const t of initialTaint) {
17433
- taintInfoByDefId.set(t.defId, t);
17918
+ if (!yieldsToRealSource(taintInfoByDefId.get(t.defId), t)) taintInfoByDefId.set(t.defId, t);
17434
17919
  }
17435
17920
  const queue = [...initialTaint.map((t) => t.defId)];
17436
17921
  const visited = new Set(queue);
@@ -17441,6 +17926,7 @@ function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitize
17441
17926
  const outgoingChains = chainsByFromDef.get(currentDefId) ?? [];
17442
17927
  for (const chain of outgoingChains) {
17443
17928
  if (visited.has(chain.to_def)) continue;
17929
+ if (taintBarrierDefIds.has(chain.to_def)) continue;
17444
17930
  const targetDef = defById.get(chain.to_def);
17445
17931
  if (!targetDef) continue;
17446
17932
  const sanitizeCheck = checkSanitized(
@@ -17487,6 +17973,92 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
17487
17973
  "crlf",
17488
17974
  "mass_assignment"
17489
17975
  ]);
17976
+ function readBalancedArg(text, openParen) {
17977
+ let depth = 0;
17978
+ let quote = null;
17979
+ let escaped = false;
17980
+ for (let i2 = openParen; i2 < text.length; i2++) {
17981
+ const c = text[i2];
17982
+ if (quote) {
17983
+ if (escaped) {
17984
+ escaped = false;
17985
+ continue;
17986
+ }
17987
+ if (c === "\\") {
17988
+ escaped = true;
17989
+ continue;
17990
+ }
17991
+ if (c === quote) quote = null;
17992
+ continue;
17993
+ }
17994
+ if (c === '"' || c === "'") {
17995
+ quote = c;
17996
+ continue;
17997
+ }
17998
+ if (c === "(") depth++;
17999
+ else if (c === ")") {
18000
+ depth--;
18001
+ if (depth === 0) return text.slice(openParen + 1, i2);
18002
+ }
18003
+ }
18004
+ return null;
18005
+ }
18006
+ function sanitizerCallRefs(sanitizers, sinkType) {
18007
+ const refs = [];
18008
+ for (const san of sanitizers) {
18009
+ if (!sanitizerCoversSink(san, sinkType)) continue;
18010
+ const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
18011
+ if (!m?.[2]) continue;
18012
+ refs.push({ receiver: m[1], methodName: m[2] });
18013
+ }
18014
+ return refs;
18015
+ }
18016
+ function maskSanitizerCalls(expr, refs) {
18017
+ let out2 = expr;
18018
+ for (const ref of refs) {
18019
+ const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
18020
+ for (const needle of needles) {
18021
+ const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
18022
+ for (let guard = 0; guard < 8; guard++) {
18023
+ const m = re.exec(out2);
18024
+ if (!m) break;
18025
+ const open = m.index + m[0].length - 1;
18026
+ const arg = readBalancedArg(out2, open);
18027
+ if (arg === null) break;
18028
+ out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
18029
+ }
18030
+ }
18031
+ }
18032
+ return out2;
18033
+ }
18034
+ function maskLengthOf(expr) {
18035
+ return expr.replace(/\b(?:len|strlen|count)\s*\(\s*[\w$.]+\s*\)/g, " 0 ").replace(/[\w$.]+\s*\.\s*(?:length|size|count)\s*(?:\(\s*\))?(?![\w$(])/g, " 0 ");
18036
+ }
18037
+ function blankStringLiterals(expr) {
18038
+ return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
18039
+ }
18040
+ function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
18041
+ if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
18042
+ const refs = sanitizerCallRefs(sanitizers, sinkType);
18043
+ if (refs.length === 0) return false;
18044
+ const re = new RegExp(
18045
+ `(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
18046
+ );
18047
+ for (const call of calls) {
18048
+ if (refs.some((r) => r.methodName === call.method_name)) continue;
18049
+ const masked = call.arguments.map((arg) => {
18050
+ const expr = arg.expression ?? "";
18051
+ return { expr, masked: expr ? maskSanitizerCalls(expr, refs) : expr };
18052
+ });
18053
+ if (!masked.some((m) => m.masked !== m.expr)) continue;
18054
+ for (const m of masked) {
18055
+ if (!m.masked) continue;
18056
+ const text = m.masked === m.expr ? maskLengthOf(m.masked) : m.masked;
18057
+ if (re.test(blankStringLiterals(text))) return true;
18058
+ }
18059
+ }
18060
+ return false;
18061
+ }
17490
18062
  function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
17491
18063
  const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
17492
18064
  const sanitizersAtTarget = sanitizersByLine.get(toLine);