circle-ir 3.216.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +15 -1
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +68 -0
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/findings.d.ts.map +1 -1
  6. package/dist/analysis/findings.js +15 -9
  7. package/dist/analysis/findings.js.map +1 -1
  8. package/dist/analysis/note-coalescer.d.ts +0 -4
  9. package/dist/analysis/note-coalescer.d.ts.map +1 -1
  10. package/dist/analysis/note-coalescer.js +36 -1
  11. package/dist/analysis/note-coalescer.js.map +1 -1
  12. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  13. package/dist/analysis/passes/language-sources-pass.js +54 -1
  14. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  15. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  16. package/dist/analysis/passes/sink-filter-pass.js +178 -0
  17. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  18. package/dist/analysis/passes/taint-propagation-pass.js +1 -1
  19. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  20. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  21. package/dist/analysis/taint-matcher.js +18 -3
  22. package/dist/analysis/taint-matcher.js.map +1 -1
  23. package/dist/browser/circle-ir.js +573 -17
  24. package/dist/core/circle-ir-core.cjs +338 -4
  25. package/dist/core/circle-ir-core.js +338 -4
  26. package/dist/core/extractors/calls.d.ts.map +1 -1
  27. package/dist/core/extractors/calls.js +133 -0
  28. package/dist/core/extractors/calls.js.map +1 -1
  29. package/dist/core/extractors/dfg.d.ts.map +1 -1
  30. package/dist/core/extractors/dfg.js +86 -1
  31. package/dist/core/extractors/dfg.js.map +1 -1
  32. package/dist/core/extractors/types.d.ts.map +1 -1
  33. package/dist/core/extractors/types.js +89 -0
  34. package/dist/core/extractors/types.js.map +1 -1
  35. package/dist/languages/plugins/csharp.d.ts +35 -0
  36. package/dist/languages/plugins/csharp.d.ts.map +1 -0
  37. package/dist/languages/plugins/csharp.js +72 -0
  38. package/dist/languages/plugins/csharp.js.map +1 -0
  39. package/dist/languages/plugins/index.d.ts +1 -0
  40. package/dist/languages/plugins/index.d.ts.map +1 -1
  41. package/dist/languages/plugins/index.js +3 -0
  42. package/dist/languages/plugins/index.js.map +1 -1
  43. package/dist/languages/types.d.ts +1 -1
  44. package/dist/languages/types.d.ts.map +1 -1
  45. package/dist/types/index.d.ts +1 -1
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/wasm/tree-sitter-csharp.wasm +0 -0
  48. package/docs/SPEC.md +1 -1
  49. package/package.json +1 -1
@@ -4402,8 +4402,84 @@ function extractTypes(tree, cache, language) {
4402
4402
  if (isJavaScript) {
4403
4403
  return extractJavaScriptTypes(tree, cache);
4404
4404
  }
4405
+ if (effectiveLanguage === "csharp") {
4406
+ return extractCSharpTypes(tree, cache);
4407
+ }
4405
4408
  return extractJavaTypes(tree, cache);
4406
4409
  }
4410
+ function extractCSharpTypes(tree, cache) {
4411
+ const types = [];
4412
+ const KINDS = {
4413
+ class_declaration: "class",
4414
+ record_declaration: "class",
4415
+ struct_declaration: "class",
4416
+ interface_declaration: "interface",
4417
+ enum_declaration: "enum"
4418
+ };
4419
+ for (const kindNode of Object.keys(KINDS)) {
4420
+ for (const node of getNodesFromCache(tree.rootNode, kindNode, cache)) {
4421
+ const nameNode = node.childForFieldName("name");
4422
+ const body2 = node.childForFieldName("body");
4423
+ const methods = [];
4424
+ if (body2) {
4425
+ for (let i2 = 0; i2 < body2.childCount; i2++) {
4426
+ const m = body2.child(i2);
4427
+ if (!m || m.type !== "method_declaration") continue;
4428
+ const mName = m.childForFieldName("name");
4429
+ const paramList = m.childForFieldName("parameters");
4430
+ const parameters = [];
4431
+ if (paramList) {
4432
+ for (let j = 0; j < paramList.childCount; j++) {
4433
+ const pnode = paramList.child(j);
4434
+ if (!pnode || pnode.type !== "parameter") continue;
4435
+ const pName = pnode.childForFieldName("name");
4436
+ const pType = pnode.childForFieldName("type");
4437
+ if (pName) {
4438
+ parameters.push({
4439
+ name: getNodeText(pName),
4440
+ type: pType ? getNodeText(pType) : null,
4441
+ annotations: [],
4442
+ line: pnode.startPosition.row + 1
4443
+ });
4444
+ }
4445
+ }
4446
+ }
4447
+ const returns = m.childForFieldName("returns");
4448
+ methods.push({
4449
+ name: mName ? getNodeText(mName) : "unknown",
4450
+ return_type: returns ? getNodeText(returns) : null,
4451
+ parameters,
4452
+ annotations: [],
4453
+ modifiers: extractCSharpModifiers(m),
4454
+ start_line: m.startPosition.row + 1,
4455
+ end_line: m.endPosition.row + 1
4456
+ });
4457
+ }
4458
+ }
4459
+ types.push({
4460
+ name: nameNode ? getNodeText(nameNode) : "anonymous",
4461
+ kind: KINDS[kindNode],
4462
+ package: null,
4463
+ extends: null,
4464
+ implements: [],
4465
+ annotations: [],
4466
+ methods,
4467
+ fields: [],
4468
+ start_line: node.startPosition.row + 1,
4469
+ end_line: node.endPosition.row + 1
4470
+ });
4471
+ }
4472
+ }
4473
+ return types;
4474
+ }
4475
+ function extractCSharpModifiers(node) {
4476
+ const mods = [];
4477
+ for (let i2 = 0; i2 < node.childCount; i2++) {
4478
+ const c = node.child(i2);
4479
+ if (c && c.type === "modifier") mods.push(getNodeText(c));
4480
+ }
4481
+ return mods;
4482
+ }
4407
4483
  function extractJavaTypes(tree, cache) {
4408
4484
  const types = [];
4409
4485
  const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
@@ -5862,6 +5938,9 @@ function extractCalls(tree, cache, language) {
5862
5938
  if (isJavaScript) {
5863
5939
  return extractJavaScriptCalls(tree, cache);
5864
5940
  }
5941
+ if (detectedLanguage === "csharp") {
5942
+ return extractCSharpCalls(tree, cache);
5943
+ }
5865
5944
  const context = buildResolutionContext(tree, cache);
5866
5945
  const invocations = getNodesFromCache(tree.rootNode, "method_invocation", cache);
5867
5946
  for (const inv of invocations) {
@@ -5876,6 +5955,107 @@ function extractCalls(tree, cache, language) {
5876
5955
  }
5877
5956
  return calls;
5878
5957
  }
5958
+ function buildCSharpReceiverTypeMap(tree, cache) {
5959
+ const map = /* @__PURE__ */ new Map();
5960
+ const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
5961
+ for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
5962
+ const typeNode = vd.childForFieldName("type");
5963
+ const declaredType = typeNode ? getNodeText(typeNode) : null;
5964
+ for (let i2 = 0; i2 < vd.childCount; i2++) {
5965
+ const decl = vd.child(i2);
5966
+ if (!decl || decl.type !== "variable_declarator") continue;
5967
+ const nameNode = decl.childForFieldName("name");
5968
+ if (nameNode?.type !== "identifier") continue;
5969
+ let t = declaredType && declaredType !== "var" ? declaredType : null;
5970
+ if (!t) {
5971
+ const oce = findFirstDescendant(decl, "object_creation_expression");
5972
+ const oceType = oce?.childForFieldName("type");
5973
+ if (oceType) t = getNodeText(oceType);
5974
+ }
5975
+ if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
5976
+ }
5977
+ }
5978
+ return map;
5979
+ }
5980
+ function findFirstDescendant(node, type) {
5981
+ for (let i2 = 0; i2 < node.childCount; i2++) {
5982
+ const c = node.child(i2);
5983
+ if (!c) continue;
5984
+ if (c.type === type) return c;
5985
+ const found = findFirstDescendant(c, type);
5986
+ if (found) return found;
5987
+ }
5988
+ return null;
5989
+ }
5990
+ function extractCSharpCalls(tree, cache) {
5991
+ const calls = [];
5992
+ const typeMap = buildCSharpReceiverTypeMap(tree, cache);
5993
+ const invocations = getNodesFromCache(tree.rootNode, "invocation_expression", cache);
5994
+ for (const inv of invocations) {
5995
+ const fn = inv.childForFieldName("function");
5996
+ let methodName = "unknown";
5997
+ let receiver = null;
5998
+ if (fn?.type === "member_access_expression") {
5999
+ const nameNode = fn.childForFieldName("name");
6000
+ const exprNode = fn.childForFieldName("expression");
6001
+ methodName = nameNode ? getNodeText(nameNode) : "unknown";
6002
+ receiver = exprNode ? getNodeText(exprNode) : null;
6003
+ } else if (fn) {
6004
+ methodName = getNodeText(fn);
6005
+ }
6006
+ const argsNode = inv.childForFieldName("arguments");
6007
+ calls.push({
6008
+ method_name: methodName,
6009
+ receiver,
6010
+ receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
6011
+ receiver_type_fqn: null,
6012
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
6013
+ location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
6014
+ in_method: findEnclosingMethod(inv)
6015
+ });
6016
+ }
6017
+ const objectCreations = getNodesFromCache(tree.rootNode, "object_creation_expression", cache);
6018
+ for (const creation of objectCreations) {
6019
+ const typeNode = creation.childForFieldName("type");
6020
+ const argsNode = creation.childForFieldName("arguments");
6021
+ calls.push({
6022
+ method_name: typeNode ? getNodeText(typeNode) : "unknown",
6023
+ receiver: null,
6024
+ receiver_type: null,
6025
+ receiver_type_fqn: null,
6026
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
6027
+ location: { line: creation.startPosition.row + 1, column: creation.startPosition.column },
6028
+ in_method: findEnclosingMethod(creation),
6029
+ is_constructor: true
6030
+ });
6031
+ }
6032
+ return calls;
6033
+ }
6034
+ function extractCSharpArguments(argsNode) {
6035
+ const args2 = [];
6036
+ let position = 0;
6037
+ for (let i2 = 0; i2 < argsNode.childCount; i2++) {
6038
+ const child = argsNode.child(i2);
6039
+ if (!child || child.type !== "argument") continue;
6040
+ let expr = null;
6041
+ for (let j = child.childCount - 1; j >= 0; j--) {
6042
+ const c = child.child(j);
6043
+ if (c && c.isNamed) {
6044
+ expr = c;
6045
+ break;
6046
+ }
6047
+ }
6048
+ const text = expr ? getNodeText(expr) : getNodeText(child);
6049
+ args2.push({
6050
+ position: position++,
6051
+ expression: text,
6052
+ variable: expr?.type === "identifier" ? text : null,
6053
+ literal: expr?.type === "string_literal" ? text : null,
6054
+ value: null
6055
+ });
6056
+ }
6057
+ return args2;
6058
+ }
5879
6059
  function extractJavaScriptCalls(tree, cache) {
5880
6060
  const calls = [];
5881
6061
  const context = buildJSResolutionContext(tree, cache);
@@ -8978,8 +9158,72 @@ function buildDFG(tree, cache, language) {
8978
9158
  if (effectiveLanguage === "go") {
8979
9159
  return buildGoDFG(tree);
8980
9160
  }
9161
+ if (effectiveLanguage === "csharp") {
9162
+ return buildCSharpDFG(tree, cache);
9163
+ }
8981
9164
  return buildJavaDFG(tree, cache);
8982
9165
  }
9166
+ function buildCSharpDFG(tree, cache) {
9167
+ const defs = [];
9168
+ const uses = [];
9169
+ let defIdCounter = 1;
9170
+ let useIdCounter = 1;
9171
+ const scopeStack = [/* @__PURE__ */ new Map()];
9172
+ const methods = [
9173
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
9174
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
9175
+ ...getNodesFromCache(tree.rootNode, "local_function_statement", cache)
9176
+ ];
9177
+ for (const method of methods) {
9178
+ scopeStack.push(/* @__PURE__ */ new Map());
9179
+ const params = method.childForFieldName("parameters");
9180
+ if (params) {
9181
+ for (const def of extractParameterDefs(params, defIdCounter)) {
9182
+ defs.push(def);
9183
+ currentScope(scopeStack).set(def.variable, def.id);
9184
+ defIdCounter++;
9185
+ }
9186
+ }
9187
+ const body2 = method.childForFieldName("body");
9188
+ if (body2) {
9189
+ for (const decl of findNodes(body2, "variable_declarator")) {
9190
+ const nameNode = decl.childForFieldName("name");
9191
+ if (nameNode?.type === "identifier") {
9192
+ const name2 = getNodeText(nameNode);
9193
+ const def = { id: defIdCounter++, variable: name2, line: decl.startPosition.row + 1, kind: "local" };
9194
+ defs.push(def);
9195
+ currentScope(scopeStack).set(name2, def.id);
9196
+ }
9197
+ }
9198
+ for (const asn of findNodes(body2, "assignment_expression")) {
9199
+ const left = asn.childForFieldName("left");
9200
+ if (left?.type === "identifier") {
9201
+ const name2 = getNodeText(left);
9202
+ const def = { id: defIdCounter++, variable: name2, line: asn.startPosition.row + 1, kind: "local" };
9203
+ defs.push(def);
9204
+ currentScope(scopeStack).set(name2, def.id);
9205
+ }
9206
+ }
9207
+ const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
9208
+ uses.push(...bodyUses.uses);
9209
+ useIdCounter = bodyUses.nextId;
9210
+ }
9211
+ scopeStack.pop();
9212
+ }
9213
+ const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
9214
+ for (const cls of classes) {
9215
+ const body2 = cls.childForFieldName("body");
9216
+ if (body2) {
9217
+ for (const def of extractFieldDefs(body2, defIdCounter)) {
9218
+ defs.push(def);
9219
+ currentScope(scopeStack).set(def.variable, def.id);
9220
+ defIdCounter++;
9221
+ }
9222
+ }
9223
+ }
9224
+ const chains = computeChains(defs, uses);
9225
+ return { defs, uses, chains };
9226
+ }
8983
9227
  function buildJavaDFG(tree, cache) {
8984
9228
  const defs = [];
8985
9229
  const uses = [];
@@ -9442,7 +9686,7 @@ function extractParameterDefs(params, startId) {
9442
9686
  for (let i2 = 0; i2 < params.childCount; i2++) {
9443
9687
  const param = params.child(i2);
9444
9688
  if (!param) continue;
9445
- if (param.type === "formal_parameter" || param.type === "spread_parameter") {
9689
+ if (param.type === "formal_parameter" || param.type === "spread_parameter" || param.type === "parameter") {
9446
9690
  const nameNode = param.childForFieldName("name");
9447
9691
  if (nameNode) {
9448
9692
  defs.push({
@@ -12881,6 +13125,67 @@ var DEFAULT_SINKS = [
12881
13125
  { method: "put", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
12882
13126
  { method: "patch", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
12883
13127
  { method: "head", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13128
+ // C# SQLi (Phase-0 spike — ADO.NET). `new SqlCommand(sql, conn)` builds the
13129
+ // command from a raw string; the tainted SQL is arg[0] of the constructor.
13130
+ { method: "SqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13131
+ { method: "NpgsqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13132
+ { method: "MySqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13133
+ { method: "SqliteCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13134
+ { method: "OracleCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13135
+ { method: "ExecuteSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13136
+ { method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13137
+ { method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13138
+ { method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13139
+ // C# command injection — Process.Start / ProcessStartInfo (CWE-78).
13140
+ { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13141
+ { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13142
+ // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13143
+ { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13144
+ { method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13145
+ { method: "ReadAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13146
+ { method: "WriteAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13147
+ { method: "WriteAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13148
+ { method: "AppendAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13149
+ { method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13150
+ { method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13151
+ { method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13152
+ { method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13153
+ { method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13154
+ // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13155
+ { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13156
+ { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13157
+ { method: "PutAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13158
+ { method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13159
+ { method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13160
+ { method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13161
+ { method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13162
+ { method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13163
+ { method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13164
+ { method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13165
+ { method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
13166
+ { method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13167
+ // C# code injection — dynamic script/assembly loading (CWE-94).
13168
+ { method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13169
+ { method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13170
+ // C# insecure deserialization — BinaryFormatter et al. (CWE-502).
13171
+ { method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13172
+ { method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13173
+ // C# XSS — raw HTML output (CWE-79).
13174
+ { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13175
+ { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13176
+ { method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13177
+ // C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
13178
+ // filter is the constructor argument.
13179
+ { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13180
+ { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13181
+ // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13182
+ { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13183
+ { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13184
+ { method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13185
+ // C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
13186
+ { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13187
+ { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13188
+ { method: "Load", class: "XDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
12884
13189
  // Python SQLi — asyncpg Connection.*
12885
13190
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
12886
13191
  { method: "fetch", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -13088,6 +13393,15 @@ var DEFAULT_SANITIZERS = [
13088
13393
  { method: "escapeCsv", removes: ["xss"] },
13089
13394
  // OWASP HTML sanitizer (`com.googlecode.owasp-java-html-sanitizer`).
13090
13395
  { method: "sanitize", class: "PolicyFactory", removes: ["xss"] },
13396
+ // C# sanitizers (Phase-1). HTML encoders neutralise XSS; Path.GetFileName
13397
+ // strips any directory component (basename) so a traversal payload cannot
13398
+ // escape the target directory. Method names are C#-distinctive.
13399
+ { method: "HtmlEncode", removes: ["xss"] },
13400
+ // HttpUtility / WebUtility
13401
+ { method: "JavaScriptStringEncode", removes: ["xss"] },
13402
+ // HttpUtility
13403
+ { method: "Encode", class: "HtmlEncoder", removes: ["xss"] },
13404
+ { method: "GetFileName", class: "Path", removes: ["path_traversal"] },
13091
13405
  // Java Base64 / Hex / MessageDigest — encoded output is binary-safe
13092
13406
  // (cognium-dev #213 seventh slice). None of these carry attacker
13093
13407
  // shell/SQL/HTML metacharacters through the encoding — Base64 emits
@@ -13831,7 +14145,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13831
14145
  const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
13832
14146
  if (skipMethods.includes(method.name)) continue;
13833
14147
  for (const param of method.parameters) {
13834
- const isTaintable = param.type ? isInterproceduralTaintableType(param.type) : true;
14148
+ const isTaintable = param.type ? isInterproceduralTaintableType(param.type, language) : true;
13835
14149
  if (isTaintable) {
13836
14150
  const paramLine = param.line ?? method.start_line;
13837
14151
  sources.push({
@@ -13854,7 +14168,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13854
14168
  // interprocedural_param at the method decl line instead of the
13855
14169
  // real HTTP source, breaking downstream sink-type filters
13856
14170
  // (regressed #78, #92.1, #105 FP-31, #215 recall).
13857
- ...language === "java" ? { variable: param.name } : {}
14171
+ ...language === "java" || language === "csharp" ? { variable: param.name } : {}
13858
14172
  });
13859
14173
  }
13860
14174
  }
@@ -13951,7 +14265,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13951
14265
  }
13952
14266
  return result;
13953
14267
  }
13954
- function isInterproceduralTaintableType(typeName) {
14268
+ function isInterproceduralTaintableType(typeName, language) {
13955
14269
  const baseType = typeName.split("<")[0].trim();
13956
14270
  const excludedTypes = [
13957
14271
  // Servlet framework - taint comes from specific methods, not the parameter itself
@@ -14024,6 +14338,26 @@ function isInterproceduralTaintableType(typeName) {
14024
14338
  if (taintableTypes.includes(baseType)) {
14025
14339
  return true;
14026
14340
  }
14341
+ if (language === "csharp") {
14342
+ const csharpTaintable = [
14343
+ "string",
14344
+ "object",
14345
+ "IEnumerable",
14346
+ "ICollection",
14347
+ "IList",
14348
+ "Dictionary",
14349
+ "IDictionary",
14350
+ "IReadOnlyList",
14351
+ "IReadOnlyCollection",
14352
+ // Byte/stream payloads carry deserialization / upload input.
14353
+ "Stream",
14354
+ "byte[]",
14355
+ "Byte[]",
14356
+ "MemoryStream"
14357
+ ];
14358
+ const leaf = baseType.split(".").pop() ?? baseType;
14359
+ if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
14360
+ }
14027
14361
  if (typeName.endsWith("[]")) {
14028
14362
  const elementType = typeName.slice(0, -2);
14029
14363
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {