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.
@@ -6046,13 +6046,21 @@ function extractCSharpCalls(tree, cache) {
6046
6046
  const left = asn.childForFieldName("left");
6047
6047
  if (left?.type !== "member_access_expression") continue;
6048
6048
  const nameNode = left.childForFieldName("name");
6049
- if (!nameNode || getNodeText(nameNode) !== "CommandText") continue;
6049
+ if (!nameNode) continue;
6050
+ const propName = getNodeText(nameNode);
6051
+ const exprNode = left.childForFieldName("expression");
6052
+ if (propName === "Filter") {
6053
+ const recv = exprNode ? getNodeText(exprNode) : null;
6054
+ const recvType = recv ? typeMap.get(recv) : void 0;
6055
+ if (recvType !== "DirectorySearcher") continue;
6056
+ } else if (propName !== "CommandText") {
6057
+ continue;
6058
+ }
6050
6059
  const right = asn.childForFieldName("right");
6051
6060
  if (!right) continue;
6052
6061
  const rhsText = getNodeText(right);
6053
- const exprNode = left.childForFieldName("expression");
6054
6062
  calls.push({
6055
- method_name: "CommandText",
6063
+ method_name: propName,
6056
6064
  receiver: exprNode ? getNodeText(exprNode) : null,
6057
6065
  receiver_type: null,
6058
6066
  receiver_type_fqn: null,
@@ -8618,6 +8626,9 @@ function buildCFG(tree, language, cache) {
8618
8626
  if (effectiveLanguage === "go") {
8619
8627
  return buildGoCFG(tree, blockIdCounter, cache);
8620
8628
  }
8629
+ if (effectiveLanguage === "csharp") {
8630
+ return buildCSharpCFG(tree, blockIdCounter, cache);
8631
+ }
8621
8632
  if (isJavaScript) {
8622
8633
  const functions = [
8623
8634
  ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
@@ -8630,7 +8641,7 @@ function buildCFG(tree, language, cache) {
8630
8641
  const body2 = func2.childForFieldName("body");
8631
8642
  if (!body2) continue;
8632
8643
  if (body2.type === "statement_block") {
8633
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, true);
8644
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
8634
8645
  allBlocks.push(...blocks);
8635
8646
  allEdges.push(...edges);
8636
8647
  blockIdCounter = nextId;
@@ -8652,7 +8663,7 @@ function buildCFG(tree, language, cache) {
8652
8663
  for (const method of methods) {
8653
8664
  const body2 = method.childForFieldName("body");
8654
8665
  if (!body2) continue;
8655
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
8666
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
8656
8667
  allBlocks.push(...blocks);
8657
8668
  allEdges.push(...edges);
8658
8669
  blockIdCounter = nextId;
@@ -8660,7 +8671,28 @@ function buildCFG(tree, language, cache) {
8660
8671
  }
8661
8672
  return { blocks: allBlocks, edges: allEdges };
8662
8673
  }
8663
- function buildMethodCFG(body2, startId, isJavaScript) {
8674
+ function buildCSharpCFG(tree, blockIdCounter, cache) {
8675
+ const allBlocks = [];
8676
+ const allEdges = [];
8677
+ const containers = [
8678
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
8679
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
8680
+ ...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
8681
+ ...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
8682
+ ...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
8683
+ ...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
8684
+ ];
8685
+ for (const container of containers) {
8686
+ const body2 = container.childForFieldName("body");
8687
+ if (!body2 || body2.type !== "block") continue;
8688
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
8689
+ allBlocks.push(...blocks);
8690
+ allEdges.push(...edges);
8691
+ blockIdCounter = nextId;
8692
+ }
8693
+ return { blocks: allBlocks, edges: allEdges };
8694
+ }
8695
+ function buildMethodCFG(body2, startId, dialect) {
8664
8696
  const blocks = [];
8665
8697
  const edges = [];
8666
8698
  let currentId = startId;
@@ -8671,7 +8703,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
8671
8703
  end_line: body2.startPosition.row + 1
8672
8704
  };
8673
8705
  blocks.push(entryBlock);
8674
- const result = processStatements(body2, currentId, blocks, edges, isJavaScript);
8706
+ const result = processStatements(body2, currentId, blocks, edges, dialect);
8675
8707
  currentId = result.nextId;
8676
8708
  if (result.entryId !== -1) {
8677
8709
  edges.push({
@@ -8703,15 +8735,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
8703
8735
  }
8704
8736
  return { blocks, edges, nextId: currentId };
8705
8737
  }
8706
- function processStatements(container, startId, blocks, edges, isJavaScript) {
8738
+ function processStatements(container, startId, blocks, edges, dialect) {
8707
8739
  let currentId = startId;
8708
8740
  let firstBlockId = -1;
8709
8741
  let lastExitIds = [];
8710
8742
  for (let i2 = 0; i2 < container.childCount; i2++) {
8711
8743
  const stmt = container.child(i2);
8712
8744
  if (!stmt) continue;
8713
- if (!isStatement(stmt, isJavaScript)) continue;
8714
- const result = processStatement(stmt, currentId, blocks, edges, isJavaScript);
8745
+ if (!isStatement(stmt, dialect)) continue;
8746
+ const result = processStatement(stmt, currentId, blocks, edges, dialect);
8715
8747
  currentId = result.nextId;
8716
8748
  if (firstBlockId === -1) {
8717
8749
  firstBlockId = result.entryId;
@@ -8732,31 +8764,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
8732
8764
  nextId: currentId
8733
8765
  };
8734
8766
  }
8735
- function processStatement(stmt, startId, blocks, edges, isJavaScript) {
8767
+ function processStatement(stmt, startId, blocks, edges, dialect) {
8736
8768
  switch (stmt.type) {
8737
8769
  case "if_statement":
8738
- return processIfStatement(stmt, startId, blocks, edges, isJavaScript);
8770
+ return processIfStatement(stmt, startId, blocks, edges, dialect);
8739
8771
  case "for_statement":
8740
8772
  case "enhanced_for_statement":
8741
8773
  case "for_in_statement":
8742
8774
  case "for_of_statement":
8743
- return processForStatement(stmt, startId, blocks, edges, isJavaScript);
8775
+ case "foreach_statement":
8776
+ return processForStatement(stmt, startId, blocks, edges, dialect);
8744
8777
  case "while_statement":
8745
- return processWhileStatement(stmt, startId, blocks, edges, isJavaScript);
8778
+ return processWhileStatement(stmt, startId, blocks, edges, dialect);
8746
8779
  case "do_statement":
8747
- return processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript);
8780
+ return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
8748
8781
  case "try_statement":
8749
- return processTryStatement(stmt, startId, blocks, edges, isJavaScript);
8782
+ return processTryStatement(stmt, startId, blocks, edges, dialect);
8750
8783
  case "switch_expression":
8751
8784
  case "switch_statement":
8752
- return processSwitchStatement(stmt, startId, blocks, edges, isJavaScript);
8785
+ return processSwitchStatement(stmt, startId, blocks, edges, dialect);
8786
+ // C# scoped blocks add no branching — flow through the inner block so its
8787
+ // nested statements are still captured.
8788
+ case "using_statement":
8789
+ case "lock_statement":
8790
+ case "checked_statement":
8791
+ case "unsafe_statement": {
8792
+ const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
8793
+ if (inner) return processStatement(inner, startId, blocks, edges, dialect);
8794
+ return processSimpleStatement(stmt, startId, blocks);
8795
+ }
8753
8796
  case "block":
8754
8797
  case "statement_block":
8755
- return processStatements(stmt, startId, blocks, edges, isJavaScript);
8798
+ return processStatements(stmt, startId, blocks, edges, dialect);
8756
8799
  default:
8757
8800
  return processSimpleStatement(stmt, startId, blocks);
8758
8801
  }
8759
8802
  }
8803
+ function lastBlockChild(node) {
8804
+ for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
8805
+ const c = node.child(i2);
8806
+ if (c && c.type === "block") return c;
8807
+ }
8808
+ return null;
8809
+ }
8760
8810
  function processSimpleStatement(stmt, startId, blocks) {
8761
8811
  const block = {
8762
8812
  id: startId,
@@ -8771,7 +8821,7 @@ function processSimpleStatement(stmt, startId, blocks) {
8771
8821
  nextId: startId + 1
8772
8822
  };
8773
8823
  }
8774
- function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8824
+ function processIfStatement(stmt, startId, blocks, edges, dialect) {
8775
8825
  let currentId = startId;
8776
8826
  const condBlock = {
8777
8827
  id: currentId++,
@@ -8783,7 +8833,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8783
8833
  const exitIds = [];
8784
8834
  const consequence = stmt.childForFieldName("consequence");
8785
8835
  if (consequence) {
8786
- const thenResult = processStatement(consequence, currentId, blocks, edges, isJavaScript);
8836
+ const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
8787
8837
  currentId = thenResult.nextId;
8788
8838
  edges.push({
8789
8839
  from: condBlock.id,
@@ -8794,7 +8844,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8794
8844
  }
8795
8845
  const alternative = stmt.childForFieldName("alternative");
8796
8846
  if (alternative) {
8797
- const elseResult = processStatement(alternative, currentId, blocks, edges, isJavaScript);
8847
+ const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
8798
8848
  currentId = elseResult.nextId;
8799
8849
  edges.push({
8800
8850
  from: condBlock.id,
@@ -8811,7 +8861,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
8811
8861
  nextId: currentId
8812
8862
  };
8813
8863
  }
8814
- function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
8864
+ function processForStatement(stmt, startId, blocks, edges, dialect) {
8815
8865
  let currentId = startId;
8816
8866
  const loopBlock = {
8817
8867
  id: currentId++,
@@ -8822,7 +8872,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
8822
8872
  blocks.push(loopBlock);
8823
8873
  const body2 = stmt.childForFieldName("body");
8824
8874
  if (body2) {
8825
- const bodyResult = processStatement(body2, currentId, blocks, edges, isJavaScript);
8875
+ const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
8826
8876
  currentId = bodyResult.nextId;
8827
8877
  edges.push({
8828
8878
  from: loopBlock.id,
@@ -8844,16 +8894,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
8844
8894
  nextId: currentId
8845
8895
  };
8846
8896
  }
8847
- function processWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
8848
- return processForStatement(stmt, startId, blocks, edges, isJavaScript);
8897
+ function processWhileStatement(stmt, startId, blocks, edges, dialect) {
8898
+ return processForStatement(stmt, startId, blocks, edges, dialect);
8849
8899
  }
8850
- function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
8900
+ function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
8851
8901
  let currentId = startId;
8852
8902
  const body2 = stmt.childForFieldName("body");
8853
8903
  let bodyEntryId = currentId;
8854
8904
  let bodyExitIds = [];
8855
8905
  if (body2) {
8856
- const bodyResult = processStatement(body2, currentId, blocks, edges, isJavaScript);
8906
+ const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
8857
8907
  currentId = bodyResult.nextId;
8858
8908
  bodyEntryId = bodyResult.entryId;
8859
8909
  bodyExitIds = bodyResult.exitIds;
@@ -8883,13 +8933,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
8883
8933
  nextId: currentId
8884
8934
  };
8885
8935
  }
8886
- function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8936
+ function processTryStatement(stmt, startId, blocks, edges, dialect) {
8887
8937
  let currentId = startId;
8888
8938
  const exitIds = [];
8889
8939
  const body2 = stmt.childForFieldName("body");
8890
8940
  let tryEntryId = -1;
8891
8941
  if (body2) {
8892
- const bodyResult = processStatements(body2, currentId, blocks, edges, isJavaScript);
8942
+ const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
8893
8943
  currentId = bodyResult.nextId;
8894
8944
  tryEntryId = bodyResult.entryId;
8895
8945
  exitIds.push(...bodyResult.exitIds);
@@ -8899,7 +8949,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8899
8949
  if (child?.type === "catch_clause") {
8900
8950
  const catchBody = child.childForFieldName("body");
8901
8951
  if (catchBody) {
8902
- const catchResult = processStatements(catchBody, currentId, blocks, edges, isJavaScript);
8952
+ const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
8903
8953
  currentId = catchResult.nextId;
8904
8954
  if (tryEntryId !== -1) {
8905
8955
  edges.push({
@@ -8912,9 +8962,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8912
8962
  }
8913
8963
  }
8914
8964
  }
8915
- const finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
8965
+ let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
8966
+ if (!finallyClause && dialect === "csharp") {
8967
+ for (let i2 = 0; i2 < stmt.childCount; i2++) {
8968
+ const child = stmt.child(i2);
8969
+ if (child?.type === "finally_clause") {
8970
+ finallyClause = lastBlockChild(child);
8971
+ break;
8972
+ }
8973
+ }
8974
+ }
8916
8975
  if (finallyClause) {
8917
- const finallyResult = processStatements(finallyClause, currentId, blocks, edges, isJavaScript);
8976
+ const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
8918
8977
  currentId = finallyResult.nextId;
8919
8978
  for (const exitId of exitIds) {
8920
8979
  edges.push({
@@ -8935,7 +8994,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
8935
8994
  nextId: currentId
8936
8995
  };
8937
8996
  }
8938
- function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8997
+ function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
8939
8998
  let currentId = startId;
8940
8999
  const switchBlock = {
8941
9000
  id: currentId++,
@@ -8947,11 +9006,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8947
9006
  const exitIds = [];
8948
9007
  const body2 = stmt.childForFieldName("body");
8949
9008
  if (body2) {
8950
- const caseTypes = isJavaScript ? ["switch_case", "switch_default"] : ["switch_block_statement_group", "switch_rule"];
9009
+ const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
8951
9010
  for (let i2 = 0; i2 < body2.childCount; i2++) {
8952
9011
  const child = body2.child(i2);
8953
9012
  if (child && caseTypes.includes(child.type)) {
8954
- const caseResult = processStatements(child, currentId, blocks, edges, isJavaScript);
9013
+ const caseResult = processStatements(child, currentId, blocks, edges, dialect);
8955
9014
  currentId = caseResult.nextId;
8956
9015
  if (caseResult.entryId !== -1) {
8957
9016
  edges.push({
@@ -8981,7 +9040,7 @@ function buildBashCFG(tree, startId, cache) {
8981
9040
  for (const func2 of functions) {
8982
9041
  const body2 = func2.childForFieldName("body");
8983
9042
  if (!body2) continue;
8984
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
9043
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
8985
9044
  allBlocks.push(...blocks);
8986
9045
  allEdges.push(...edges);
8987
9046
  blockIdCounter = nextId;
@@ -9004,7 +9063,7 @@ function buildBashCFG(tree, startId, cache) {
9004
9063
  let lastExitIds = [];
9005
9064
  let firstBlockId = -1;
9006
9065
  for (const stmt of topLevelStatements) {
9007
- const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, false);
9066
+ const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
9008
9067
  blockIdCounter = result.nextId;
9009
9068
  if (firstBlockId === -1) {
9010
9069
  firstBlockId = result.entryId;
@@ -9048,7 +9107,8 @@ function isBashStatement(node) {
9048
9107
  ]);
9049
9108
  return bashStatementTypes.has(node.type);
9050
9109
  }
9051
- function isStatement(node, isJavaScript) {
9110
+ function isStatement(node, dialect) {
9111
+ if (dialect === "csharp") return csharpStatementTypes.has(node.type);
9052
9112
  const javaStatementTypes = /* @__PURE__ */ new Set([
9053
9113
  "local_variable_declaration",
9054
9114
  "expression_statement",
@@ -9092,8 +9152,30 @@ function isStatement(node, isJavaScript) {
9092
9152
  "export_statement",
9093
9153
  "import_statement"
9094
9154
  ]);
9095
- return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
9096
- }
9155
+ return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
9156
+ }
9157
+ var csharpStatementTypes = /* @__PURE__ */ new Set([
9158
+ "local_declaration_statement",
9159
+ "expression_statement",
9160
+ "if_statement",
9161
+ "for_statement",
9162
+ "foreach_statement",
9163
+ "while_statement",
9164
+ "do_statement",
9165
+ "try_statement",
9166
+ "switch_statement",
9167
+ "return_statement",
9168
+ "throw_statement",
9169
+ "break_statement",
9170
+ "continue_statement",
9171
+ "using_statement",
9172
+ "lock_statement",
9173
+ "checked_statement",
9174
+ "unsafe_statement",
9175
+ "yield_statement",
9176
+ "goto_statement",
9177
+ "block"
9178
+ ]);
9097
9179
  function buildGoCFG(tree, blockIdCounter, cache) {
9098
9180
  const allBlocks = [];
9099
9181
  const allEdges = [];
@@ -9104,7 +9186,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
9104
9186
  for (const func2 of functions) {
9105
9187
  const body2 = func2.childForFieldName("body");
9106
9188
  if (!body2 || body2.type !== "block") continue;
9107
- const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
9189
+ const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
9108
9190
  allBlocks.push(...blocks);
9109
9191
  allEdges.push(...edges);
9110
9192
  blockIdCounter = nextId;
@@ -13184,6 +13266,11 @@ var DEFAULT_SINKS = [
13184
13266
  // ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
13185
13267
  // extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
13186
13268
  { method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13269
+ // `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
13270
+ // Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
13271
+ // resolves to a DirectorySearcher, so this classless entry never over-matches
13272
+ // other `.Filter =` assignments (DataView/BindingSource/collection filters).
13273
+ { method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13187
13274
  // ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
13188
13275
  // rides the SqlCommand receiver (CommandText set from tainted data); the
13189
13276
  // receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
@@ -13200,6 +13287,9 @@ var DEFAULT_SINKS = [
13200
13287
  // injectable — the second is the argv path where taint rides arg[1] (#276).
13201
13288
  { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13202
13289
  { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13290
+ // P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
13291
+ // always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
13292
+ { method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13203
13293
  // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13204
13294
  { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13205
13295
  { method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13228,6 +13318,9 @@ var DEFAULT_SINKS = [
13228
13318
  { method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13229
13319
  { method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13230
13320
  { method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13321
+ // ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
13322
+ // an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
13323
+ { method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13231
13324
  // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13232
13325
  { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13233
13326
  { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13266,6 +13359,14 @@ var DEFAULT_SINKS = [
13266
13359
  { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13267
13360
  { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13268
13361
  { method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13362
+ // Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
13363
+ // documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
13364
+ { method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13365
+ // `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
13366
+ // Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
13367
+ // only an argument is tainted never fires. .NET composite formatting can't corrupt
13368
+ // memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
13369
+ { method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13269
13370
  // C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
13270
13371
  // filter is the constructor argument.
13271
13372
  { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13304,6 +13405,10 @@ var DEFAULT_SINKS = [
13304
13405
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13305
13406
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13306
13407
  { method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13408
+ // XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
13409
+ // `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
13410
+ { method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13411
+ { method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13307
13412
  // C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
13308
13413
  { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13309
13414
  { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -14630,33 +14735,56 @@ function isSafeJSChildProcessCall(call, pattern, language) {
14630
14735
  if (SHELL_PROGRAMS.has(program)) return false;
14631
14736
  return true;
14632
14737
  }
14633
- function isSafeCSharpProcessStartCall(call, pattern, language) {
14738
+ var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
14739
+ "sh",
14740
+ "bash",
14741
+ "zsh",
14742
+ "dash",
14743
+ "ash",
14744
+ "ksh",
14745
+ "cmd",
14746
+ "powershell",
14747
+ "pwsh"
14748
+ ]);
14749
+ function isConstNonShellExe(raw) {
14750
+ if (!raw) return false;
14751
+ const t = raw.trim();
14752
+ if (!/^@?"[^"]*"$/.test(t)) return false;
14753
+ const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
14754
+ return !CSHARP_SHELL_PROGRAMS.has(program);
14755
+ }
14756
+ var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
14757
+ function processStartInfoExe(expr, sourceLines) {
14758
+ const inline = PSI_CTOR_EXE_RE.exec(expr);
14759
+ if (inline) return inline[1];
14760
+ if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
14761
+ const varName = expr.trim();
14762
+ const assignRe = new RegExp(
14763
+ `\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
14764
+ );
14765
+ for (const line of sourceLines) {
14766
+ const m = assignRe.exec(line);
14767
+ if (m) return m[1];
14768
+ }
14769
+ }
14770
+ return null;
14771
+ }
14772
+ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
14634
14773
  if (language !== "csharp") return false;
14635
14774
  if (pattern.type !== "command_injection") return false;
14636
- if (call.method_name !== "Start") return false;
14637
- if (call.arguments.length < 2) return false;
14638
- const fileArg = call.arguments.find((a) => a.position === 0);
14639
- if (!fileArg) return false;
14640
- let raw;
14641
- if (fileArg.literal !== null && fileArg.literal !== void 0) {
14642
- raw = String(fileArg.literal).trim();
14643
- } else {
14644
- raw = (fileArg.expression ?? "").trim();
14775
+ const method = call.method_name;
14776
+ if (method !== "Start" && method !== "ProcessStartInfo") return false;
14777
+ if (call.arguments.length >= 2) {
14778
+ const fileArg = call.arguments.find((a) => a.position === 0);
14779
+ const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
14780
+ return isConstNonShellExe(raw);
14645
14781
  }
14646
- if (!/^@?"[^"]*"$/.test(raw)) return false;
14647
- const program = (raw.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
14648
- const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
14649
- "sh",
14650
- "bash",
14651
- "zsh",
14652
- "dash",
14653
- "ash",
14654
- "ksh",
14655
- "cmd",
14656
- "powershell",
14657
- "pwsh"
14658
- ]);
14659
- return !SHELL_PROGRAMS.has(program);
14782
+ if (method === "Start" && call.arguments.length === 1) {
14783
+ const arg0 = call.arguments.find((a) => a.position === 0);
14784
+ const expr = (arg0?.expression ?? "").trim();
14785
+ return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
14786
+ }
14787
+ return false;
14660
14788
  }
14661
14789
  function isSafeRustCommandCall(call, pattern, language) {
14662
14790
  if (language !== "rust") return false;
@@ -14913,7 +15041,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
14913
15041
  if (isSafeJSChildProcessCall(call, pattern, language)) {
14914
15042
  continue;
14915
15043
  }
14916
- if (isSafeCSharpProcessStartCall(call, pattern, language)) {
15044
+ if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
14917
15045
  continue;
14918
15046
  }
14919
15047
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {