circle-ir 4.16.1 → 4.17.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 (49) hide show
  1. package/dist/analysis/constant-propagation/index.d.ts +2 -0
  2. package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
  3. package/dist/analysis/constant-propagation/index.js +10 -0
  4. package/dist/analysis/constant-propagation/index.js.map +1 -1
  5. package/dist/analysis/constant-propagation/propagator.d.ts +11 -0
  6. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  7. package/dist/analysis/constant-propagation/propagator.js +125 -20
  8. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  9. package/dist/analysis/constant-propagation/types.d.ts +2 -0
  10. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  11. package/dist/analysis/constant-propagation.d.ts +1 -1
  12. package/dist/analysis/constant-propagation.d.ts.map +1 -1
  13. package/dist/analysis/constant-propagation.js +1 -1
  14. package/dist/analysis/constant-propagation.js.map +1 -1
  15. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  16. package/dist/analysis/passes/cross-file-pass.js +3 -1
  17. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  18. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  19. package/dist/analysis/passes/language-sources-pass.js +22 -0
  20. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  21. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  22. package/dist/analysis/passes/param-source-flow-gate-pass.js +17 -1
  23. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  24. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  25. package/dist/analysis/passes/taint-propagation-pass.js +167 -11
  26. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  27. package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
  28. package/dist/analysis/rust-literal-guard.js +100 -1
  29. package/dist/analysis/rust-literal-guard.js.map +1 -1
  30. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  31. package/dist/analysis/taint-matcher.js +1 -0
  32. package/dist/analysis/taint-matcher.js.map +1 -1
  33. package/dist/browser/circle-ir.js +427 -26
  34. package/dist/core/circle-ir-core.cjs +149 -17
  35. package/dist/core/circle-ir-core.js +149 -17
  36. package/dist/core/extractors/types.js +53 -9
  37. package/dist/core/extractors/types.js.map +1 -1
  38. package/dist/navigation/navigation-index.d.ts +40 -1
  39. package/dist/navigation/navigation-index.d.ts.map +1 -1
  40. package/dist/navigation/navigation-index.js +300 -45
  41. package/dist/navigation/navigation-index.js.map +1 -1
  42. package/dist/resolution/cross-file.d.ts +9 -0
  43. package/dist/resolution/cross-file.d.ts.map +1 -1
  44. package/dist/resolution/cross-file.js +134 -0
  45. package/dist/resolution/cross-file.js.map +1 -1
  46. package/dist/types/index.d.ts +18 -0
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/index.js.map +1 -1
  49. package/package.json +1 -1
@@ -4663,14 +4663,43 @@ function extractJavaTypes(tree, cache, options) {
4663
4663
  );
4664
4664
  const path = node ? enclosingTypePath(node) : [];
4665
4665
  if (path.length > 0) t.enclosing_type = path.join(".");
4666
- const params = node?.childForFieldName("type_parameters");
4667
- if (params) {
4668
- const names = params.namedChildren.filter((c) => c?.type === "type_parameter").map((c) => c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier")).map((x) => x ? getNodeText(x) : "").filter(Boolean);
4669
- if (names.length > 0) t.type_parameters = names;
4666
+ const tp = typeParameters(node);
4667
+ if (tp.names.length > 0) t.type_parameters = tp.names;
4668
+ if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
4669
+ }
4670
+ const allMethods = /* @__PURE__ */ new Map();
4671
+ for (const t of types) {
4672
+ for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
4673
+ }
4674
+ for (const kind of ["method_declaration", "constructor_declaration"]) {
4675
+ for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
4676
+ const name2 = getIdentifier(node, "name");
4677
+ const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
4678
+ if (!info2) continue;
4679
+ const tp = typeParameters(node);
4680
+ if (tp.names.length > 0) info2.type_parameters = tp.names;
4681
+ if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
4670
4682
  }
4671
4683
  }
4672
4684
  return types;
4673
4685
  }
4686
+ function typeParameters(node) {
4687
+ const names = [];
4688
+ const bounds = {};
4689
+ const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
4690
+ if (!list) return { names, bounds };
4691
+ for (const c of list.namedChildren) {
4692
+ if (c?.type !== "type_parameter") continue;
4693
+ const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
4694
+ const name2 = id ? getNodeText(id) : "";
4695
+ if (!name2) continue;
4696
+ names.push(name2);
4697
+ const bound = c.namedChildren.find((x) => x?.type === "type_bound");
4698
+ const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
4699
+ if (first) bounds[name2] = getNodeText(first);
4700
+ }
4701
+ return { names, bounds };
4702
+ }
4674
4703
  function recordComponents(node) {
4675
4704
  const out2 = [];
4676
4705
  const params = node.childForFieldName("parameters");
@@ -16735,6 +16764,85 @@ function passesMutParam(line, n) {
16735
16764
  if (!new RegExp(String.raw`(?<![\w.])(?:[A-Za-z_]\w*(?:\s*::\s*[A-Za-z_]\w*)*)\s*\((?:[^)]*,\s*)?${n}\b`).test(line)) return false;
16736
16765
  return !new RegExp(String.raw`\b${n}\s*\.\s*\w+\s*\(`).test(line) || new RegExp(String.raw`(?<![\w.])\w+\s*\(\s*${n}\b`).test(line);
16737
16766
  }
16767
+ function findRustAllowlistTernarySanitizers(code) {
16768
+ const masked = maskRust(code);
16769
+ const origLines = code.split("\n");
16770
+ const maskedLines = masked.split("\n");
16771
+ const out2 = [];
16772
+ const allowlistVar = (cond) => {
16773
+ const c = cond.trim();
16774
+ let m = new RegExp(String.raw`^!?(\w+)\s*\.\s*contains\s*\(\s*&\s*(\w+)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)$`).exec(c);
16775
+ if (m) return m[2];
16776
+ if (m = new RegExp(String.raw`^!\s*\[\s*${STR}(?:\s*,\s*${STR})*\s*,?\s*\]\s*\.\s*contains\s*\(\s*&\s*${VAR}\s*\)$`).exec(c)) {
16777
+ return m[1];
16778
+ }
16779
+ return guardedVariable(c);
16780
+ };
16781
+ const LITERAL_CHAIN = /* @__PURE__ */ new Set(["to_string", "into", "to_owned", "clone"]);
16782
+ const literalOnly = (block) => {
16783
+ const maskedB = maskRust(block.trim());
16784
+ if (!maskedB.trim()) return false;
16785
+ const re = /[A-Za-z_]\w*/g;
16786
+ let m;
16787
+ while ((m = re.exec(maskedB)) !== null) {
16788
+ const id = m[0];
16789
+ if (KEYWORDS.has(id) || /[A-Z]/.test(id[0]) || LITERAL_CHAIN.has(id)) continue;
16790
+ return false;
16791
+ }
16792
+ return true;
16793
+ };
16794
+ const usesOnlyVar = (block, name2) => {
16795
+ const b = block.trim();
16796
+ if (!b) return false;
16797
+ const maskedB = maskRust(b);
16798
+ const re = new RegExp(String.raw`(?<![\w.])${name2}\b`);
16799
+ if (!re.test(maskedB)) return false;
16800
+ return !readsOtherValue(b, maskedB, name2);
16801
+ };
16802
+ for (const m of masked.matchAll(/\blet\s+(\w+)\s*=\s*if\b/g)) {
16803
+ const bound = m[1];
16804
+ const at = m.index + m[0].length - 2;
16805
+ let depth = 0;
16806
+ let open = -1;
16807
+ for (let i2 = at + 2; i2 < masked.length; i2++) {
16808
+ const ch = masked[i2];
16809
+ if (ch === "(" || ch === "[") depth++;
16810
+ else if (ch === ")" || ch === "]") depth--;
16811
+ else if (ch === "{" && depth === 0) {
16812
+ open = i2;
16813
+ break;
16814
+ } else if (ch === ";" || ch === "}") break;
16815
+ }
16816
+ if (open < 0) continue;
16817
+ const cond = code.slice(at + 2, open).trim();
16818
+ const guarded = allowlistVar(cond);
16819
+ if (!guarded) continue;
16820
+ const thenClose = matchBrace(masked, open);
16821
+ if (thenClose < 0) continue;
16822
+ const thenBody = code.slice(open + 1, thenClose);
16823
+ if (!/^\s*else\b/.test(masked.slice(thenClose + 1, thenClose + 10))) continue;
16824
+ const elseOpen = masked.indexOf("{", thenClose + 1);
16825
+ if (elseOpen < 0) continue;
16826
+ const elseClose = matchBrace(masked, elseOpen);
16827
+ if (elseClose < 0) continue;
16828
+ const elseBody = code.slice(elseOpen + 1, elseClose);
16829
+ const inverted = /^\s*!/.test(cond);
16830
+ const ok = inverted ? literalOnly(thenBody) && usesOnlyVar(elseBody, guarded) : usesOnlyVar(thenBody, guarded) && literalOnly(elseBody);
16831
+ if (!ok) continue;
16832
+ const assignLine = masked.slice(0, at).split("\n").length;
16833
+ const mention = new RegExp(String.raw`(?<![\w.])${bound}\b`);
16834
+ for (let l = assignLine; l < origLines.length; l++) {
16835
+ if (!mention.test(maskedLines[l])) continue;
16836
+ out2.push({
16837
+ type: "rust_allowlist_ternary",
16838
+ method: "guard",
16839
+ line: l + 1,
16840
+ sanitizes: CREDITED_TYPES
16841
+ });
16842
+ }
16843
+ }
16844
+ return out2;
16845
+ }
16738
16846
  function findRustLiteralEqualityGuardSanitizers(code) {
16739
16847
  const masked = maskRust(code);
16740
16848
  const origLines = code.split("\n");
@@ -16855,7 +16963,7 @@ function findRustLiteralEqualityGuardSanitizers(code) {
16855
16963
  });
16856
16964
  }
16857
16965
  }
16858
- return out2;
16966
+ return [...out2, ...findRustAllowlistTernarySanitizers(code)];
16859
16967
  }
16860
16968
  function statementSpan(code, masked, lineStart, endIdx) {
16861
16969
  let start2 = lineStart;
@@ -18233,6 +18341,7 @@ function findCSharpRequestSources(sourceCode, language) {
18233
18341
  const assignRe = /^\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
18234
18342
  const arrayElementAssignRe = /^\s*([A-Za-z_]\w*)\s*\[[^\]]+\]\s*=\s*(.+?);?\s*$/;
18235
18343
  const memberFieldAssignRe = /^\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
18344
+ const collectionAddRe = /^\s*([A-Za-z_]\w*)\s*\.\s*Add\s*\(\s*(.+?)\s*\)\s*;?\s*$/;
18236
18345
  const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
18237
18346
  const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
18238
18347
  const formFileParams = /* @__PURE__ */ new Set();
@@ -18326,6 +18435,25 @@ function findCSharpRequestSources(sourceCode, language) {
18326
18435
  continue;
18327
18436
  }
18328
18437
  }
18438
+ const addEl = collectionAddRe.exec(stmt);
18439
+ if (addEl) {
18440
+ const [, baseName, rhs2] = addEl;
18441
+ const type2 = classify(rhs2);
18442
+ if (type2 && baseName) {
18443
+ const lineNumber2 = i2 + 1;
18444
+ if (!sources.some((s) => s.line === lineNumber2 && s.variable === baseName)) {
18445
+ sources.push({
18446
+ type: type2,
18447
+ location: `${baseName}.Add(${rhs2.trim().substring(0, 50)}${rhs2.length > 50 ? "..." : ""})`,
18448
+ severity: type2 === "db_input" ? "medium" : "high",
18449
+ line: lineNumber2,
18450
+ confidence: 1,
18451
+ variable: baseName
18452
+ });
18453
+ }
18454
+ continue;
18455
+ }
18456
+ }
18329
18457
  const m = assignRe.exec(stmt);
18330
18458
  const rhsCrossesBlock = m !== null && m[2].includes("{");
18331
18459
  if (!m || rhsCrossesBlock) {
@@ -26404,6 +26532,9 @@ function isInterproceduralTaintableType(typeName, language) {
26404
26532
  "IEnumerable",
26405
26533
  "ICollection",
26406
26534
  "IList",
26535
+ "List",
26536
+ "Hashtable",
26537
+ "ArrayList",
26407
26538
  "Dictionary",
26408
26539
  "IDictionary",
26409
26540
  "IReadOnlyList",
@@ -32027,6 +32158,11 @@ var ConstantPropagator = class _ConstantPropagator {
32027
32158
  taintedArrayElements = /* @__PURE__ */ new Map();
32028
32159
  // Track current method name for scoping local variables
32029
32160
  currentMethod = null;
32161
+ /** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
32162
+ blockScopeStack = [];
32163
+ lineBlockScopes = /* @__PURE__ */ new Map();
32164
+ /** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
32165
+ skipMethodBodyBlockScope = false;
32030
32166
  // Track conditional taints: which variables were tainted under which conditions
32031
32167
  // Maps condition expression string → set of variables tainted under that condition
32032
32168
  conditionalTaints = /* @__PURE__ */ new Map();
@@ -32040,6 +32176,8 @@ var ConstantPropagator = class _ConstantPropagator {
32040
32176
  inSynchronizedBlock = false;
32041
32177
  // Track iterator sources: iterator variable name → collection name it was created from
32042
32178
  iteratorSources = /* @__PURE__ */ new Map();
32179
+ /** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
32180
+ taintedFromIteratorNext = /* @__PURE__ */ new Set();
32043
32181
  // Track class field names (declared at class level, not local variables)
32044
32182
  classFields = /* @__PURE__ */ new Set();
32045
32183
  // Track tainted method parameters for inter-procedural analysis
@@ -32097,12 +32235,16 @@ var ConstantPropagator = class _ConstantPropagator {
32097
32235
  this.sanitizedVars.clear();
32098
32236
  this.sanitizedVarUncovered.clear();
32099
32237
  this.currentMethod = null;
32238
+ this.blockScopeStack = [];
32239
+ this.lineBlockScopes.clear();
32240
+ this.skipMethodBodyBlockScope = false;
32100
32241
  this.conditionalTaints.clear();
32101
32242
  this.conditionStack = [];
32102
32243
  this.lineConditions.clear();
32103
32244
  this.synchronizedLines.clear();
32104
32245
  this.inSynchronizedBlock = false;
32105
32246
  this.iteratorSources.clear();
32247
+ this.taintedFromIteratorNext.clear();
32106
32248
  this.classFields.clear();
32107
32249
  this.instanceFieldTaint.clear();
32108
32250
  this.currentClassName = null;
@@ -32127,6 +32269,9 @@ var ConstantPropagator = class _ConstantPropagator {
32127
32269
  const resultSymbols = new Map(this.symbols);
32128
32270
  for (const name2 of this.tainted) {
32129
32271
  if (name2.includes(":")) {
32272
+ if (/^[^:]+:b\d+:/.test(name2)) {
32273
+ continue;
32274
+ }
32130
32275
  const unscoped = name2.substring(name2.indexOf(":") + 1);
32131
32276
  resultTainted.add(unscoped);
32132
32277
  }
@@ -32141,6 +32286,9 @@ var ConstantPropagator = class _ConstantPropagator {
32141
32286
  for (const [name2, value] of this.symbols) {
32142
32287
  if (this.goScopedNames.has(name2)) continue;
32143
32288
  if (name2.includes(":")) {
32289
+ if (/^[^:]+:b\d+:/.test(name2)) {
32290
+ continue;
32291
+ }
32144
32292
  const unscoped = name2.substring(name2.indexOf(":") + 1);
32145
32293
  if (!resultSymbols.has(unscoped)) {
32146
32294
  resultSymbols.set(unscoped, value);
@@ -32150,6 +32298,7 @@ var ConstantPropagator = class _ConstantPropagator {
32150
32298
  return {
32151
32299
  symbols: resultSymbols,
32152
32300
  tainted: resultTainted,
32301
+ lineBlockScopes: new Map(this.lineBlockScopes),
32153
32302
  unreachableLines: new Set(this.unreachableLines),
32154
32303
  taintedCollections: new Map(this.taintedCollections),
32155
32304
  taintedArrayElements: new Map(this.taintedArrayElements),
@@ -32693,6 +32842,18 @@ var ConstantPropagator = class _ConstantPropagator {
32693
32842
  // ===========================================================================
32694
32843
  // Taint Refinement
32695
32844
  // ===========================================================================
32845
+ /** True when `node` is `iter.next()` (possibly under a cast). */
32846
+ isIteratorNextExpression(node) {
32847
+ const text = getNodeText2(node, this.source);
32848
+ if (/\.\s*next\s*\(/.test(text)) return true;
32849
+ let current = node;
32850
+ if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
32851
+ current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
32852
+ }
32853
+ if (!current || current.type !== "method_invocation") return false;
32854
+ const nameNode = current.childForFieldName("name");
32855
+ return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
32856
+ }
32696
32857
  refineTaintFromConstants() {
32697
32858
  let changed = true;
32698
32859
  let iterations = 0;
@@ -32702,6 +32863,9 @@ var ConstantPropagator = class _ConstantPropagator {
32702
32863
  iterations++;
32703
32864
  const toRemove = [];
32704
32865
  for (const varName of this.tainted) {
32866
+ if (this.taintedFromIteratorNext.has(varName)) {
32867
+ continue;
32868
+ }
32705
32869
  const symbol = this.symbols.get(varName);
32706
32870
  if (symbol && symbol.type !== "unknown") {
32707
32871
  toRemove.push(varName);
@@ -32750,6 +32914,9 @@ var ConstantPropagator = class _ConstantPropagator {
32750
32914
  if (this.unreachableLines.has(line)) {
32751
32915
  return true;
32752
32916
  }
32917
+ if (this.blockScopeStack.length > 0) {
32918
+ this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
32919
+ }
32753
32920
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
32754
32921
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
32755
32922
  }
@@ -32781,6 +32948,15 @@ var ConstantPropagator = class _ConstantPropagator {
32781
32948
  case "if_statement":
32782
32949
  this.handleIfStatement(node);
32783
32950
  return true;
32951
+ case "block": {
32952
+ if (this.skipMethodBodyBlockScope) {
32953
+ this.skipMethodBodyBlockScope = false;
32954
+ }
32955
+ for (const child of node.children) {
32956
+ this.visit(child);
32957
+ }
32958
+ return true;
32959
+ }
32784
32960
  case "switch_expression":
32785
32961
  case "switch_statement":
32786
32962
  this.handleSwitch(node);
@@ -32866,7 +33042,9 @@ var ConstantPropagator = class _ConstantPropagator {
32866
33042
  }
32867
33043
  const body2 = node.childForFieldName("body");
32868
33044
  if (body2) {
33045
+ this.skipMethodBodyBlockScope = true;
32869
33046
  this.visit(body2);
33047
+ this.skipMethodBodyBlockScope = false;
32870
33048
  }
32871
33049
  this.currentMethod = prevMethod;
32872
33050
  this.inConstructor = prevInConstructor;
@@ -32880,10 +33058,31 @@ var ConstantPropagator = class _ConstantPropagator {
32880
33058
  if (varName.includes(".") || varName.includes(":")) {
32881
33059
  return varName;
32882
33060
  }
33061
+ const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
32883
33062
  if (this.currentMethod) {
32884
- return `${this.currentMethod}:${varName}`;
33063
+ return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
32885
33064
  }
32886
- return varName;
33065
+ return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
33066
+ }
33067
+ /** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
33068
+ getScopedNameCandidates(varName) {
33069
+ if (varName.includes(".") || varName.includes(":")) {
33070
+ return [varName];
33071
+ }
33072
+ const out2 = [];
33073
+ for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
33074
+ const blockId = this.blockScopeStack[i2];
33075
+ if (this.currentMethod) {
33076
+ out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
33077
+ } else {
33078
+ out2.push(`b${blockId}:${varName}`);
33079
+ }
33080
+ }
33081
+ if (this.currentMethod) {
33082
+ out2.push(`${this.currentMethod}:${varName}`);
33083
+ }
33084
+ out2.push(varName);
33085
+ return out2;
32887
33086
  }
32888
33087
  /**
32889
33088
  * Look up a variable value, checking both scoped and unscoped names.
@@ -32891,11 +33090,12 @@ var ConstantPropagator = class _ConstantPropagator {
32891
33090
  * either local (scoped) or global (unscoped, like class fields).
32892
33091
  */
32893
33092
  lookupSymbol(varName) {
32894
- if (this.currentMethod && !varName.includes(".") && !varName.includes(":")) {
32895
- const scopedName = `${this.currentMethod}:${varName}`;
32896
- const scopedValue = this.symbols.get(scopedName);
32897
- if (scopedValue) {
32898
- return scopedValue;
33093
+ if (!varName.includes(".") && !varName.includes(":")) {
33094
+ for (const scopedName of this.getScopedNameCandidates(varName)) {
33095
+ const scopedValue = this.symbols.get(scopedName);
33096
+ if (scopedValue) {
33097
+ return scopedValue;
33098
+ }
32899
33099
  }
32900
33100
  }
32901
33101
  return this.symbols.get(varName);
@@ -32947,8 +33147,22 @@ var ConstantPropagator = class _ConstantPropagator {
32947
33147
  this.trackIteratorsInNode(initNode);
32948
33148
  }
32949
33149
  }
32950
- for (const child of node.children) {
32951
- this.visit(child);
33150
+ if (node.type === "while_statement" || node.type === "do_statement") {
33151
+ const body2 = node.childForFieldName("body");
33152
+ for (const child of node.children) {
33153
+ if (child !== body2) this.visit(child);
33154
+ }
33155
+ if (body2?.type === "block") {
33156
+ this.blockScopeStack.push(getNodeLine(body2));
33157
+ this.visit(body2);
33158
+ this.blockScopeStack.pop();
33159
+ } else if (body2) {
33160
+ this.visit(body2);
33161
+ }
33162
+ } else {
33163
+ for (const child of node.children) {
33164
+ this.visit(child);
33165
+ }
32952
33166
  }
32953
33167
  for (const varName of loopVarNames) {
32954
33168
  this.symbols.set(varName, createUnknown(getNodeLine(node)));
@@ -33090,6 +33304,10 @@ var ConstantPropagator = class _ConstantPropagator {
33090
33304
  this.sanitizedVars.delete(scopedName);
33091
33305
  this.sanitizedVarUncovered.delete(scopedName);
33092
33306
  this.definitionNodes.set(scopedName, valueNode);
33307
+ if (this.isIteratorNextExpression(valueNode)) {
33308
+ this.taintedFromIteratorNext.add(scopedName);
33309
+ this.taintedFromIteratorNext.add(varName);
33310
+ }
33093
33311
  this.symbols.set(scopedName, createUnknown(line));
33094
33312
  } else {
33095
33313
  if (this.inConditionalBranch) {
@@ -33217,6 +33435,12 @@ var ConstantPropagator = class _ConstantPropagator {
33217
33435
  this.sanitizedVars.delete(scopedName);
33218
33436
  this.sanitizedVarUncovered.delete(scopedName);
33219
33437
  this.definitionNodes.set(scopedName, right);
33438
+ if (this.isIteratorNextExpression(right)) {
33439
+ this.taintedFromIteratorNext.add(scopedName);
33440
+ if (!varName.includes(".")) {
33441
+ this.taintedFromIteratorNext.add(varName);
33442
+ }
33443
+ }
33220
33444
  this.symbols.set(scopedName, createUnknown(line));
33221
33445
  if (this.inConstructor && varName.startsWith("this.")) {
33222
33446
  const fieldName = varName.substring(5);
@@ -34207,10 +34431,17 @@ var ConstantPropagator = class _ConstantPropagator {
34207
34431
  }
34208
34432
  }
34209
34433
  if (node.type === "identifier") {
34210
- const scopedName = this.currentMethod ? `${this.currentMethod}:${text}` : text;
34211
- const isTainted = this.tainted.has(scopedName) || this.tainted.has(text);
34212
- if (isTainted) {
34213
- if (this.isExcludedByCondition(scopedName) || this.isExcludedByCondition(text)) {
34434
+ let isTainted = false;
34435
+ let boundName = null;
34436
+ for (const candidate of this.getScopedNameCandidates(text)) {
34437
+ if (this.tainted.has(candidate)) {
34438
+ isTainted = true;
34439
+ boundName = candidate;
34440
+ break;
34441
+ }
34442
+ }
34443
+ if (isTainted && boundName) {
34444
+ if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
34214
34445
  return false;
34215
34446
  }
34216
34447
  }
@@ -34417,6 +34648,14 @@ function analyzeConstantPropagation(tree, sourceCode, options = {}) {
34417
34648
  options.taintedParameters ?? []
34418
34649
  );
34419
34650
  }
34651
+ function constPropScopedVar(method, varName, line, result) {
34652
+ if (!method) return varName;
34653
+ const blockId = result.lineBlockScopes?.get(line);
34654
+ if (blockId !== void 0) {
34655
+ return `${method}:b${blockId}:${varName}`;
34656
+ }
34657
+ return `${method}:${varName}`;
34658
+ }
34420
34659
  function isFalsePositive(result, sinkLine, taintedVar) {
34421
34660
  if (result.unreachableLines.has(sinkLine)) {
34422
34661
  return { isFalsePositive: true, reason: "sink_in_dead_code" };
@@ -43296,6 +43535,15 @@ function isReactHtmlComponent(m, f, lines) {
43296
43535
  const sig = lines[m.start_line - 1] ?? "";
43297
43536
  return /\bfunction\s+[A-Z][A-Za-z0-9]*\s*\(\s*\{/.test(sig);
43298
43537
  }
43538
+ function isPythonYamlLoadParamDeser(f, lines) {
43539
+ if (f.source_type !== "interprocedural_param" || f.sink_type !== "deserialization") return false;
43540
+ const head = f.path[0]?.variable;
43541
+ if (!head || f.path.length < 2) return false;
43542
+ const sinkLine = lines[f.sink_line - 1] ?? "";
43543
+ if (!/\byaml\.load\s*\(/.test(sinkLine) || /\bLoader\s*=\s*yaml\.SafeLoader/.test(sinkLine)) return false;
43544
+ const esc = head.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
43545
+ return new RegExp(`\\byaml\\.load\\s*\\(\\s*${esc}\\b`).test(sinkLine);
43546
+ }
43299
43547
  var FIELD_CARRIED_SOURCE = /constructor-injected field|returns tainted field/;
43300
43548
  var EXECUTOR_CALLEES = /* @__PURE__ */ new Map([
43301
43549
  ["submit", 0],
@@ -43435,7 +43683,7 @@ var ParamSourceFlowGatePass = class {
43435
43683
  let keep = true;
43436
43684
  if (f.source_type === "interprocedural_param" && !realSourceLines.has(f.source_line)) {
43437
43685
  const m = enclosingMethod(methods, f.source_line);
43438
- keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines));
43686
+ keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines) || ctx.language === "python" && isPythonYamlLoadParamDeser(f, lines));
43439
43687
  }
43440
43688
  if (keep) flows[w++] = f;
43441
43689
  else {
@@ -44068,6 +44316,32 @@ function javaReceiverChainDerivedVar(rhsByLocal, localVar, seedVar) {
44068
44316
  const receiverOnRhs = new RegExp(`(?:^|[^\\w.$])${esc}\\s*\\.`, "u");
44069
44317
  return rhsList.some((rhs) => receiverOnRhs.test(rhs));
44070
44318
  }
44319
+ function defLineForUse(graph, varName, useLine) {
44320
+ const own = (graph.defsByLine.get(useLine) ?? []).find((d2) => d2.variable === varName);
44321
+ if (own) return own.line;
44322
+ const use = (graph.usesByLine.get(useLine) ?? []).find((u) => u.variable === varName && u.def_id != null);
44323
+ const d = use ? graph.defById.get(use.def_id) : void 0;
44324
+ return d?.line ?? useLine;
44325
+ }
44326
+ function constPropFlowLookup(language, method, varName, blockLine, constProp, preferMethodScoped = false) {
44327
+ if (language === "go") {
44328
+ return constPropScopedVar(method, varName, blockLine, constProp);
44329
+ }
44330
+ if (!method || varName.includes(":")) return varName;
44331
+ const blockScoped = constPropScopedVar(method, varName, blockLine, constProp);
44332
+ const hasTaintBinding = (name2) => constProp.tainted.has(name2) || constProp.sanitizedVars.has(name2);
44333
+ if (blockScoped.includes(":b") && hasTaintBinding(blockScoped)) {
44334
+ return blockScoped;
44335
+ }
44336
+ const methodScoped = `${method}:${varName}`;
44337
+ if (hasTaintBinding(methodScoped)) {
44338
+ return methodScoped;
44339
+ }
44340
+ if (preferMethodScoped && method) {
44341
+ return methodScoped;
44342
+ }
44343
+ return constProp.lineBlockScopes?.has(blockLine) ? blockScoped : varName;
44344
+ }
44071
44345
  function indexJavaLocalAssignments(code) {
44072
44346
  const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
44073
44347
  const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
@@ -44113,10 +44387,24 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44113
44387
  };
44114
44388
  return (step, sourceLine, sinkLine, sinkType) => {
44115
44389
  const method = methodAtLine(graph, step.line);
44116
- const scopedVar = method ? `${method}:${step.variable}` : step.variable;
44117
- const r = isFalsePositive(constProp, step.line, language === "go" ? scopedVar : step.variable);
44390
+ const blockLine = defLineForUse(graph, step.variable, step.line);
44391
+ const fpLookup = constPropFlowLookup(
44392
+ language,
44393
+ method,
44394
+ step.variable,
44395
+ blockLine,
44396
+ constProp
44397
+ );
44398
+ const r = isFalsePositive(constProp, step.line, fpLookup);
44118
44399
  if (!r.isFalsePositive) return false;
44119
44400
  if (r.reason !== "variable_not_tainted") return true;
44401
+ if (r.reason === "variable_not_tainted" && language === "java" && method && fpLookup === step.variable && !step.variable.includes(".")) {
44402
+ const v = step.variable;
44403
+ if (constProp.tainted.has(`${method}:${v}`)) return false;
44404
+ for (const t of constProp.tainted) {
44405
+ if (t.startsWith(`${method}:b`) && t.endsWith(`:${v}`)) return false;
44406
+ }
44407
+ }
44120
44408
  if (sinkType !== void 0 && constProp.sanitizedVarUncovered) {
44121
44409
  const unc = constProp.sanitizedVarUncovered;
44122
44410
  if (step.variable.includes(":")) {
@@ -44124,7 +44412,9 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44124
44412
  } else {
44125
44413
  const method2 = methodAtLine(graph, step.line);
44126
44414
  if (method2) {
44127
- if (unc.get(`${method2}:${step.variable}`)?.has(sinkType)) return false;
44415
+ if (unc.get(fpLookup)?.has(sinkType)) {
44416
+ return false;
44417
+ }
44128
44418
  } else if (unc.get(step.variable)?.has(sinkType)) {
44129
44419
  return false;
44130
44420
  }
@@ -44162,7 +44452,7 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
44162
44452
  }
44163
44453
  if (!d) return true;
44164
44454
  if (d.line === sourceLine && sources.some((s) => s.line === sourceLine && !CONTAINER_READ_SOURCES.has(s.type))) {
44165
- if (step.line === sinkLine && !constProp.tainted.has(scopedVar)) {
44455
+ if (step.line === sinkLine && !constProp.tainted.has(fpLookup)) {
44166
44456
  for (let ln = sourceLine; ln <= sinkLine; ln++) {
44167
44457
  const calleeMatch = (lines[ln - 1] ?? "").match(/:=\s*(\w+)\s*\(/);
44168
44458
  if (calleeMatch && constProp.constantReturningMethods?.has(calleeMatch[1])) {
@@ -44341,7 +44631,15 @@ var TaintPropagationPass = class {
44341
44631
  for (const f of arrayFlows) {
44342
44632
  pushIfNew(f);
44343
44633
  }
44344
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
44634
+ const collectionFlows = detectCollectionFlows(
44635
+ calls,
44636
+ sources,
44637
+ sinks,
44638
+ constProp,
44639
+ ctx.code,
44640
+ types,
44641
+ graph
44642
+ ) ?? [];
44345
44643
  for (const f of collectionFlows) {
44346
44644
  if (flowKeys.has(flowKey(f))) continue;
44347
44645
  const flowForCheck = {
@@ -44395,6 +44693,17 @@ var TaintPropagationPass = class {
44395
44693
  pushIfNew(f);
44396
44694
  }
44397
44695
  }
44696
+ if ((ctx.language === "javascript" || ctx.language === "typescript") && typeof ctx.code === "string") {
44697
+ const jsModuleFlows = detectJsModuleScopeFlows(
44698
+ ctx.code,
44699
+ sources,
44700
+ sinks,
44701
+ constProp.unreachableLines
44702
+ ) ?? [];
44703
+ for (const f of jsModuleFlows) {
44704
+ pushIfNew(f);
44705
+ }
44706
+ }
44398
44707
  const sanitizedNames = constProp.sanitizedVars;
44399
44708
  const sanitizedUncovered = constProp.sanitizedVarUncovered;
44400
44709
  let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
@@ -44808,6 +45117,85 @@ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLin
44808
45117
  }
44809
45118
  return flows;
44810
45119
  }
45120
+ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
45121
+ const flows = [];
45122
+ if (!code || sinks.length === 0) return flows;
45123
+ const lines = code.split("\n");
45124
+ const moduleVars = /* @__PURE__ */ new Set();
45125
+ for (const line of lines) {
45126
+ const stripped = line.replace(/\s+$/, "");
45127
+ if (!stripped || /^[ \t]/.test(line)) continue;
45128
+ const m = stripped.match(/^(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
45129
+ if (m) moduleVars.add(m[1]);
45130
+ }
45131
+ if (moduleVars.size === 0) return flows;
45132
+ const sourceVarNames = /* @__PURE__ */ new Set();
45133
+ for (const s of sources) {
45134
+ if (typeof s.variable === "string" && s.variable.length > 0) {
45135
+ sourceVarNames.add(s.variable);
45136
+ }
45137
+ }
45138
+ const JS_HTTP_SOURCE_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies|get)\b|\breq\.(?:param|query|body)\(/;
45139
+ const writes = [];
45140
+ for (let i2 = 0; i2 < lines.length; i2++) {
45141
+ const line = lines[i2];
45142
+ if (unreachableLines.has(i2 + 1)) continue;
45143
+ for (const lhs of moduleVars) {
45144
+ const declRe = new RegExp(`\\b(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
45145
+ if (declRe.test(line)) continue;
45146
+ const m = line.match(
45147
+ new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*([^;]+)`)
45148
+ );
45149
+ if (!m) continue;
45150
+ const rhs = m[1];
45151
+ let taintedBy = null;
45152
+ const onLine = sources.filter((s) => s.line === i2 + 1);
45153
+ if (onLine.length > 0) {
45154
+ taintedBy = { line: onLine[0].line, type: onLine[0].type };
45155
+ } else if (JS_HTTP_SOURCE_RE.test(rhs)) {
45156
+ taintedBy = { line: i2 + 1, type: "http_body" };
45157
+ } else {
45158
+ for (const v of sourceVarNames) {
45159
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
45160
+ if (re.test(rhs)) {
45161
+ const src = sources.find((s) => s.variable === v);
45162
+ if (src) {
45163
+ taintedBy = { line: src.line, type: src.type };
45164
+ break;
45165
+ }
45166
+ }
45167
+ }
45168
+ }
45169
+ if (!taintedBy) continue;
45170
+ writes.push({ varName: lhs, line: i2 + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
45171
+ }
45172
+ }
45173
+ if (writes.length === 0) return flows;
45174
+ for (const sink of sinks) {
45175
+ if (unreachableLines.has(sink.line)) continue;
45176
+ const sinkLineText = lines[sink.line - 1] ?? "";
45177
+ if (!sinkLineText) continue;
45178
+ for (const w of writes) {
45179
+ if (w.line >= sink.line) continue;
45180
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
45181
+ if (!re.test(sinkLineText)) continue;
45182
+ flows.push({
45183
+ source_line: w.sourceLine,
45184
+ sink_line: sink.line,
45185
+ source_type: w.sourceType,
45186
+ sink_type: sink.type,
45187
+ path: [
45188
+ { variable: w.varName, line: w.sourceLine, type: "source" },
45189
+ { variable: w.varName, line: w.line, type: "assignment" },
45190
+ { variable: w.varName, line: sink.line, type: "sink" }
45191
+ ],
45192
+ confidence: 0.9,
45193
+ sanitized: false
45194
+ });
45195
+ }
45196
+ }
45197
+ return flows;
45198
+ }
44811
45199
  function methodHasLocalDecl(code, methodName, varName, atLine) {
44812
45200
  if (!code || !methodName) return false;
44813
45201
  const esc = methodName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -44875,7 +45263,9 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
44875
45263
  }
44876
45264
  return true;
44877
45265
  }
44878
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
45266
+ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
45267
+ const taintedVars = constProp.tainted;
45268
+ const unreachableLines = constProp.unreachableLines;
44879
45269
  const flows = [];
44880
45270
  const fieldNames = /* @__PURE__ */ new Set();
44881
45271
  for (const t of types ?? []) for (const f of t.fields ?? []) fieldNames.add(f.name);
@@ -44895,7 +45285,18 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
44895
45285
  }
44896
45286
  if (arg.variable) {
44897
45287
  const varName = arg.variable;
44898
- const scopedName = call.in_method ? `${call.in_method}:${varName}` : varName;
45288
+ let scopedName = varName;
45289
+ if (call.in_method) {
45290
+ const blockLine = graph ? defLineForUse(graph, varName, call.location.line) : call.location.line;
45291
+ scopedName = constPropFlowLookup(
45292
+ "java",
45293
+ call.in_method,
45294
+ varName,
45295
+ blockLine,
45296
+ constProp,
45297
+ true
45298
+ );
45299
+ }
44899
45300
  const expr = arg.expression ?? "";
44900
45301
  const explicitField = isFieldUseExpression(
44901
45302
  expr,