circle-ir 3.176.0 → 3.178.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 (43) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +75 -0
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/constant-propagation/propagator.d.ts +1 -2
  5. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  6. package/dist/analysis/constant-propagation/propagator.js +28 -30
  7. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  8. package/dist/analysis/dependency-versions.d.ts +102 -0
  9. package/dist/analysis/dependency-versions.d.ts.map +1 -0
  10. package/dist/analysis/dependency-versions.js +153 -0
  11. package/dist/analysis/dependency-versions.js.map +1 -0
  12. package/dist/analysis/dfg-walk.d.ts.map +1 -1
  13. package/dist/analysis/dfg-walk.js +28 -1
  14. package/dist/analysis/dfg-walk.js.map +1 -1
  15. package/dist/analysis/note-coalescer.d.ts +46 -0
  16. package/dist/analysis/note-coalescer.d.ts.map +1 -0
  17. package/dist/analysis/note-coalescer.js +106 -0
  18. package/dist/analysis/note-coalescer.js.map +1 -0
  19. package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts +58 -0
  20. package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts.map +1 -0
  21. package/dist/analysis/passes/deserialization-safety-gate-pass.js +122 -0
  22. package/dist/analysis/passes/deserialization-safety-gate-pass.js.map +1 -0
  23. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  24. package/dist/analysis/passes/sink-filter-pass.js +33 -2
  25. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  26. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  27. package/dist/analysis/taint-matcher.js +55 -33
  28. package/dist/analysis/taint-matcher.js.map +1 -1
  29. package/dist/analyzer.d.ts +34 -0
  30. package/dist/analyzer.d.ts.map +1 -1
  31. package/dist/analyzer.js +21 -1
  32. package/dist/analyzer.js.map +1 -1
  33. package/dist/browser/circle-ir.js +351 -47
  34. package/dist/core/circle-ir-core.cjs +197 -45
  35. package/dist/core/circle-ir-core.js +197 -45
  36. package/dist/core/extractors/calls.js +131 -1
  37. package/dist/core/extractors/calls.js.map +1 -1
  38. package/dist/core/extractors/cfg.d.ts.map +1 -1
  39. package/dist/core/extractors/cfg.js +17 -9
  40. package/dist/core/extractors/cfg.js.map +1 -1
  41. package/dist/types/index.d.ts +24 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/package.json +1 -1
@@ -7349,8 +7349,16 @@ function extractGoCallInfo(node) {
7349
7349
  if (funcNode.type === "selector_expression") {
7350
7350
  const operand = funcNode.childForFieldName("operand");
7351
7351
  const field = funcNode.childForFieldName("field");
7352
- receiver = operand ? getNodeText(operand) : null;
7353
7352
  methodName = field ? getNodeText(field) : getNodeText(funcNode);
7353
+ if (operand) {
7354
+ const opText = getNodeText(operand);
7355
+ if (operand.type === "identifier") {
7356
+ const resolved = resolveGoLocalReceiverType(opText, node);
7357
+ receiver = resolved !== null ? resolved : opText;
7358
+ } else {
7359
+ receiver = opText;
7360
+ }
7361
+ }
7354
7362
  } else if (funcNode.type === "identifier") {
7355
7363
  methodName = getNodeText(funcNode);
7356
7364
  } else {
@@ -7428,6 +7436,80 @@ function findGoEnclosingFunction(node) {
7428
7436
  }
7429
7437
  return null;
7430
7438
  }
7439
+ function resolveGoLocalReceiverType(operandName, callNode) {
7440
+ let cur = callNode.parent;
7441
+ while (cur) {
7442
+ if (cur.type === "method_declaration") {
7443
+ const receiver = cur.childForFieldName("receiver");
7444
+ if (receiver) {
7445
+ const t = extractGoParamTypeForName(receiver, operandName);
7446
+ if (t !== null) return t;
7447
+ }
7448
+ const params = cur.childForFieldName("parameters");
7449
+ if (params) {
7450
+ const t = extractGoParamTypeForName(params, operandName);
7451
+ if (t !== null) return t;
7452
+ }
7453
+ return null;
7454
+ }
7455
+ if (cur.type === "function_declaration" || cur.type === "func_literal") {
7456
+ const params = cur.childForFieldName("parameters");
7457
+ if (params) {
7458
+ const t = extractGoParamTypeForName(params, operandName);
7459
+ if (t !== null) return t;
7460
+ }
7461
+ return null;
7462
+ }
7463
+ cur = cur.parent;
7464
+ }
7465
+ return null;
7466
+ }
7467
+ function extractGoParamTypeForName(list, operandName) {
7468
+ for (let i2 = 0; i2 < list.namedChildCount; i2++) {
7469
+ const child = list.namedChild(i2);
7470
+ if (!child || child.type !== "parameter_declaration") continue;
7471
+ const typeNode = child.childForFieldName("type");
7472
+ if (!typeNode) continue;
7473
+ let matched = false;
7474
+ for (let j = 0; j < child.namedChildCount; j++) {
7475
+ const c = child.namedChild(j);
7476
+ if (!c) continue;
7477
+ if (c.type === "identifier" && getNodeText(c) === operandName) {
7478
+ matched = true;
7479
+ break;
7480
+ }
7481
+ if (c.type === "identifier_list") {
7482
+ for (let k = 0; k < c.namedChildCount; k++) {
7483
+ const id = c.namedChild(k);
7484
+ if (id && id.type === "identifier" && getNodeText(id) === operandName) {
7485
+ matched = true;
7486
+ break;
7487
+ }
7488
+ }
7489
+ if (matched) break;
7490
+ }
7491
+ }
7492
+ if (!matched) continue;
7493
+ return extractGoTypeLastSegment(typeNode);
7494
+ }
7495
+ return null;
7496
+ }
7497
+ function extractGoTypeLastSegment(typeNode) {
7498
+ if (typeNode.type === "pointer_type") {
7499
+ const inner = typeNode.namedChild(0);
7500
+ return inner ? extractGoTypeLastSegment(inner) : null;
7501
+ }
7502
+ if (typeNode.type === "qualified_type") {
7503
+ const name2 = typeNode.childForFieldName("name");
7504
+ if (name2) return getNodeText(name2);
7505
+ const last = typeNode.namedChild(typeNode.namedChildCount - 1);
7506
+ return last ? getNodeText(last) : null;
7507
+ }
7508
+ if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
7509
+ return getNodeText(typeNode);
7510
+ }
7511
+ return null;
7512
+ }
7431
7513
 
7432
7514
  // src/core/extractors/imports.ts
7433
7515
  function detectLanguage2(tree) {
@@ -8176,10 +8258,10 @@ function buildCFG(tree, language, cache) {
8176
8258
  const allEdges = [];
8177
8259
  let blockIdCounter = 0;
8178
8260
  if (effectiveLanguage === "bash") {
8179
- return buildBashCFG(tree, blockIdCounter);
8261
+ return buildBashCFG(tree, blockIdCounter, cache);
8180
8262
  }
8181
8263
  if (effectiveLanguage === "go") {
8182
- return buildGoCFG(tree, blockIdCounter);
8264
+ return buildGoCFG(tree, blockIdCounter, cache);
8183
8265
  }
8184
8266
  if (isJavaScript) {
8185
8267
  const functions = [
@@ -8536,11 +8618,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8536
8618
  nextId: currentId
8537
8619
  };
8538
8620
  }
8539
- function buildBashCFG(tree, startId) {
8621
+ function buildBashCFG(tree, startId, cache) {
8540
8622
  const allBlocks = [];
8541
8623
  const allEdges = [];
8542
8624
  let blockIdCounter = startId;
8543
- const functions = findNodes(tree.rootNode, "function_definition");
8625
+ const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
8544
8626
  for (const func2 of functions) {
8545
8627
  const body2 = func2.childForFieldName("body");
8546
8628
  if (!body2) continue;
@@ -8657,12 +8739,12 @@ function isStatement(node, isJavaScript) {
8657
8739
  ]);
8658
8740
  return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
8659
8741
  }
8660
- function buildGoCFG(tree, blockIdCounter) {
8742
+ function buildGoCFG(tree, blockIdCounter, cache) {
8661
8743
  const allBlocks = [];
8662
8744
  const allEdges = [];
8663
8745
  const functions = [
8664
- ...findNodes(tree.rootNode, "function_declaration"),
8665
- ...findNodes(tree.rootNode, "method_declaration")
8746
+ ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
8747
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache)
8666
8748
  ];
8667
8749
  for (const func2 of functions) {
8668
8750
  const body2 = func2.childForFieldName("body");
@@ -10576,6 +10658,59 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
10576
10658
  { method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
10577
10659
  { method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
10578
10660
  ];
10661
+ var DESERIALIZATION_FRAMEWORK_SINKS = [
10662
+ // --- Python: stdlib + popular third-party --------------------------------
10663
+ // pickle: known-dangerous, any unpickle on untrusted bytes is RCE.
10664
+ { method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10665
+ { method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10666
+ // cPickle alias (Python 2 name, still around in older codebases).
10667
+ { method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10668
+ { method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10669
+ // marshal: stdlib code-object deserializer. Loading a tainted bytestring
10670
+ // as a code object followed by `exec` is arbitrary-code execution.
10671
+ { method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10672
+ { method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10673
+ // dill: pickle superset — same RCE profile.
10674
+ { method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10675
+ { method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10676
+ // jsonpickle: JSON wrapper around pickle — trusts `py/object` marker.
10677
+ { method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10678
+ // --- Go: encoding/gob + yaml.Unmarshal -----------------------------------
10679
+ // gob.NewDecoder(r).Decode(&v): tainted io.Reader → arbitrary Go values.
10680
+ { method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
10681
+ // gopkg.in/yaml.v2 + v3 top-level function; interface{} target is unsafe.
10682
+ { method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
10683
+ // --- JS/TS: node-serialize ------------------------------------------------
10684
+ // Known-dangerous — accepts embedded IIFE that runs during deserialize.
10685
+ { method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
10686
+ ];
10687
+ var NOSQL_FRAMEWORK_SINKS = [
10688
+ // --- Python: pymongo Collection ------------------------------------------
10689
+ // Every filter-taking Collection method; filter arg[0] is the query dict.
10690
+ { method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10691
+ { method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10692
+ { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10693
+ { method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
10694
+ { method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
10695
+ { method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10696
+ { method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10697
+ { method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10698
+ // --- Java: Spring Data MongoTemplate + native MongoCollection ------------
10699
+ { method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10700
+ { method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10701
+ { method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10702
+ { method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10703
+ { method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10704
+ // --- Go: go.mongodb.org/mongo-driver Collection --------------------------
10705
+ // These fire once Go local-receiver type resolution lands (see
10706
+ // taint-matcher.ts + #240 ship 2 Go receiver work). Same gate as the
10707
+ // gin/fiber Ctx sinks in ship 1.
10708
+ { method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
10709
+ { method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
10710
+ { method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
10711
+ { method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
10712
+ { method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
10713
+ ];
10579
10714
  var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
10580
10715
  // --- Python: Django cache write -----------------------------------------
10581
10716
  { method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
@@ -12259,7 +12394,12 @@ var DEFAULT_SINKS = [
12259
12394
  // keep the DEFAULT_SINKS literal within TypeScript's union-type
12260
12395
  // inference complexity limit (TS2590). See ~lines 661-752.
12261
12396
  ...OPEN_REDIRECT_FRAMEWORK_SINKS,
12262
- ...TRUST_BOUNDARY_FRAMEWORK_SINKS
12397
+ ...TRUST_BOUNDARY_FRAMEWORK_SINKS,
12398
+ // cognium-dev #240 ship 2 — extended framework sinks for
12399
+ // deserialization (CWE-502) and nosql_injection (CWE-943). Same
12400
+ // pattern: constants defined near the ship-1 blocks and spread here.
12401
+ ...DESERIALIZATION_FRAMEWORK_SINKS,
12402
+ ...NOSQL_FRAMEWORK_SINKS
12263
12403
  ];
12264
12404
  var DEFAULT_SANITIZERS = [
12265
12405
  // SQL Injection - proper parameter binding sanitizes input
@@ -12779,11 +12919,11 @@ function attachSourceLineCode(sources, sinks, code) {
12779
12919
  }
12780
12920
  function findSources(calls, types, patterns, sourceLines, language) {
12781
12921
  const sources = [];
12922
+ const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
12923
+ (p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
12924
+ );
12782
12925
  for (const call of calls) {
12783
- for (const pattern of patterns) {
12784
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12785
- continue;
12786
- }
12926
+ for (const pattern of patternsForLanguage) {
12787
12927
  if (matchesSourcePattern(call, pattern)) {
12788
12928
  sources.push({
12789
12929
  type: pattern.type,
@@ -12799,11 +12939,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12799
12939
  for (const type of types) {
12800
12940
  for (const method of type.methods) {
12801
12941
  for (const param of method.parameters) {
12802
- for (const pattern of patterns) {
12942
+ for (const pattern of patternsForLanguage) {
12803
12943
  if (pattern.annotation && pattern.param_tainted) {
12804
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12805
- continue;
12806
- }
12807
12944
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
12808
12945
  const paramLine = param.line ?? method.start_line;
12809
12946
  sources.push({
@@ -12822,11 +12959,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12822
12959
  }
12823
12960
  for (const type of types) {
12824
12961
  for (const method of type.methods) {
12825
- for (const pattern of patterns) {
12962
+ for (const pattern of patternsForLanguage) {
12826
12963
  if (!pattern.method_annotation) continue;
12827
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12828
- continue;
12829
- }
12830
12964
  if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
12831
12965
  for (const param of method.parameters) {
12832
12966
  const paramLine = param.line ?? method.start_line;
@@ -13448,9 +13582,12 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
13448
13582
  return false;
13449
13583
  }
13450
13584
  function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
13585
+ const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
13586
+ (p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
13587
+ );
13451
13588
  const sinkMap = /* @__PURE__ */ new Map();
13452
13589
  for (const call of calls) {
13453
- for (const pattern of patterns) {
13590
+ for (const pattern of patternsForLanguage) {
13454
13591
  if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
13455
13592
  if (isParameterizedQueryCall(call, pattern)) {
13456
13593
  continue;
@@ -13868,7 +14005,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
13868
14005
  }
13869
14006
  return false;
13870
14007
  }
14008
+ var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
14009
+ var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
13871
14010
  function receiverMightBeClass(receiver, className) {
14011
+ const key = receiver + "\0" + className;
14012
+ const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
14013
+ if (cached !== void 0) return cached;
14014
+ const result = receiverMightBeClassImpl(receiver, className);
14015
+ if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
14016
+ RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
14017
+ }
14018
+ RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
14019
+ return result;
14020
+ }
14021
+ function receiverMightBeClassImpl(receiver, className) {
13872
14022
  if (className.startsWith("*") && className.length > 1) {
13873
14023
  const suffix = className.slice(1).toLowerCase();
13874
14024
  let simpleReceiver = receiver;
@@ -14572,8 +14722,15 @@ var CodeGraph = class {
14572
14722
  };
14573
14723
 
14574
14724
  // src/analysis/dfg-walk.ts
14725
+ var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
14575
14726
  function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
14576
14727
  const maxHops = options.maxHops ?? 32;
14728
+ let perFile = walkBackwardDefsMemo.get(chainsByToDef);
14729
+ if (perFile !== void 0) {
14730
+ const key = `${startDefId}|${maxHops}`;
14731
+ const hit = perFile.get(key);
14732
+ if (hit !== void 0) return hit;
14733
+ }
14577
14734
  const visited = /* @__PURE__ */ new Set();
14578
14735
  const lines = /* @__PURE__ */ new Set();
14579
14736
  let hopCapReached = false;
@@ -14605,7 +14762,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
14605
14762
  queue.push(fromId);
14606
14763
  }
14607
14764
  }
14608
- return { visited, lines, hopCapReached };
14765
+ const result = { visited, lines, hopCapReached };
14766
+ if (perFile === void 0) {
14767
+ perFile = /* @__PURE__ */ new Map();
14768
+ walkBackwardDefsMemo.set(chainsByToDef, perFile);
14769
+ }
14770
+ perFile.set(`${startDefId}|${maxHops}`, result);
14771
+ return result;
14609
14772
  }
14610
14773
 
14611
14774
  // src/analysis/sanitizer-index.ts
@@ -15588,7 +15751,7 @@ var ConstantPropagator = class _ConstantPropagator {
15588
15751
  this.constructorParamPositions.clear();
15589
15752
  this.safePatternFieldsCache = null;
15590
15753
  this.isTaintedExpressionCache = null;
15591
- this.collectClassFields(tree.rootNode);
15754
+ const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
15592
15755
  for (const methodName of sanitizerMethods) {
15593
15756
  this.methodReturnsSanitized.add(methodName);
15594
15757
  }
@@ -15596,7 +15759,7 @@ var ConstantPropagator = class _ConstantPropagator {
15596
15759
  this.source,
15597
15760
  (name2) => this.lookupSymbol(name2)
15598
15761
  );
15599
- this.analyzeMethodReturns(tree.rootNode);
15762
+ this.analyzeMethodReturns(prePassMethods);
15600
15763
  this.seedPythonModuleConstants(tree.rootNode);
15601
15764
  this.visit(tree.rootNode);
15602
15765
  this.refineTaintFromConstants();
@@ -15666,8 +15829,7 @@ var ConstantPropagator = class _ConstantPropagator {
15666
15829
  /**
15667
15830
  * Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
15668
15831
  */
15669
- analyzeMethodReturns(root) {
15670
- const methods = this.findAllMethods(root);
15832
+ analyzeMethodReturns(methods) {
15671
15833
  for (const method of methods) {
15672
15834
  const methodName = this.getMethodName(method);
15673
15835
  if (!methodName) continue;
@@ -15931,11 +16093,15 @@ var ConstantPropagator = class _ConstantPropagator {
15931
16093
  * Collect all class field names (instance/static variables declared at class level).
15932
16094
  * These are variables declared directly in the class body, not inside methods.
15933
16095
  */
15934
- collectClassFields(root) {
16096
+ collectClassFieldsAndMethods(root) {
16097
+ const methods = [];
15935
16098
  const stack = [root];
15936
16099
  while (stack.length > 0) {
15937
16100
  const n = stack.pop();
15938
16101
  if (!n) continue;
16102
+ if (n.type === "method_declaration" || n.type === "function_declaration") {
16103
+ methods.push(n);
16104
+ }
15939
16105
  if (n.type === "class_body") {
15940
16106
  for (const child of n.children) {
15941
16107
  if (child.type === "field_declaration") {
@@ -15949,14 +16115,15 @@ var ConstantPropagator = class _ConstantPropagator {
15949
16115
  }
15950
16116
  }
15951
16117
  }
15952
- stack.push(child);
16118
+ if (child) stack.push(child);
15953
16119
  }
15954
16120
  continue;
15955
16121
  }
15956
16122
  for (const child of n.children) {
15957
- stack.push(child);
16123
+ if (child) stack.push(child);
15958
16124
  }
15959
16125
  }
16126
+ return methods;
15960
16127
  }
15961
16128
  /**
15962
16129
  * Sprint 9 #55 — seed the symbol table with Python module-level constant
@@ -16114,21 +16281,6 @@ var ConstantPropagator = class _ConstantPropagator {
16114
16281
  this.symbols.set(name2, value);
16115
16282
  }
16116
16283
  }
16117
- findAllMethods(node) {
16118
- const methods = [];
16119
- const stack = [node];
16120
- while (stack.length > 0) {
16121
- const n = stack.pop();
16122
- if (!n) continue;
16123
- if (n.type === "method_declaration" || n.type === "function_declaration") {
16124
- methods.push(n);
16125
- }
16126
- for (const child of n.children) {
16127
- if (child) stack.push(child);
16128
- }
16129
- }
16130
- return methods;
16131
- }
16132
16284
  getMethodName(method) {
16133
16285
  const nameNode = method.childForFieldName("name");
16134
16286
  if (nameNode) {
@@ -2113,8 +2113,27 @@ function extractGoCallInfo(node) {
2113
2113
  // pkg.Function() or obj.Method()
2114
2114
  const operand = funcNode.childForFieldName('operand');
2115
2115
  const field = funcNode.childForFieldName('field');
2116
- receiver = operand ? getNodeText(operand) : null;
2117
2116
  methodName = field ? getNodeText(field) : getNodeText(funcNode);
2117
+ // cognium-dev #240 ship 2 — Go local-receiver type resolution.
2118
+ // When the operand is a bare identifier and matches a method
2119
+ // receiver or function parameter in the enclosing declaration,
2120
+ // rewrite receiver to the type's last identifier segment. This
2121
+ // unblocks sinks defined with `class: 'Context'` (gin/echo/fiber),
2122
+ // `class: 'Ctx'` (fiber), and `class: 'Collection'` (mongo-driver)
2123
+ // when the source shape is `c.Redirect(...)` / `c.SetCookie(...)` /
2124
+ // `col.Find(...)`. Package-qualified calls like `fmt.Sprintf(...)`
2125
+ // fall through to the original operand text (fmt is not a
2126
+ // parameter / receiver, resolver returns null).
2127
+ if (operand) {
2128
+ const opText = getNodeText(operand);
2129
+ if (operand.type === 'identifier') {
2130
+ const resolved = resolveGoLocalReceiverType(opText, node);
2131
+ receiver = resolved !== null ? resolved : opText;
2132
+ }
2133
+ else {
2134
+ receiver = opText;
2135
+ }
2136
+ }
2118
2137
  }
2119
2138
  else if (funcNode.type === 'identifier') {
2120
2139
  // Plain function call: funcName()
@@ -2213,4 +2232,115 @@ function findGoEnclosingFunction(node) {
2213
2232
  }
2214
2233
  return null;
2215
2234
  }
2235
+ /**
2236
+ * Resolve a Go local-variable operand (bare identifier receiver in a
2237
+ * `selector_expression`) to its declared type's last identifier segment.
2238
+ *
2239
+ * Handles three enclosing-declaration shapes:
2240
+ * 1. Method receiver: `func (c *gin.Context) H() { c.Redirect(...) }`
2241
+ * 2. Function param: `func H(c *gin.Context, url string) { c.Redirect(...) }`
2242
+ * 3. Multi-name param: `func H(a, b *Foo) { a.Bar() }` (identifier_list)
2243
+ *
2244
+ * Returns `null` when the operand does not match any receiver or
2245
+ * parameter in the nearest enclosing `method_declaration` /
2246
+ * `function_declaration` / `func_literal` — the caller then falls back
2247
+ * to the operand text so package-qualified calls like `fmt.Sprintf(...)`
2248
+ * continue to work.
2249
+ *
2250
+ * cognium-dev #240 ship 2.
2251
+ */
2252
+ function resolveGoLocalReceiverType(operandName, callNode) {
2253
+ let cur = callNode.parent;
2254
+ while (cur) {
2255
+ if (cur.type === 'method_declaration') {
2256
+ const receiver = cur.childForFieldName('receiver');
2257
+ if (receiver) {
2258
+ const t = extractGoParamTypeForName(receiver, operandName);
2259
+ if (t !== null)
2260
+ return t;
2261
+ }
2262
+ const params = cur.childForFieldName('parameters');
2263
+ if (params) {
2264
+ const t = extractGoParamTypeForName(params, operandName);
2265
+ if (t !== null)
2266
+ return t;
2267
+ }
2268
+ return null;
2269
+ }
2270
+ if (cur.type === 'function_declaration' || cur.type === 'func_literal') {
2271
+ const params = cur.childForFieldName('parameters');
2272
+ if (params) {
2273
+ const t = extractGoParamTypeForName(params, operandName);
2274
+ if (t !== null)
2275
+ return t;
2276
+ }
2277
+ return null;
2278
+ }
2279
+ cur = cur.parent;
2280
+ }
2281
+ return null;
2282
+ }
2283
+ /**
2284
+ * Scan a Go `parameter_list` for a `parameter_declaration` naming
2285
+ * `operandName` (directly or inside an `identifier_list`) and return
2286
+ * its type's last identifier segment. Returns `null` on miss.
2287
+ */
2288
+ function extractGoParamTypeForName(list, operandName) {
2289
+ for (let i = 0; i < list.namedChildCount; i++) {
2290
+ const child = list.namedChild(i);
2291
+ if (!child || child.type !== 'parameter_declaration')
2292
+ continue;
2293
+ const typeNode = child.childForFieldName('type');
2294
+ if (!typeNode)
2295
+ continue;
2296
+ let matched = false;
2297
+ for (let j = 0; j < child.namedChildCount; j++) {
2298
+ const c = child.namedChild(j);
2299
+ if (!c)
2300
+ continue;
2301
+ if (c.type === 'identifier' && getNodeText(c) === operandName) {
2302
+ matched = true;
2303
+ break;
2304
+ }
2305
+ if (c.type === 'identifier_list') {
2306
+ for (let k = 0; k < c.namedChildCount; k++) {
2307
+ const id = c.namedChild(k);
2308
+ if (id && id.type === 'identifier' && getNodeText(id) === operandName) {
2309
+ matched = true;
2310
+ break;
2311
+ }
2312
+ }
2313
+ if (matched)
2314
+ break;
2315
+ }
2316
+ }
2317
+ if (!matched)
2318
+ continue;
2319
+ return extractGoTypeLastSegment(typeNode);
2320
+ }
2321
+ return null;
2322
+ }
2323
+ /**
2324
+ * Return the last identifier segment of a Go type expression:
2325
+ * `*gin.Context` → `"Context"`; `gin.Context` → `"Context"`;
2326
+ * `*Context` → `"Context"`; `Context` → `"Context"`;
2327
+ * `[]byte` → `null` (structured types not yet handled).
2328
+ */
2329
+ function extractGoTypeLastSegment(typeNode) {
2330
+ if (typeNode.type === 'pointer_type') {
2331
+ const inner = typeNode.namedChild(0);
2332
+ return inner ? extractGoTypeLastSegment(inner) : null;
2333
+ }
2334
+ if (typeNode.type === 'qualified_type') {
2335
+ const name = typeNode.childForFieldName('name');
2336
+ if (name)
2337
+ return getNodeText(name);
2338
+ const last = typeNode.namedChild(typeNode.namedChildCount - 1);
2339
+ return last ? getNodeText(last) : null;
2340
+ }
2341
+ if (typeNode.type === 'type_identifier' || typeNode.type === 'identifier') {
2342
+ return getNodeText(typeNode);
2343
+ }
2344
+ return null;
2345
+ }
2216
2346
  //# sourceMappingURL=calls.js.map