circle-ir 4.7.2 → 4.8.2

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.
@@ -6000,13 +6000,21 @@ function extractCSharpCalls(tree, cache) {
6000
6000
  const left = asn.childForFieldName("left");
6001
6001
  if (left?.type !== "member_access_expression") continue;
6002
6002
  const nameNode = left.childForFieldName("name");
6003
- if (!nameNode || getNodeText(nameNode) !== "CommandText") continue;
6003
+ if (!nameNode) continue;
6004
+ const propName = getNodeText(nameNode);
6005
+ const exprNode = left.childForFieldName("expression");
6006
+ if (propName === "Filter") {
6007
+ const recv = exprNode ? getNodeText(exprNode) : null;
6008
+ const recvType = recv ? typeMap.get(recv) : void 0;
6009
+ if (recvType !== "DirectorySearcher") continue;
6010
+ } else if (propName !== "CommandText") {
6011
+ continue;
6012
+ }
6004
6013
  const right = asn.childForFieldName("right");
6005
6014
  if (!right) continue;
6006
6015
  const rhsText = getNodeText(right);
6007
- const exprNode = left.childForFieldName("expression");
6008
6016
  calls.push({
6009
- method_name: "CommandText",
6017
+ method_name: propName,
6010
6018
  receiver: exprNode ? getNodeText(exprNode) : null,
6011
6019
  receiver_type: null,
6012
6020
  receiver_type_fqn: null,
@@ -8572,6 +8580,9 @@ function buildCFG(tree, language, cache) {
8572
8580
  if (effectiveLanguage === "go") {
8573
8581
  return buildGoCFG(tree, blockIdCounter, cache);
8574
8582
  }
8583
+ if (effectiveLanguage === "csharp") {
8584
+ return buildCSharpCFG(tree, blockIdCounter, cache);
8585
+ }
8575
8586
  if (isJavaScript) {
8576
8587
  const functions = [
8577
8588
  ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
@@ -8584,7 +8595,7 @@ function buildCFG(tree, language, cache) {
8584
8595
  const body2 = func2.childForFieldName("body");
8585
8596
  if (!body2) continue;
8586
8597
  if (body2.type === "statement_block") {
8587
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, true);
8598
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
8588
8599
  allBlocks.push(...blocks);
8589
8600
  allEdges.push(...edges);
8590
8601
  blockIdCounter = nextId;
@@ -8606,7 +8617,7 @@ function buildCFG(tree, language, cache) {
8606
8617
  for (const method of methods) {
8607
8618
  const body2 = method.childForFieldName("body");
8608
8619
  if (!body2) continue;
8609
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
8620
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
8610
8621
  allBlocks.push(...blocks);
8611
8622
  allEdges.push(...edges);
8612
8623
  blockIdCounter = nextId;
@@ -8614,7 +8625,28 @@ function buildCFG(tree, language, cache) {
8614
8625
  }
8615
8626
  return { blocks: allBlocks, edges: allEdges };
8616
8627
  }
8617
- function buildMethodCFG(body2, startId, isJavaScript) {
8628
+ function buildCSharpCFG(tree, blockIdCounter, cache) {
8629
+ const allBlocks = [];
8630
+ const allEdges = [];
8631
+ const containers = [
8632
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
8633
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
8634
+ ...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
8635
+ ...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
8636
+ ...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
8637
+ ...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
8638
+ ];
8639
+ for (const container of containers) {
8640
+ const body2 = container.childForFieldName("body");
8641
+ if (!body2 || body2.type !== "block") continue;
8642
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
8643
+ allBlocks.push(...blocks);
8644
+ allEdges.push(...edges);
8645
+ blockIdCounter = nextId;
8646
+ }
8647
+ return { blocks: allBlocks, edges: allEdges };
8648
+ }
8649
+ function buildMethodCFG(body2, startId, dialect) {
8618
8650
  const blocks = [];
8619
8651
  const edges = [];
8620
8652
  let currentId = startId;
@@ -8625,7 +8657,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
8625
8657
  end_line: body2.startPosition.row + 1
8626
8658
  };
8627
8659
  blocks.push(entryBlock);
8628
- const result = processStatements(body2, currentId, blocks, edges, isJavaScript);
8660
+ const result = processStatements(body2, currentId, blocks, edges, dialect);
8629
8661
  currentId = result.nextId;
8630
8662
  if (result.entryId !== -1) {
8631
8663
  edges.push({
@@ -8657,15 +8689,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
8657
8689
  }
8658
8690
  return { blocks, edges, nextId: currentId };
8659
8691
  }
8660
- function processStatements(container, startId, blocks, edges, isJavaScript) {
8692
+ function processStatements(container, startId, blocks, edges, dialect) {
8661
8693
  let currentId = startId;
8662
8694
  let firstBlockId = -1;
8663
8695
  let lastExitIds = [];
8664
8696
  for (let i2 = 0; i2 < container.childCount; i2++) {
8665
8697
  const stmt = container.child(i2);
8666
8698
  if (!stmt) continue;
8667
- if (!isStatement(stmt, isJavaScript)) continue;
8668
- const result = processStatement(stmt, currentId, blocks, edges, isJavaScript);
8699
+ if (!isStatement(stmt, dialect)) continue;
8700
+ const result = processStatement(stmt, currentId, blocks, edges, dialect);
8669
8701
  currentId = result.nextId;
8670
8702
  if (firstBlockId === -1) {
8671
8703
  firstBlockId = result.entryId;
@@ -8686,31 +8718,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
8686
8718
  nextId: currentId
8687
8719
  };
8688
8720
  }
8689
- function processStatement(stmt, startId, blocks, edges, isJavaScript) {
8721
+ function processStatement(stmt, startId, blocks, edges, dialect) {
8690
8722
  switch (stmt.type) {
8691
8723
  case "if_statement":
8692
- return processIfStatement(stmt, startId, blocks, edges, isJavaScript);
8724
+ return processIfStatement(stmt, startId, blocks, edges, dialect);
8693
8725
  case "for_statement":
8694
8726
  case "enhanced_for_statement":
8695
8727
  case "for_in_statement":
8696
8728
  case "for_of_statement":
8697
- return processForStatement(stmt, startId, blocks, edges, isJavaScript);
8729
+ case "foreach_statement":
8730
+ return processForStatement(stmt, startId, blocks, edges, dialect);
8698
8731
  case "while_statement":
8699
- return processWhileStatement(stmt, startId, blocks, edges, isJavaScript);
8732
+ return processWhileStatement(stmt, startId, blocks, edges, dialect);
8700
8733
  case "do_statement":
8701
- return processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript);
8734
+ return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
8702
8735
  case "try_statement":
8703
- return processTryStatement(stmt, startId, blocks, edges, isJavaScript);
8736
+ return processTryStatement(stmt, startId, blocks, edges, dialect);
8704
8737
  case "switch_expression":
8705
8738
  case "switch_statement":
8706
- return processSwitchStatement(stmt, startId, blocks, edges, isJavaScript);
8739
+ return processSwitchStatement(stmt, startId, blocks, edges, dialect);
8740
+ // C# scoped blocks add no branching — flow through the inner block so its
8741
+ // nested statements are still captured.
8742
+ case "using_statement":
8743
+ case "lock_statement":
8744
+ case "checked_statement":
8745
+ case "unsafe_statement": {
8746
+ const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
8747
+ if (inner) return processStatement(inner, startId, blocks, edges, dialect);
8748
+ return processSimpleStatement(stmt, startId, blocks);
8749
+ }
8707
8750
  case "block":
8708
8751
  case "statement_block":
8709
- return processStatements(stmt, startId, blocks, edges, isJavaScript);
8752
+ return processStatements(stmt, startId, blocks, edges, dialect);
8710
8753
  default:
8711
8754
  return processSimpleStatement(stmt, startId, blocks);
8712
8755
  }
8713
8756
  }
8757
+ function lastBlockChild(node) {
8758
+ for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
8759
+ const c = node.child(i2);
8760
+ if (c && c.type === "block") return c;
8761
+ }
8762
+ return null;
8763
+ }
8714
8764
  function processSimpleStatement(stmt, startId, blocks) {
8715
8765
  const block = {
8716
8766
  id: startId,
@@ -8725,7 +8775,7 @@ function processSimpleStatement(stmt, startId, blocks) {
8725
8775
  nextId: startId + 1
8726
8776
  };
8727
8777
  }
8728
- function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8778
+ function processIfStatement(stmt, startId, blocks, edges, dialect) {
8729
8779
  let currentId = startId;
8730
8780
  const condBlock = {
8731
8781
  id: currentId++,
@@ -8737,7 +8787,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8737
8787
  const exitIds = [];
8738
8788
  const consequence = stmt.childForFieldName("consequence");
8739
8789
  if (consequence) {
8740
- const thenResult = processStatement(consequence, currentId, blocks, edges, isJavaScript);
8790
+ const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
8741
8791
  currentId = thenResult.nextId;
8742
8792
  edges.push({
8743
8793
  from: condBlock.id,
@@ -8748,7 +8798,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8748
8798
  }
8749
8799
  const alternative = stmt.childForFieldName("alternative");
8750
8800
  if (alternative) {
8751
- const elseResult = processStatement(alternative, currentId, blocks, edges, isJavaScript);
8801
+ const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
8752
8802
  currentId = elseResult.nextId;
8753
8803
  edges.push({
8754
8804
  from: condBlock.id,
@@ -8765,7 +8815,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8765
8815
  nextId: currentId
8766
8816
  };
8767
8817
  }
8768
- function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
8818
+ function processForStatement(stmt, startId, blocks, edges, dialect) {
8769
8819
  let currentId = startId;
8770
8820
  const loopBlock = {
8771
8821
  id: currentId++,
@@ -8776,7 +8826,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
8776
8826
  blocks.push(loopBlock);
8777
8827
  const body2 = stmt.childForFieldName("body");
8778
8828
  if (body2) {
8779
- const bodyResult = processStatement(body2, currentId, blocks, edges, isJavaScript);
8829
+ const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
8780
8830
  currentId = bodyResult.nextId;
8781
8831
  edges.push({
8782
8832
  from: loopBlock.id,
@@ -8798,16 +8848,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
8798
8848
  nextId: currentId
8799
8849
  };
8800
8850
  }
8801
- function processWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
8802
- return processForStatement(stmt, startId, blocks, edges, isJavaScript);
8851
+ function processWhileStatement(stmt, startId, blocks, edges, dialect) {
8852
+ return processForStatement(stmt, startId, blocks, edges, dialect);
8803
8853
  }
8804
- function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
8854
+ function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
8805
8855
  let currentId = startId;
8806
8856
  const body2 = stmt.childForFieldName("body");
8807
8857
  let bodyEntryId = currentId;
8808
8858
  let bodyExitIds = [];
8809
8859
  if (body2) {
8810
- const bodyResult = processStatement(body2, currentId, blocks, edges, isJavaScript);
8860
+ const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
8811
8861
  currentId = bodyResult.nextId;
8812
8862
  bodyEntryId = bodyResult.entryId;
8813
8863
  bodyExitIds = bodyResult.exitIds;
@@ -8837,13 +8887,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
8837
8887
  nextId: currentId
8838
8888
  };
8839
8889
  }
8840
- function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8890
+ function processTryStatement(stmt, startId, blocks, edges, dialect) {
8841
8891
  let currentId = startId;
8842
8892
  const exitIds = [];
8843
8893
  const body2 = stmt.childForFieldName("body");
8844
8894
  let tryEntryId = -1;
8845
8895
  if (body2) {
8846
- const bodyResult = processStatements(body2, currentId, blocks, edges, isJavaScript);
8896
+ const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
8847
8897
  currentId = bodyResult.nextId;
8848
8898
  tryEntryId = bodyResult.entryId;
8849
8899
  exitIds.push(...bodyResult.exitIds);
@@ -8853,7 +8903,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8853
8903
  if (child?.type === "catch_clause") {
8854
8904
  const catchBody = child.childForFieldName("body");
8855
8905
  if (catchBody) {
8856
- const catchResult = processStatements(catchBody, currentId, blocks, edges, isJavaScript);
8906
+ const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
8857
8907
  currentId = catchResult.nextId;
8858
8908
  if (tryEntryId !== -1) {
8859
8909
  edges.push({
@@ -8866,9 +8916,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8866
8916
  }
8867
8917
  }
8868
8918
  }
8869
- const finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
8919
+ let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
8920
+ if (!finallyClause && dialect === "csharp") {
8921
+ for (let i2 = 0; i2 < stmt.childCount; i2++) {
8922
+ const child = stmt.child(i2);
8923
+ if (child?.type === "finally_clause") {
8924
+ finallyClause = lastBlockChild(child);
8925
+ break;
8926
+ }
8927
+ }
8928
+ }
8870
8929
  if (finallyClause) {
8871
- const finallyResult = processStatements(finallyClause, currentId, blocks, edges, isJavaScript);
8930
+ const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
8872
8931
  currentId = finallyResult.nextId;
8873
8932
  for (const exitId of exitIds) {
8874
8933
  edges.push({
@@ -8889,7 +8948,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8889
8948
  nextId: currentId
8890
8949
  };
8891
8950
  }
8892
- function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8951
+ function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
8893
8952
  let currentId = startId;
8894
8953
  const switchBlock = {
8895
8954
  id: currentId++,
@@ -8901,11 +8960,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8901
8960
  const exitIds = [];
8902
8961
  const body2 = stmt.childForFieldName("body");
8903
8962
  if (body2) {
8904
- const caseTypes = isJavaScript ? ["switch_case", "switch_default"] : ["switch_block_statement_group", "switch_rule"];
8963
+ const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
8905
8964
  for (let i2 = 0; i2 < body2.childCount; i2++) {
8906
8965
  const child = body2.child(i2);
8907
8966
  if (child && caseTypes.includes(child.type)) {
8908
- const caseResult = processStatements(child, currentId, blocks, edges, isJavaScript);
8967
+ const caseResult = processStatements(child, currentId, blocks, edges, dialect);
8909
8968
  currentId = caseResult.nextId;
8910
8969
  if (caseResult.entryId !== -1) {
8911
8970
  edges.push({
@@ -8935,7 +8994,7 @@ function buildBashCFG(tree, startId, cache) {
8935
8994
  for (const func2 of functions) {
8936
8995
  const body2 = func2.childForFieldName("body");
8937
8996
  if (!body2) continue;
8938
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
8997
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
8939
8998
  allBlocks.push(...blocks);
8940
8999
  allEdges.push(...edges);
8941
9000
  blockIdCounter = nextId;
@@ -8958,7 +9017,7 @@ function buildBashCFG(tree, startId, cache) {
8958
9017
  let lastExitIds = [];
8959
9018
  let firstBlockId = -1;
8960
9019
  for (const stmt of topLevelStatements) {
8961
- const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, false);
9020
+ const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
8962
9021
  blockIdCounter = result.nextId;
8963
9022
  if (firstBlockId === -1) {
8964
9023
  firstBlockId = result.entryId;
@@ -9002,7 +9061,8 @@ function isBashStatement(node) {
9002
9061
  ]);
9003
9062
  return bashStatementTypes.has(node.type);
9004
9063
  }
9005
- function isStatement(node, isJavaScript) {
9064
+ function isStatement(node, dialect) {
9065
+ if (dialect === "csharp") return csharpStatementTypes.has(node.type);
9006
9066
  const javaStatementTypes = /* @__PURE__ */ new Set([
9007
9067
  "local_variable_declaration",
9008
9068
  "expression_statement",
@@ -9046,8 +9106,30 @@ function isStatement(node, isJavaScript) {
9046
9106
  "export_statement",
9047
9107
  "import_statement"
9048
9108
  ]);
9049
- return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
9050
- }
9109
+ return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
9110
+ }
9111
+ var csharpStatementTypes = /* @__PURE__ */ new Set([
9112
+ "local_declaration_statement",
9113
+ "expression_statement",
9114
+ "if_statement",
9115
+ "for_statement",
9116
+ "foreach_statement",
9117
+ "while_statement",
9118
+ "do_statement",
9119
+ "try_statement",
9120
+ "switch_statement",
9121
+ "return_statement",
9122
+ "throw_statement",
9123
+ "break_statement",
9124
+ "continue_statement",
9125
+ "using_statement",
9126
+ "lock_statement",
9127
+ "checked_statement",
9128
+ "unsafe_statement",
9129
+ "yield_statement",
9130
+ "goto_statement",
9131
+ "block"
9132
+ ]);
9051
9133
  function buildGoCFG(tree, blockIdCounter, cache) {
9052
9134
  const allBlocks = [];
9053
9135
  const allEdges = [];
@@ -9058,7 +9140,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
9058
9140
  for (const func2 of functions) {
9059
9141
  const body2 = func2.childForFieldName("body");
9060
9142
  if (!body2 || body2.type !== "block") continue;
9061
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
9143
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
9062
9144
  allBlocks.push(...blocks);
9063
9145
  allEdges.push(...edges);
9064
9146
  blockIdCounter = nextId;
@@ -13789,6 +13871,11 @@ var DEFAULT_SINKS = [
13789
13871
  // ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
13790
13872
  // extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
13791
13873
  { method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13874
+ // `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
13875
+ // Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
13876
+ // resolves to a DirectorySearcher, so this classless entry never over-matches
13877
+ // other `.Filter =` assignments (DataView/BindingSource/collection filters).
13878
+ { method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13792
13879
  // ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
13793
13880
  // rides the SqlCommand receiver (CommandText set from tainted data); the
13794
13881
  // receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
@@ -13805,6 +13892,9 @@ var DEFAULT_SINKS = [
13805
13892
  // injectable — the second is the argv path where taint rides arg[1] (#276).
13806
13893
  { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13807
13894
  { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13895
+ // P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
13896
+ // always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
13897
+ { method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13808
13898
  // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13809
13899
  { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13810
13900
  { method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13833,6 +13923,9 @@ var DEFAULT_SINKS = [
13833
13923
  { method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13834
13924
  { method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13835
13925
  { method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13926
+ // ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
13927
+ // an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
13928
+ { method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13836
13929
  // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13837
13930
  { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13838
13931
  { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13871,6 +13964,14 @@ var DEFAULT_SINKS = [
13871
13964
  { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13872
13965
  { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13873
13966
  { method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13967
+ // Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
13968
+ // documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
13969
+ { method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13970
+ // `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
13971
+ // Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
13972
+ // only an argument is tainted never fires. .NET composite formatting can't corrupt
13973
+ // memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
13974
+ { method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13874
13975
  // C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
13875
13976
  // filter is the constructor argument.
13876
13977
  { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13909,6 +14010,10 @@ var DEFAULT_SINKS = [
13909
14010
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13910
14011
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13911
14012
  { method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
14013
+ // XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
14014
+ // `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
14015
+ { method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
14016
+ { method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13912
14017
  // C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
13913
14018
  { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13914
14019
  { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -15330,33 +15435,56 @@ function isSafeJSChildProcessCall(call, pattern, language) {
15330
15435
  if (SHELL_PROGRAMS.has(program)) return false;
15331
15436
  return true;
15332
15437
  }
15333
- function isSafeCSharpProcessStartCall(call, pattern, language) {
15438
+ var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
15439
+ "sh",
15440
+ "bash",
15441
+ "zsh",
15442
+ "dash",
15443
+ "ash",
15444
+ "ksh",
15445
+ "cmd",
15446
+ "powershell",
15447
+ "pwsh"
15448
+ ]);
15449
+ function isConstNonShellExe(raw) {
15450
+ if (!raw) return false;
15451
+ const t = raw.trim();
15452
+ if (!/^@?"[^"]*"$/.test(t)) return false;
15453
+ const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
15454
+ return !CSHARP_SHELL_PROGRAMS.has(program);
15455
+ }
15456
+ var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
15457
+ function processStartInfoExe(expr, sourceLines) {
15458
+ const inline = PSI_CTOR_EXE_RE.exec(expr);
15459
+ if (inline) return inline[1];
15460
+ if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
15461
+ const varName = expr.trim();
15462
+ const assignRe = new RegExp(
15463
+ `\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
15464
+ );
15465
+ for (const line of sourceLines) {
15466
+ const m = assignRe.exec(line);
15467
+ if (m) return m[1];
15468
+ }
15469
+ }
15470
+ return null;
15471
+ }
15472
+ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
15334
15473
  if (language !== "csharp") return false;
15335
15474
  if (pattern.type !== "command_injection") return false;
15336
- if (call.method_name !== "Start") return false;
15337
- if (call.arguments.length < 2) return false;
15338
- const fileArg = call.arguments.find((a) => a.position === 0);
15339
- if (!fileArg) return false;
15340
- let raw;
15341
- if (fileArg.literal !== null && fileArg.literal !== void 0) {
15342
- raw = String(fileArg.literal).trim();
15343
- } else {
15344
- raw = (fileArg.expression ?? "").trim();
15475
+ const method = call.method_name;
15476
+ if (method !== "Start" && method !== "ProcessStartInfo") return false;
15477
+ if (call.arguments.length >= 2) {
15478
+ const fileArg = call.arguments.find((a) => a.position === 0);
15479
+ const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
15480
+ return isConstNonShellExe(raw);
15345
15481
  }
15346
- if (!/^@?"[^"]*"$/.test(raw)) return false;
15347
- const program = (raw.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
15348
- const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
15349
- "sh",
15350
- "bash",
15351
- "zsh",
15352
- "dash",
15353
- "ash",
15354
- "ksh",
15355
- "cmd",
15356
- "powershell",
15357
- "pwsh"
15358
- ]);
15359
- return !SHELL_PROGRAMS.has(program);
15482
+ if (method === "Start" && call.arguments.length === 1) {
15483
+ const arg0 = call.arguments.find((a) => a.position === 0);
15484
+ const expr = (arg0?.expression ?? "").trim();
15485
+ return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
15486
+ }
15487
+ return false;
15360
15488
  }
15361
15489
  function isSafeRustCommandCall(call, pattern, language) {
15362
15490
  if (language !== "rust") return false;
@@ -15613,7 +15741,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
15613
15741
  if (isSafeJSChildProcessCall(call, pattern, language)) {
15614
15742
  continue;
15615
15743
  }
15616
- if (isSafeCSharpProcessStartCall(call, pattern, language)) {
15744
+ if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
15617
15745
  continue;
15618
15746
  }
15619
15747
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
@@ -43434,8 +43562,12 @@ var InsecureDeserializationConfigPass = class {
43434
43562
  name = "insecure-deserialization-config";
43435
43563
  category = "security";
43436
43564
  run(ctx) {
43565
+ if (ctx.language === "java") return this.runJava(ctx);
43566
+ if (ctx.language === "csharp") return this.runCSharp(ctx);
43567
+ return { findings: [] };
43568
+ }
43569
+ runJava(ctx) {
43437
43570
  const { graph, language } = ctx;
43438
- if (language !== "java") return { findings: [] };
43439
43571
  const file = graph.ir.meta.file;
43440
43572
  const findings = [];
43441
43573
  for (const call of graph.ir.calls) {
@@ -43460,12 +43592,46 @@ var InsecureDeserializationConfigPass = class {
43460
43592
  }
43461
43593
  return { findings };
43462
43594
  }
43595
+ // C# — Json.NET `TypeNameHandling` set to a value other than `None` (ca#318).
43596
+ // `TypeNameHandling.All/Auto/Objects/Arrays` makes Json.NET honour a `$type`
43597
+ // field in the payload and instantiate the named .NET type, so
43598
+ // `JsonConvert.DeserializeObject(untrusted, settings)` becomes a gadget-chain
43599
+ // RCE (the .NET analogue of XStream's grant-all). The vulnerability is the
43600
+ // constant setting, independent of flow — the secure value `None` never matches.
43601
+ runCSharp(ctx) {
43602
+ const file = ctx.graph.ir.meta.file;
43603
+ const findings = [];
43604
+ const lines = ctx.code.split("\n");
43605
+ for (let i2 = 0; i2 < lines.length; i2++) {
43606
+ const m = INSECURE_TYPE_NAME_HANDLING_RE.exec(lines[i2]);
43607
+ if (!m) continue;
43608
+ const line = i2 + 1;
43609
+ const api = `TypeNameHandling = TypeNameHandling.${m[1]}`;
43610
+ findings.push({ line, api });
43611
+ ctx.addFinding({
43612
+ id: `${this.name}-${file}-${line}`,
43613
+ pass: this.name,
43614
+ category: this.category,
43615
+ rule_id: this.name,
43616
+ cwe: "CWE-502",
43617
+ severity: "high",
43618
+ level: "error",
43619
+ message: `Json.NET configured with TypeNameHandling.${m[1]}: a $type field in untrusted JSON can instantiate arbitrary .NET types (deserialization RCE)`,
43620
+ file,
43621
+ line,
43622
+ fix: "Use TypeNameHandling.None (the default), or bind a SerializationBinder that allow-lists the exact types you deserialize.",
43623
+ evidence: { api, language: "csharp" }
43624
+ });
43625
+ }
43626
+ return { findings };
43627
+ }
43463
43628
  isPermissiveXStreamConfig(call) {
43464
43629
  if (call.method_name !== "addPermission") return false;
43465
43630
  const arg0 = call.arguments[0]?.expression;
43466
43631
  return typeof arg0 === "string" && ANY_TYPE_PERMISSION_RE.test(arg0);
43467
43632
  }
43468
43633
  };
43634
+ var INSECURE_TYPE_NAME_HANDLING_RE = /\bTypeNameHandling\s*=\s*(?:Newtonsoft\.Json\.)?TypeNameHandling\.(All|Auto|Objects|Arrays)\b/;
43469
43635
 
43470
43636
  // src/analysis/passes/plaintext-password-storage-pass.ts
43471
43637
  function isWriteStorageCall(call, language) {
@@ -45629,6 +45795,25 @@ function getNodeTypesForLanguage(language) {
45629
45795
  "selector_expression",
45630
45796
  "identifier"
45631
45797
  ]);
45798
+ case "csharp":
45799
+ return /* @__PURE__ */ new Set([
45800
+ "method_invocation",
45801
+ "object_creation_expression",
45802
+ "class_declaration",
45803
+ "method_declaration",
45804
+ "constructor_declaration",
45805
+ "field_declaration",
45806
+ "import_declaration",
45807
+ "interface_declaration",
45808
+ "enum_declaration",
45809
+ "package_declaration",
45810
+ "local_variable_declaration",
45811
+ // buildCSharpCFG method-like containers
45812
+ "destructor_declaration",
45813
+ "operator_declaration",
45814
+ "local_function_statement",
45815
+ "accessor_declaration"
45816
+ ]);
45632
45817
  default:
45633
45818
  return /* @__PURE__ */ new Set([
45634
45819
  "method_invocation",