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
@@ -4336,8 +4336,84 @@ function extractTypes(tree, cache, language) {
4336
4336
  if (isJavaScript) {
4337
4337
  return extractJavaScriptTypes(tree, cache);
4338
4338
  }
4339
+ if (effectiveLanguage === "csharp") {
4340
+ return extractCSharpTypes(tree, cache);
4341
+ }
4339
4342
  return extractJavaTypes(tree, cache);
4340
4343
  }
4344
+ function extractCSharpTypes(tree, cache) {
4345
+ const types = [];
4346
+ const KINDS = {
4347
+ class_declaration: "class",
4348
+ record_declaration: "class",
4349
+ struct_declaration: "class",
4350
+ interface_declaration: "interface",
4351
+ enum_declaration: "enum"
4352
+ };
4353
+ for (const kindNode of Object.keys(KINDS)) {
4354
+ for (const node of getNodesFromCache(tree.rootNode, kindNode, cache)) {
4355
+ const nameNode = node.childForFieldName("name");
4356
+ const body2 = node.childForFieldName("body");
4357
+ const methods = [];
4358
+ if (body2) {
4359
+ for (let i2 = 0; i2 < body2.childCount; i2++) {
4360
+ const m = body2.child(i2);
4361
+ if (!m || m.type !== "method_declaration") continue;
4362
+ const mName = m.childForFieldName("name");
4363
+ const paramList = m.childForFieldName("parameters");
4364
+ const parameters = [];
4365
+ if (paramList) {
4366
+ for (let j = 0; j < paramList.childCount; j++) {
4367
+ const pnode = paramList.child(j);
4368
+ if (!pnode || pnode.type !== "parameter") continue;
4369
+ const pName = pnode.childForFieldName("name");
4370
+ const pType = pnode.childForFieldName("type");
4371
+ if (pName) {
4372
+ parameters.push({
4373
+ name: getNodeText(pName),
4374
+ type: pType ? getNodeText(pType) : null,
4375
+ annotations: [],
4376
+ line: pnode.startPosition.row + 1
4377
+ });
4378
+ }
4379
+ }
4380
+ }
4381
+ const returns = m.childForFieldName("returns");
4382
+ methods.push({
4383
+ name: mName ? getNodeText(mName) : "unknown",
4384
+ return_type: returns ? getNodeText(returns) : null,
4385
+ parameters,
4386
+ annotations: [],
4387
+ modifiers: extractCSharpModifiers(m),
4388
+ start_line: m.startPosition.row + 1,
4389
+ end_line: m.endPosition.row + 1
4390
+ });
4391
+ }
4392
+ }
4393
+ types.push({
4394
+ name: nameNode ? getNodeText(nameNode) : "anonymous",
4395
+ kind: KINDS[kindNode],
4396
+ package: null,
4397
+ extends: null,
4398
+ implements: [],
4399
+ annotations: [],
4400
+ methods,
4401
+ fields: [],
4402
+ start_line: node.startPosition.row + 1,
4403
+ end_line: node.endPosition.row + 1
4404
+ });
4405
+ }
4406
+ }
4407
+ return types;
4408
+ }
4409
+ function extractCSharpModifiers(node) {
4410
+ const mods = [];
4411
+ for (let i2 = 0; i2 < node.childCount; i2++) {
4412
+ const c = node.child(i2);
4413
+ if (c && c.type === "modifier") mods.push(getNodeText(c));
4414
+ }
4415
+ return mods;
4416
+ }
4341
4417
  function extractJavaTypes(tree, cache) {
4342
4418
  const types = [];
4343
4419
  const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
@@ -5796,6 +5872,9 @@ function extractCalls(tree, cache, language) {
5796
5872
  if (isJavaScript) {
5797
5873
  return extractJavaScriptCalls(tree, cache);
5798
5874
  }
5875
+ if (detectedLanguage === "csharp") {
5876
+ return extractCSharpCalls(tree, cache);
5877
+ }
5799
5878
  const context = buildResolutionContext(tree, cache);
5800
5879
  const invocations = getNodesFromCache(tree.rootNode, "method_invocation", cache);
5801
5880
  for (const inv of invocations) {
@@ -5810,6 +5889,107 @@ function extractCalls(tree, cache, language) {
5810
5889
  }
5811
5890
  return calls;
5812
5891
  }
5892
+ function buildCSharpReceiverTypeMap(tree, cache) {
5893
+ const map = /* @__PURE__ */ new Map();
5894
+ const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
5895
+ for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
5896
+ const typeNode = vd.childForFieldName("type");
5897
+ const declaredType = typeNode ? getNodeText(typeNode) : null;
5898
+ for (let i2 = 0; i2 < vd.childCount; i2++) {
5899
+ const decl = vd.child(i2);
5900
+ if (!decl || decl.type !== "variable_declarator") continue;
5901
+ const nameNode = decl.childForFieldName("name");
5902
+ if (nameNode?.type !== "identifier") continue;
5903
+ let t = declaredType && declaredType !== "var" ? declaredType : null;
5904
+ if (!t) {
5905
+ const oce = findFirstDescendant(decl, "object_creation_expression");
5906
+ const oceType = oce?.childForFieldName("type");
5907
+ if (oceType) t = getNodeText(oceType);
5908
+ }
5909
+ if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
5910
+ }
5911
+ }
5912
+ return map;
5913
+ }
5914
+ function findFirstDescendant(node, type) {
5915
+ for (let i2 = 0; i2 < node.childCount; i2++) {
5916
+ const c = node.child(i2);
5917
+ if (!c) continue;
5918
+ if (c.type === type) return c;
5919
+ const found = findFirstDescendant(c, type);
5920
+ if (found) return found;
5921
+ }
5922
+ return null;
5923
+ }
5924
+ function extractCSharpCalls(tree, cache) {
5925
+ const calls = [];
5926
+ const typeMap = buildCSharpReceiverTypeMap(tree, cache);
5927
+ const invocations = getNodesFromCache(tree.rootNode, "invocation_expression", cache);
5928
+ for (const inv of invocations) {
5929
+ const fn = inv.childForFieldName("function");
5930
+ let methodName = "unknown";
5931
+ let receiver = null;
5932
+ if (fn?.type === "member_access_expression") {
5933
+ const nameNode = fn.childForFieldName("name");
5934
+ const exprNode = fn.childForFieldName("expression");
5935
+ methodName = nameNode ? getNodeText(nameNode) : "unknown";
5936
+ receiver = exprNode ? getNodeText(exprNode) : null;
5937
+ } else if (fn) {
5938
+ methodName = getNodeText(fn);
5939
+ }
5940
+ const argsNode = inv.childForFieldName("arguments");
5941
+ calls.push({
5942
+ method_name: methodName,
5943
+ receiver,
5944
+ receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
5945
+ receiver_type_fqn: null,
5946
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
5947
+ location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
5948
+ in_method: findEnclosingMethod(inv)
5949
+ });
5950
+ }
5951
+ const objectCreations = getNodesFromCache(tree.rootNode, "object_creation_expression", cache);
5952
+ for (const creation of objectCreations) {
5953
+ const typeNode = creation.childForFieldName("type");
5954
+ const argsNode = creation.childForFieldName("arguments");
5955
+ calls.push({
5956
+ method_name: typeNode ? getNodeText(typeNode) : "unknown",
5957
+ receiver: null,
5958
+ receiver_type: null,
5959
+ receiver_type_fqn: null,
5960
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
5961
+ location: { line: creation.startPosition.row + 1, column: creation.startPosition.column },
5962
+ in_method: findEnclosingMethod(creation),
5963
+ is_constructor: true
5964
+ });
5965
+ }
5966
+ return calls;
5967
+ }
5968
+ function extractCSharpArguments(argsNode) {
5969
+ const args2 = [];
5970
+ let position = 0;
5971
+ for (let i2 = 0; i2 < argsNode.childCount; i2++) {
5972
+ const child = argsNode.child(i2);
5973
+ if (!child || child.type !== "argument") continue;
5974
+ let expr = null;
5975
+ for (let j = child.childCount - 1; j >= 0; j--) {
5976
+ const c = child.child(j);
5977
+ if (c && c.isNamed) {
5978
+ expr = c;
5979
+ break;
5980
+ }
5981
+ }
5982
+ const text = expr ? getNodeText(expr) : getNodeText(child);
5983
+ args2.push({
5984
+ position: position++,
5985
+ expression: text,
5986
+ variable: expr?.type === "identifier" ? text : null,
5987
+ literal: expr?.type === "string_literal" ? text : null,
5988
+ value: null
5989
+ });
5990
+ }
5991
+ return args2;
5992
+ }
5813
5993
  function extractJavaScriptCalls(tree, cache) {
5814
5994
  const calls = [];
5815
5995
  const context = buildJSResolutionContext(tree, cache);
@@ -8912,8 +9092,72 @@ function buildDFG(tree, cache, language) {
8912
9092
  if (effectiveLanguage === "go") {
8913
9093
  return buildGoDFG(tree);
8914
9094
  }
9095
+ if (effectiveLanguage === "csharp") {
9096
+ return buildCSharpDFG(tree, cache);
9097
+ }
8915
9098
  return buildJavaDFG(tree, cache);
8916
9099
  }
9100
+ function buildCSharpDFG(tree, cache) {
9101
+ const defs = [];
9102
+ const uses = [];
9103
+ let defIdCounter = 1;
9104
+ let useIdCounter = 1;
9105
+ const scopeStack = [/* @__PURE__ */ new Map()];
9106
+ const methods = [
9107
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
9108
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
9109
+ ...getNodesFromCache(tree.rootNode, "local_function_statement", cache)
9110
+ ];
9111
+ for (const method of methods) {
9112
+ scopeStack.push(/* @__PURE__ */ new Map());
9113
+ const params = method.childForFieldName("parameters");
9114
+ if (params) {
9115
+ for (const def of extractParameterDefs(params, defIdCounter)) {
9116
+ defs.push(def);
9117
+ currentScope(scopeStack).set(def.variable, def.id);
9118
+ defIdCounter++;
9119
+ }
9120
+ }
9121
+ const body2 = method.childForFieldName("body");
9122
+ if (body2) {
9123
+ for (const decl of findNodes(body2, "variable_declarator")) {
9124
+ const nameNode = decl.childForFieldName("name");
9125
+ if (nameNode?.type === "identifier") {
9126
+ const name2 = getNodeText(nameNode);
9127
+ const def = { id: defIdCounter++, variable: name2, line: decl.startPosition.row + 1, kind: "local" };
9128
+ defs.push(def);
9129
+ currentScope(scopeStack).set(name2, def.id);
9130
+ }
9131
+ }
9132
+ for (const asn of findNodes(body2, "assignment_expression")) {
9133
+ const left = asn.childForFieldName("left");
9134
+ if (left?.type === "identifier") {
9135
+ const name2 = getNodeText(left);
9136
+ const def = { id: defIdCounter++, variable: name2, line: asn.startPosition.row + 1, kind: "local" };
9137
+ defs.push(def);
9138
+ currentScope(scopeStack).set(name2, def.id);
9139
+ }
9140
+ }
9141
+ const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
9142
+ uses.push(...bodyUses.uses);
9143
+ useIdCounter = bodyUses.nextId;
9144
+ }
9145
+ scopeStack.pop();
9146
+ }
9147
+ const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
9148
+ for (const cls of classes) {
9149
+ const body2 = cls.childForFieldName("body");
9150
+ if (body2) {
9151
+ for (const def of extractFieldDefs(body2, defIdCounter)) {
9152
+ defs.push(def);
9153
+ currentScope(scopeStack).set(def.variable, def.id);
9154
+ defIdCounter++;
9155
+ }
9156
+ }
9157
+ }
9158
+ const chains = computeChains(defs, uses);
9159
+ return { defs, uses, chains };
9160
+ }
8917
9161
  function buildJavaDFG(tree, cache) {
8918
9162
  const defs = [];
8919
9163
  const uses = [];
@@ -9376,7 +9620,7 @@ function extractParameterDefs(params, startId) {
9376
9620
  for (let i2 = 0; i2 < params.childCount; i2++) {
9377
9621
  const param = params.child(i2);
9378
9622
  if (!param) continue;
9379
- if (param.type === "formal_parameter" || param.type === "spread_parameter") {
9623
+ if (param.type === "formal_parameter" || param.type === "spread_parameter" || param.type === "parameter") {
9380
9624
  const nameNode = param.childForFieldName("name");
9381
9625
  if (nameNode) {
9382
9626
  defs.push({
@@ -12815,6 +13059,67 @@ var DEFAULT_SINKS = [
12815
13059
  { method: "put", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
12816
13060
  { method: "patch", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
12817
13061
  { method: "head", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13062
+ // C# SQLi (Phase-0 spike — ADO.NET). `new SqlCommand(sql, conn)` builds the
13063
+ // command from a raw string; the tainted SQL is arg[0] of the constructor.
13064
+ { method: "SqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13065
+ { method: "NpgsqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13066
+ { method: "MySqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13067
+ { method: "SqliteCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13068
+ { method: "OracleCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13069
+ { method: "ExecuteSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13070
+ { method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13071
+ { method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13072
+ { method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13073
+ // C# command injection — Process.Start / ProcessStartInfo (CWE-78).
13074
+ { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13075
+ { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13076
+ // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13077
+ { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13078
+ { method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13079
+ { method: "ReadAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13080
+ { method: "WriteAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13081
+ { method: "WriteAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13082
+ { method: "AppendAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13083
+ { method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13084
+ { method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13085
+ { method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13086
+ { method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13087
+ { method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13088
+ // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13089
+ { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13090
+ { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13091
+ { method: "PutAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13092
+ { method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13093
+ { method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13094
+ { method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13095
+ { method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13096
+ { method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13097
+ { method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13098
+ { method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13099
+ { method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
13100
+ { method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13101
+ // C# code injection — dynamic script/assembly loading (CWE-94).
13102
+ { method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13103
+ { method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13104
+ // C# insecure deserialization — BinaryFormatter et al. (CWE-502).
13105
+ { method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13106
+ { method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13107
+ // C# XSS — raw HTML output (CWE-79).
13108
+ { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13109
+ { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13110
+ { method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13111
+ // C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
13112
+ // filter is the constructor argument.
13113
+ { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13114
+ { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13115
+ // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13116
+ { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13117
+ { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13118
+ { method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13119
+ // C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
13120
+ { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13121
+ { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13122
+ { method: "Load", class: "XDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
12818
13123
  // Python SQLi — asyncpg Connection.*
12819
13124
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
12820
13125
  { method: "fetch", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -13022,6 +13327,15 @@ var DEFAULT_SANITIZERS = [
13022
13327
  { method: "escapeCsv", removes: ["xss"] },
13023
13328
  // OWASP HTML sanitizer (`com.googlecode.owasp-java-html-sanitizer`).
13024
13329
  { method: "sanitize", class: "PolicyFactory", removes: ["xss"] },
13330
+ // C# sanitizers (Phase-1). HTML encoders neutralise XSS; Path.GetFileName
13331
+ // strips any directory component (basename) so a traversal payload cannot
13332
+ // escape the target directory. Method names are C#-distinctive.
13333
+ { method: "HtmlEncode", removes: ["xss"] },
13334
+ // HttpUtility / WebUtility
13335
+ { method: "JavaScriptStringEncode", removes: ["xss"] },
13336
+ // HttpUtility
13337
+ { method: "Encode", class: "HtmlEncoder", removes: ["xss"] },
13338
+ { method: "GetFileName", class: "Path", removes: ["path_traversal"] },
13025
13339
  // Java Base64 / Hex / MessageDigest — encoded output is binary-safe
13026
13340
  // (cognium-dev #213 seventh slice). None of these carry attacker
13027
13341
  // shell/SQL/HTML metacharacters through the encoding — Base64 emits
@@ -13765,7 +14079,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13765
14079
  const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
13766
14080
  if (skipMethods.includes(method.name)) continue;
13767
14081
  for (const param of method.parameters) {
13768
- const isTaintable = param.type ? isInterproceduralTaintableType(param.type) : true;
14082
+ const isTaintable = param.type ? isInterproceduralTaintableType(param.type, language) : true;
13769
14083
  if (isTaintable) {
13770
14084
  const paramLine = param.line ?? method.start_line;
13771
14085
  sources.push({
@@ -13788,7 +14102,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13788
14102
  // interprocedural_param at the method decl line instead of the
13789
14103
  // real HTTP source, breaking downstream sink-type filters
13790
14104
  // (regressed #78, #92.1, #105 FP-31, #215 recall).
13791
- ...language === "java" ? { variable: param.name } : {}
14105
+ ...language === "java" || language === "csharp" ? { variable: param.name } : {}
13792
14106
  });
13793
14107
  }
13794
14108
  }
@@ -13885,7 +14199,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13885
14199
  }
13886
14200
  return result;
13887
14201
  }
13888
- function isInterproceduralTaintableType(typeName) {
14202
+ function isInterproceduralTaintableType(typeName, language) {
13889
14203
  const baseType = typeName.split("<")[0].trim();
13890
14204
  const excludedTypes = [
13891
14205
  // Servlet framework - taint comes from specific methods, not the parameter itself
@@ -13958,6 +14272,26 @@ function isInterproceduralTaintableType(typeName) {
13958
14272
  if (taintableTypes.includes(baseType)) {
13959
14273
  return true;
13960
14274
  }
14275
+ if (language === "csharp") {
14276
+ const csharpTaintable = [
14277
+ "string",
14278
+ "object",
14279
+ "IEnumerable",
14280
+ "ICollection",
14281
+ "IList",
14282
+ "Dictionary",
14283
+ "IDictionary",
14284
+ "IReadOnlyList",
14285
+ "IReadOnlyCollection",
14286
+ // Byte/stream payloads carry deserialization / upload input.
14287
+ "Stream",
14288
+ "byte[]",
14289
+ "Byte[]",
14290
+ "MemoryStream"
14291
+ ];
14292
+ const leaf = baseType.split(".").pop() ?? baseType;
14293
+ if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
14294
+ }
13961
14295
  if (typeName.endsWith("[]")) {
13962
14296
  const elementType = typeName.slice(0, -2);
13963
14297
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -1 +1 @@
1
- {"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/calls.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAgC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA8EvG;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAgDzF"}
1
+ {"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/calls.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAgC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA8EvG;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAoDzF"}
@@ -75,6 +75,9 @@ export function extractCalls(tree, cache, language) {
75
75
  if (isJavaScript) {
76
76
  return extractJavaScriptCalls(tree, cache);
77
77
  }
78
+ if (detectedLanguage === 'csharp') {
79
+ return extractCSharpCalls(tree, cache);
80
+ }
78
81
  // Build resolution context for Java
79
82
  const context = buildResolutionContext(tree, cache);
80
83
  // Find all method invocations
@@ -95,6 +98,136 @@ export function extractCalls(tree, cache, language) {
95
98
  /**
96
99
  * Extract all function/method calls from a JavaScript/TypeScript tree.
97
100
  */
101
+ /**
102
+ * C# call extraction (Phase-0 spike — cognium-dev C#/.NET epic).
103
+ *
104
+ * Handles `invocation_expression` (`cmd.ExecuteReader()`) and
105
+ * `object_creation_expression` (`new SqlCommand(q, conn)`). C# node/field shapes
106
+ * differ from Java (invocation_expression.function → member_access_expression
107
+ * with `name`/`expression` fields; argument_list wraps each arg in an `argument`
108
+ * node), so this cannot reuse `extractCallInfo`. Resolution/type inference is
109
+ * left null for the spike — sink matching keys on method name + receiver text.
110
+ */
111
+ /**
112
+ * Map local/field variable names → their simple type (cognium-dev C#/.NET
113
+ * Phase-1). Enables class-scoped C# sinks (`BinaryFormatter.Deserialize`) to
114
+ * match an instance receiver (`bf.Deserialize(s)`), which the receiver-text
115
+ * match only covers for static calls. Type comes from the declaration
116
+ * (`HttpClient c = …`) or, for `var`, from the `new T(…)` initializer.
117
+ */
118
+ function buildCSharpReceiverTypeMap(tree, cache) {
119
+ const map = new Map();
120
+ const simple = (t) => t.replace(/<[^>]*>/g, '').split('.').pop()?.trim() ?? t;
121
+ for (const vd of getNodesFromCache(tree.rootNode, 'variable_declaration', cache)) {
122
+ const typeNode = vd.childForFieldName('type');
123
+ const declaredType = typeNode ? getNodeText(typeNode) : null;
124
+ for (let i = 0; i < vd.childCount; i++) {
125
+ const decl = vd.child(i);
126
+ if (!decl || decl.type !== 'variable_declarator')
127
+ continue;
128
+ const nameNode = decl.childForFieldName('name');
129
+ if (nameNode?.type !== 'identifier')
130
+ continue;
131
+ let t = declaredType && declaredType !== 'var' ? declaredType : null;
132
+ if (!t) {
133
+ const oce = findFirstDescendant(decl, 'object_creation_expression');
134
+ const oceType = oce?.childForFieldName('type');
135
+ if (oceType)
136
+ t = getNodeText(oceType);
137
+ }
138
+ if (t && t !== 'var')
139
+ map.set(getNodeText(nameNode), simple(t));
140
+ }
141
+ }
142
+ return map;
143
+ }
144
+ /** First descendant of `node` with the given type (pre-order). */
145
+ function findFirstDescendant(node, type) {
146
+ for (let i = 0; i < node.childCount; i++) {
147
+ const c = node.child(i);
148
+ if (!c)
149
+ continue;
150
+ if (c.type === type)
151
+ return c;
152
+ const found = findFirstDescendant(c, type);
153
+ if (found)
154
+ return found;
155
+ }
156
+ return null;
157
+ }
158
+ function extractCSharpCalls(tree, cache) {
159
+ const calls = [];
160
+ const typeMap = buildCSharpReceiverTypeMap(tree, cache);
161
+ const invocations = getNodesFromCache(tree.rootNode, 'invocation_expression', cache);
162
+ for (const inv of invocations) {
163
+ const fn = inv.childForFieldName('function');
164
+ let methodName = 'unknown';
165
+ let receiver = null;
166
+ if (fn?.type === 'member_access_expression') {
167
+ const nameNode = fn.childForFieldName('name');
168
+ const exprNode = fn.childForFieldName('expression');
169
+ methodName = nameNode ? getNodeText(nameNode) : 'unknown';
170
+ receiver = exprNode ? getNodeText(exprNode) : null;
171
+ }
172
+ else if (fn) {
173
+ methodName = getNodeText(fn);
174
+ }
175
+ const argsNode = inv.childForFieldName('arguments');
176
+ calls.push({
177
+ method_name: methodName,
178
+ receiver,
179
+ receiver_type: receiver ? (typeMap.get(receiver) ?? null) : null,
180
+ receiver_type_fqn: null,
181
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
182
+ location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
183
+ in_method: findEnclosingMethod(inv),
184
+ });
185
+ }
186
+ const objectCreations = getNodesFromCache(tree.rootNode, 'object_creation_expression', cache);
187
+ for (const creation of objectCreations) {
188
+ const typeNode = creation.childForFieldName('type');
189
+ const argsNode = creation.childForFieldName('arguments');
190
+ calls.push({
191
+ method_name: typeNode ? getNodeText(typeNode) : 'unknown',
192
+ receiver: null,
193
+ receiver_type: null,
194
+ receiver_type_fqn: null,
195
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
196
+ location: { line: creation.startPosition.row + 1, column: creation.startPosition.column },
197
+ in_method: findEnclosingMethod(creation),
198
+ is_constructor: true,
199
+ });
200
+ }
201
+ return calls;
202
+ }
203
+ /** Extract args from a C# `argument_list` (each child is an `argument` wrapper). */
204
+ function extractCSharpArguments(argsNode) {
205
+ const args = [];
206
+ let position = 0;
207
+ for (let i = 0; i < argsNode.childCount; i++) {
208
+ const child = argsNode.child(i);
209
+ if (!child || child.type !== 'argument')
210
+ continue;
211
+ // The argument's payload is its last named child (skips `ref`/`out`/`in` kws).
212
+ let expr = null;
213
+ for (let j = child.childCount - 1; j >= 0; j--) {
214
+ const c = child.child(j);
215
+ if (c && c.isNamed) {
216
+ expr = c;
217
+ break;
218
+ }
219
+ }
220
+ const text = expr ? getNodeText(expr) : getNodeText(child);
221
+ args.push({
222
+ position: position++,
223
+ expression: text,
224
+ variable: expr?.type === 'identifier' ? text : null,
225
+ literal: expr?.type === 'string_literal' ? text : null,
226
+ value: null,
227
+ });
228
+ }
229
+ return args;
230
+ }
98
231
  function extractJavaScriptCalls(tree, cache) {
99
232
  const calls = [];
100
233
  // Build JS resolution context