circle-ir 3.216.0 → 4.0.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 (40) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +68 -0
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  5. package/dist/analysis/passes/language-sources-pass.js +42 -0
  6. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  7. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  8. package/dist/analysis/passes/sink-filter-pass.js +178 -0
  9. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  10. package/dist/analysis/passes/taint-propagation-pass.js +1 -1
  11. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  12. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  13. package/dist/analysis/taint-matcher.js +18 -3
  14. package/dist/analysis/taint-matcher.js.map +1 -1
  15. package/dist/browser/circle-ir.js +530 -5
  16. package/dist/core/circle-ir-core.cjs +338 -4
  17. package/dist/core/circle-ir-core.js +338 -4
  18. package/dist/core/extractors/calls.d.ts.map +1 -1
  19. package/dist/core/extractors/calls.js +133 -0
  20. package/dist/core/extractors/calls.js.map +1 -1
  21. package/dist/core/extractors/dfg.d.ts.map +1 -1
  22. package/dist/core/extractors/dfg.js +86 -1
  23. package/dist/core/extractors/dfg.js.map +1 -1
  24. package/dist/core/extractors/types.d.ts.map +1 -1
  25. package/dist/core/extractors/types.js +89 -0
  26. package/dist/core/extractors/types.js.map +1 -1
  27. package/dist/languages/plugins/csharp.d.ts +35 -0
  28. package/dist/languages/plugins/csharp.d.ts.map +1 -0
  29. package/dist/languages/plugins/csharp.js +72 -0
  30. package/dist/languages/plugins/csharp.js.map +1 -0
  31. package/dist/languages/plugins/index.d.ts +1 -0
  32. package/dist/languages/plugins/index.d.ts.map +1 -1
  33. package/dist/languages/plugins/index.js +3 -0
  34. package/dist/languages/plugins/index.js.map +1 -1
  35. package/dist/languages/types.d.ts +1 -1
  36. package/dist/languages/types.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/wasm/tree-sitter-csharp.wasm +0 -0
  40. package/package.json +1 -1
@@ -4356,8 +4356,84 @@ function extractTypes(tree, cache, language) {
4356
4356
  if (isJavaScript) {
4357
4357
  return extractJavaScriptTypes(tree, cache);
4358
4358
  }
4359
+ if (effectiveLanguage === "csharp") {
4360
+ return extractCSharpTypes(tree, cache);
4361
+ }
4359
4362
  return extractJavaTypes(tree, cache);
4360
4363
  }
4364
+ function extractCSharpTypes(tree, cache) {
4365
+ const types = [];
4366
+ const KINDS = {
4367
+ class_declaration: "class",
4368
+ record_declaration: "class",
4369
+ struct_declaration: "class",
4370
+ interface_declaration: "interface",
4371
+ enum_declaration: "enum"
4372
+ };
4373
+ for (const kindNode of Object.keys(KINDS)) {
4374
+ for (const node of getNodesFromCache(tree.rootNode, kindNode, cache)) {
4375
+ const nameNode = node.childForFieldName("name");
4376
+ const body2 = node.childForFieldName("body");
4377
+ const methods = [];
4378
+ if (body2) {
4379
+ for (let i2 = 0; i2 < body2.childCount; i2++) {
4380
+ const m = body2.child(i2);
4381
+ if (!m || m.type !== "method_declaration") continue;
4382
+ const mName = m.childForFieldName("name");
4383
+ const paramList = m.childForFieldName("parameters");
4384
+ const parameters = [];
4385
+ if (paramList) {
4386
+ for (let j = 0; j < paramList.childCount; j++) {
4387
+ const pnode = paramList.child(j);
4388
+ if (!pnode || pnode.type !== "parameter") continue;
4389
+ const pName = pnode.childForFieldName("name");
4390
+ const pType = pnode.childForFieldName("type");
4391
+ if (pName) {
4392
+ parameters.push({
4393
+ name: getNodeText(pName),
4394
+ type: pType ? getNodeText(pType) : null,
4395
+ annotations: [],
4396
+ line: pnode.startPosition.row + 1
4397
+ });
4398
+ }
4399
+ }
4400
+ }
4401
+ const returns = m.childForFieldName("returns");
4402
+ methods.push({
4403
+ name: mName ? getNodeText(mName) : "unknown",
4404
+ return_type: returns ? getNodeText(returns) : null,
4405
+ parameters,
4406
+ annotations: [],
4407
+ modifiers: extractCSharpModifiers(m),
4408
+ start_line: m.startPosition.row + 1,
4409
+ end_line: m.endPosition.row + 1
4410
+ });
4411
+ }
4412
+ }
4413
+ types.push({
4414
+ name: nameNode ? getNodeText(nameNode) : "anonymous",
4415
+ kind: KINDS[kindNode],
4416
+ package: null,
4417
+ extends: null,
4418
+ implements: [],
4419
+ annotations: [],
4420
+ methods,
4421
+ fields: [],
4422
+ start_line: node.startPosition.row + 1,
4423
+ end_line: node.endPosition.row + 1
4424
+ });
4425
+ }
4426
+ }
4427
+ return types;
4428
+ }
4429
+ function extractCSharpModifiers(node) {
4430
+ const mods = [];
4431
+ for (let i2 = 0; i2 < node.childCount; i2++) {
4432
+ const c = node.child(i2);
4433
+ if (c && c.type === "modifier") mods.push(getNodeText(c));
4434
+ }
4435
+ return mods;
4436
+ }
4361
4437
  function extractJavaTypes(tree, cache) {
4362
4438
  const types = [];
4363
4439
  const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
@@ -5816,6 +5892,9 @@ function extractCalls(tree, cache, language) {
5816
5892
  if (isJavaScript) {
5817
5893
  return extractJavaScriptCalls(tree, cache);
5818
5894
  }
5895
+ if (detectedLanguage === "csharp") {
5896
+ return extractCSharpCalls(tree, cache);
5897
+ }
5819
5898
  const context = buildResolutionContext(tree, cache);
5820
5899
  const invocations = getNodesFromCache(tree.rootNode, "method_invocation", cache);
5821
5900
  for (const inv of invocations) {
@@ -5830,6 +5909,107 @@ function extractCalls(tree, cache, language) {
5830
5909
  }
5831
5910
  return calls;
5832
5911
  }
5912
+ function buildCSharpReceiverTypeMap(tree, cache) {
5913
+ const map = /* @__PURE__ */ new Map();
5914
+ const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
5915
+ for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
5916
+ const typeNode = vd.childForFieldName("type");
5917
+ const declaredType = typeNode ? getNodeText(typeNode) : null;
5918
+ for (let i2 = 0; i2 < vd.childCount; i2++) {
5919
+ const decl = vd.child(i2);
5920
+ if (!decl || decl.type !== "variable_declarator") continue;
5921
+ const nameNode = decl.childForFieldName("name");
5922
+ if (nameNode?.type !== "identifier") continue;
5923
+ let t = declaredType && declaredType !== "var" ? declaredType : null;
5924
+ if (!t) {
5925
+ const oce = findFirstDescendant(decl, "object_creation_expression");
5926
+ const oceType = oce?.childForFieldName("type");
5927
+ if (oceType) t = getNodeText(oceType);
5928
+ }
5929
+ if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
5930
+ }
5931
+ }
5932
+ return map;
5933
+ }
5934
+ function findFirstDescendant(node, type) {
5935
+ for (let i2 = 0; i2 < node.childCount; i2++) {
5936
+ const c = node.child(i2);
5937
+ if (!c) continue;
5938
+ if (c.type === type) return c;
5939
+ const found = findFirstDescendant(c, type);
5940
+ if (found) return found;
5941
+ }
5942
+ return null;
5943
+ }
5944
+ function extractCSharpCalls(tree, cache) {
5945
+ const calls = [];
5946
+ const typeMap = buildCSharpReceiverTypeMap(tree, cache);
5947
+ const invocations = getNodesFromCache(tree.rootNode, "invocation_expression", cache);
5948
+ for (const inv of invocations) {
5949
+ const fn = inv.childForFieldName("function");
5950
+ let methodName = "unknown";
5951
+ let receiver = null;
5952
+ if (fn?.type === "member_access_expression") {
5953
+ const nameNode = fn.childForFieldName("name");
5954
+ const exprNode = fn.childForFieldName("expression");
5955
+ methodName = nameNode ? getNodeText(nameNode) : "unknown";
5956
+ receiver = exprNode ? getNodeText(exprNode) : null;
5957
+ } else if (fn) {
5958
+ methodName = getNodeText(fn);
5959
+ }
5960
+ const argsNode = inv.childForFieldName("arguments");
5961
+ calls.push({
5962
+ method_name: methodName,
5963
+ receiver,
5964
+ receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
5965
+ receiver_type_fqn: null,
5966
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
5967
+ location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
5968
+ in_method: findEnclosingMethod(inv)
5969
+ });
5970
+ }
5971
+ const objectCreations = getNodesFromCache(tree.rootNode, "object_creation_expression", cache);
5972
+ for (const creation of objectCreations) {
5973
+ const typeNode = creation.childForFieldName("type");
5974
+ const argsNode = creation.childForFieldName("arguments");
5975
+ calls.push({
5976
+ method_name: typeNode ? getNodeText(typeNode) : "unknown",
5977
+ receiver: null,
5978
+ receiver_type: null,
5979
+ receiver_type_fqn: null,
5980
+ arguments: argsNode ? extractCSharpArguments(argsNode) : [],
5981
+ location: { line: creation.startPosition.row + 1, column: creation.startPosition.column },
5982
+ in_method: findEnclosingMethod(creation),
5983
+ is_constructor: true
5984
+ });
5985
+ }
5986
+ return calls;
5987
+ }
5988
+ function extractCSharpArguments(argsNode) {
5989
+ const args2 = [];
5990
+ let position = 0;
5991
+ for (let i2 = 0; i2 < argsNode.childCount; i2++) {
5992
+ const child = argsNode.child(i2);
5993
+ if (!child || child.type !== "argument") continue;
5994
+ let expr = null;
5995
+ for (let j = child.childCount - 1; j >= 0; j--) {
5996
+ const c = child.child(j);
5997
+ if (c && c.isNamed) {
5998
+ expr = c;
5999
+ break;
6000
+ }
6001
+ }
6002
+ const text = expr ? getNodeText(expr) : getNodeText(child);
6003
+ args2.push({
6004
+ position: position++,
6005
+ expression: text,
6006
+ variable: expr?.type === "identifier" ? text : null,
6007
+ literal: expr?.type === "string_literal" ? text : null,
6008
+ value: null
6009
+ });
6010
+ }
6011
+ return args2;
6012
+ }
5833
6013
  function extractJavaScriptCalls(tree, cache) {
5834
6014
  const calls = [];
5835
6015
  const context = buildJSResolutionContext(tree, cache);
@@ -8932,8 +9112,72 @@ function buildDFG(tree, cache, language) {
8932
9112
  if (effectiveLanguage === "go") {
8933
9113
  return buildGoDFG(tree);
8934
9114
  }
9115
+ if (effectiveLanguage === "csharp") {
9116
+ return buildCSharpDFG(tree, cache);
9117
+ }
8935
9118
  return buildJavaDFG(tree, cache);
8936
9119
  }
9120
+ function buildCSharpDFG(tree, cache) {
9121
+ const defs = [];
9122
+ const uses = [];
9123
+ let defIdCounter = 1;
9124
+ let useIdCounter = 1;
9125
+ const scopeStack = [/* @__PURE__ */ new Map()];
9126
+ const methods = [
9127
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
9128
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
9129
+ ...getNodesFromCache(tree.rootNode, "local_function_statement", cache)
9130
+ ];
9131
+ for (const method of methods) {
9132
+ scopeStack.push(/* @__PURE__ */ new Map());
9133
+ const params = method.childForFieldName("parameters");
9134
+ if (params) {
9135
+ for (const def of extractParameterDefs(params, defIdCounter)) {
9136
+ defs.push(def);
9137
+ currentScope(scopeStack).set(def.variable, def.id);
9138
+ defIdCounter++;
9139
+ }
9140
+ }
9141
+ const body2 = method.childForFieldName("body");
9142
+ if (body2) {
9143
+ for (const decl of findNodes(body2, "variable_declarator")) {
9144
+ const nameNode = decl.childForFieldName("name");
9145
+ if (nameNode?.type === "identifier") {
9146
+ const name2 = getNodeText(nameNode);
9147
+ const def = { id: defIdCounter++, variable: name2, line: decl.startPosition.row + 1, kind: "local" };
9148
+ defs.push(def);
9149
+ currentScope(scopeStack).set(name2, def.id);
9150
+ }
9151
+ }
9152
+ for (const asn of findNodes(body2, "assignment_expression")) {
9153
+ const left = asn.childForFieldName("left");
9154
+ if (left?.type === "identifier") {
9155
+ const name2 = getNodeText(left);
9156
+ const def = { id: defIdCounter++, variable: name2, line: asn.startPosition.row + 1, kind: "local" };
9157
+ defs.push(def);
9158
+ currentScope(scopeStack).set(name2, def.id);
9159
+ }
9160
+ }
9161
+ const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
9162
+ uses.push(...bodyUses.uses);
9163
+ useIdCounter = bodyUses.nextId;
9164
+ }
9165
+ scopeStack.pop();
9166
+ }
9167
+ const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
9168
+ for (const cls of classes) {
9169
+ const body2 = cls.childForFieldName("body");
9170
+ if (body2) {
9171
+ for (const def of extractFieldDefs(body2, defIdCounter)) {
9172
+ defs.push(def);
9173
+ currentScope(scopeStack).set(def.variable, def.id);
9174
+ defIdCounter++;
9175
+ }
9176
+ }
9177
+ }
9178
+ const chains = computeChains(defs, uses);
9179
+ return { defs, uses, chains };
9180
+ }
8937
9181
  function buildJavaDFG(tree, cache) {
8938
9182
  const defs = [];
8939
9183
  const uses = [];
@@ -9396,7 +9640,7 @@ function extractParameterDefs(params, startId) {
9396
9640
  for (let i2 = 0; i2 < params.childCount; i2++) {
9397
9641
  const param = params.child(i2);
9398
9642
  if (!param) continue;
9399
- if (param.type === "formal_parameter" || param.type === "spread_parameter") {
9643
+ if (param.type === "formal_parameter" || param.type === "spread_parameter" || param.type === "parameter") {
9400
9644
  const nameNode = param.childForFieldName("name");
9401
9645
  if (nameNode) {
9402
9646
  defs.push({
@@ -13486,6 +13730,67 @@ var DEFAULT_SINKS = [
13486
13730
  { method: "put", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13487
13731
  { method: "patch", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13488
13732
  { method: "head", class: "httpx", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
13733
+ // C# SQLi (Phase-0 spike — ADO.NET). `new SqlCommand(sql, conn)` builds the
13734
+ // command from a raw string; the tainted SQL is arg[0] of the constructor.
13735
+ { method: "SqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13736
+ { method: "NpgsqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13737
+ { method: "MySqlCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13738
+ { method: "SqliteCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13739
+ { method: "OracleCommand", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13740
+ { method: "ExecuteSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13741
+ { method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13742
+ { method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13743
+ { method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13744
+ // C# command injection — Process.Start / ProcessStartInfo (CWE-78).
13745
+ { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13746
+ { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13747
+ // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13748
+ { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13749
+ { method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13750
+ { method: "ReadAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13751
+ { method: "WriteAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13752
+ { method: "WriteAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13753
+ { method: "AppendAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13754
+ { method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13755
+ { method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13756
+ { method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13757
+ { method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13758
+ { method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13759
+ // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13760
+ { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13761
+ { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13762
+ { method: "PutAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13763
+ { method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13764
+ { method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13765
+ { method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13766
+ { method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13767
+ { method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13768
+ { method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13769
+ { method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13770
+ { method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
13771
+ { method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13772
+ // C# code injection — dynamic script/assembly loading (CWE-94).
13773
+ { method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13774
+ { method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13775
+ // C# insecure deserialization — BinaryFormatter et al. (CWE-502).
13776
+ { method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13777
+ { method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13778
+ // C# XSS — raw HTML output (CWE-79).
13779
+ { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13780
+ { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13781
+ { method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13782
+ // C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
13783
+ // filter is the constructor argument.
13784
+ { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13785
+ { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13786
+ // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13787
+ { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13788
+ { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13789
+ { method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13790
+ // C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
13791
+ { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13792
+ { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13793
+ { method: "Load", class: "XDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13489
13794
  // Python SQLi — asyncpg Connection.*
13490
13795
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
13491
13796
  { method: "fetch", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -13693,6 +13998,15 @@ var DEFAULT_SANITIZERS = [
13693
13998
  { method: "escapeCsv", removes: ["xss"] },
13694
13999
  // OWASP HTML sanitizer (`com.googlecode.owasp-java-html-sanitizer`).
13695
14000
  { method: "sanitize", class: "PolicyFactory", removes: ["xss"] },
14001
+ // C# sanitizers (Phase-1). HTML encoders neutralise XSS; Path.GetFileName
14002
+ // strips any directory component (basename) so a traversal payload cannot
14003
+ // escape the target directory. Method names are C#-distinctive.
14004
+ { method: "HtmlEncode", removes: ["xss"] },
14005
+ // HttpUtility / WebUtility
14006
+ { method: "JavaScriptStringEncode", removes: ["xss"] },
14007
+ // HttpUtility
14008
+ { method: "Encode", class: "HtmlEncoder", removes: ["xss"] },
14009
+ { method: "GetFileName", class: "Path", removes: ["path_traversal"] },
13696
14010
  // Java Base64 / Hex / MessageDigest — encoded output is binary-safe
13697
14011
  // (cognium-dev #213 seventh slice). None of these carry attacker
13698
14012
  // shell/SQL/HTML metacharacters through the encoding — Base64 emits
@@ -14531,7 +14845,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
14531
14845
  const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
14532
14846
  if (skipMethods.includes(method.name)) continue;
14533
14847
  for (const param of method.parameters) {
14534
- const isTaintable = param.type ? isInterproceduralTaintableType(param.type) : true;
14848
+ const isTaintable = param.type ? isInterproceduralTaintableType(param.type, language) : true;
14535
14849
  if (isTaintable) {
14536
14850
  const paramLine = param.line ?? method.start_line;
14537
14851
  sources.push({
@@ -14554,7 +14868,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
14554
14868
  // interprocedural_param at the method decl line instead of the
14555
14869
  // real HTTP source, breaking downstream sink-type filters
14556
14870
  // (regressed #78, #92.1, #105 FP-31, #215 recall).
14557
- ...language === "java" ? { variable: param.name } : {}
14871
+ ...language === "java" || language === "csharp" ? { variable: param.name } : {}
14558
14872
  });
14559
14873
  }
14560
14874
  }
@@ -14651,7 +14965,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
14651
14965
  }
14652
14966
  return result;
14653
14967
  }
14654
- function isInterproceduralTaintableType(typeName) {
14968
+ function isInterproceduralTaintableType(typeName, language) {
14655
14969
  const baseType = typeName.split("<")[0].trim();
14656
14970
  const excludedTypes = [
14657
14971
  // Servlet framework - taint comes from specific methods, not the parameter itself
@@ -14724,6 +15038,26 @@ function isInterproceduralTaintableType(typeName) {
14724
15038
  if (taintableTypes.includes(baseType)) {
14725
15039
  return true;
14726
15040
  }
15041
+ if (language === "csharp") {
15042
+ const csharpTaintable = [
15043
+ "string",
15044
+ "object",
15045
+ "IEnumerable",
15046
+ "ICollection",
15047
+ "IList",
15048
+ "Dictionary",
15049
+ "IDictionary",
15050
+ "IReadOnlyList",
15051
+ "IReadOnlyCollection",
15052
+ // Byte/stream payloads carry deserialization / upload input.
15053
+ "Stream",
15054
+ "byte[]",
15055
+ "Byte[]",
15056
+ "MemoryStream"
15057
+ ];
15058
+ const leaf = baseType.split(".").pop() ?? baseType;
15059
+ if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
15060
+ }
14727
15061
  if (typeName.endsWith("[]")) {
14728
15062
  const elementType = typeName.slice(0, -2);
14729
15063
  if (elementType === "String" || elementType === "Object" || elementType === "byte") {
@@ -25014,6 +25348,73 @@ var GoPlugin = class extends BaseLanguagePlugin {
25014
25348
  }
25015
25349
  };
25016
25350
 
25351
+ // src/languages/plugins/csharp.ts
25352
+ var CSharpPlugin = class extends BaseLanguagePlugin {
25353
+ id = "csharp";
25354
+ name = "C#";
25355
+ extensions = [".cs"];
25356
+ wasmPath = "tree-sitter-csharp.wasm";
25357
+ nodeTypes = {
25358
+ // Type declarations
25359
+ classDeclaration: ["class_declaration", "record_declaration", "struct_declaration"],
25360
+ interfaceDeclaration: ["interface_declaration"],
25361
+ enumDeclaration: ["enum_declaration"],
25362
+ functionDeclaration: [],
25363
+ methodDeclaration: ["method_declaration", "constructor_declaration", "local_function_statement"],
25364
+ // Expressions — NB these diverge from Java (invocation_expression vs
25365
+ // method_invocation, local_declaration_statement vs local_variable_declaration).
25366
+ methodCall: ["invocation_expression"],
25367
+ functionCall: [],
25368
+ assignment: ["assignment_expression"],
25369
+ variableDeclaration: ["local_declaration_statement", "field_declaration", "variable_declaration"],
25370
+ // Parameters and arguments
25371
+ parameter: ["parameter"],
25372
+ argument: ["argument_list"],
25373
+ // Attributes (C# analogue of annotations/decorators)
25374
+ annotation: ["attribute", "attribute_list"],
25375
+ decorator: [],
25376
+ // Imports
25377
+ importStatement: ["using_directive"],
25378
+ // Control flow
25379
+ ifStatement: ["if_statement"],
25380
+ forStatement: ["for_statement", "for_each_statement"],
25381
+ whileStatement: ["while_statement"],
25382
+ tryStatement: ["try_statement"],
25383
+ returnStatement: ["return_statement"]
25384
+ };
25385
+ detectFramework(context) {
25386
+ for (const imp of context.imports) {
25387
+ const path = imp.from_package || imp.imported_name;
25388
+ if (path.startsWith("Microsoft.AspNetCore") || path.startsWith("Microsoft.Extensions")) {
25389
+ return { name: "aspnetcore", confidence: 0.9, indicators: [`using: ${path}`] };
25390
+ }
25391
+ if (path.startsWith("Microsoft.EntityFrameworkCore")) {
25392
+ return { name: "efcore", confidence: 0.9, indicators: [`using: ${path}`] };
25393
+ }
25394
+ }
25395
+ return void 0;
25396
+ }
25397
+ // --- LanguagePlugin contract (unused by the main analyze() path; Phase-1) ---
25398
+ extractTypes(_context) {
25399
+ return [];
25400
+ }
25401
+ extractCalls(_context) {
25402
+ return [];
25403
+ }
25404
+ extractImports(_context) {
25405
+ return [];
25406
+ }
25407
+ extractPackage(_context) {
25408
+ return void 0;
25409
+ }
25410
+ getBuiltinSources() {
25411
+ return [];
25412
+ }
25413
+ getBuiltinSinks() {
25414
+ return [];
25415
+ }
25416
+ };
25417
+
25017
25418
  // src/languages/plugins/index.ts
25018
25419
  function registerBuiltinPlugins() {
25019
25420
  registerLanguage(new JavaPlugin());
@@ -25024,6 +25425,7 @@ function registerBuiltinPlugins() {
25024
25425
  registerLanguage(new HtmlPlugin());
25025
25426
  registerLanguage(new VuePlugin());
25026
25427
  registerLanguage(new GoPlugin());
25428
+ registerLanguage(new CSharpPlugin());
25027
25429
  }
25028
25430
 
25029
25431
  // src/analysis/html/html-extractor.ts
@@ -25892,6 +26294,7 @@ var LanguageSourcesPass = class {
25892
26294
  additionalSources.push(...findStaticFieldSources(types, code, language));
25893
26295
  additionalSources.push(...findSetterChainSources(types, code, language));
25894
26296
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
26297
+ additionalSources.push(...findCSharpRequestSources(code, language));
25895
26298
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
25896
26299
  for (const s of jsDOMSinks) {
25897
26300
  const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
@@ -26546,6 +26949,30 @@ function findSetterChainSources(types, sourceCode, language) {
26546
26949
  }
26547
26950
  return sources;
26548
26951
  }
26952
+ function findCSharpRequestSources(sourceCode, language) {
26953
+ if (language !== "csharp") return [];
26954
+ const sources = [];
26955
+ const lines = sourceCode.split("\n");
26956
+ const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
26957
+ const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
26958
+ for (let i2 = 0; i2 < lines.length; i2++) {
26959
+ const m = assignRe.exec(lines[i2]);
26960
+ if (!m) continue;
26961
+ const [, varName, rhs] = m;
26962
+ if (!requestReadRe.test(rhs)) continue;
26963
+ const lineNumber = i2 + 1;
26964
+ if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
26965
+ sources.push({
26966
+ type: "http_param",
26967
+ location: `${varName} = ${rhs.trim().substring(0, 50)}${rhs.length > 50 ? "..." : ""}`,
26968
+ severity: "high",
26969
+ line: lineNumber,
26970
+ confidence: 1,
26971
+ variable: varName
26972
+ });
26973
+ }
26974
+ return sources;
26975
+ }
26549
26976
  function findJavaScriptAssignmentSources(sourceCode, language) {
26550
26977
  if (!["javascript", "typescript"].includes(language)) return [];
26551
26978
  const sources = [];
@@ -32531,6 +32958,13 @@ var REPLACE_ALL_TO_PLACEHOLDER_RE = /\.replaceAll\s*\([\s\S]*?,\s*"[\s,?]*\?[\s,
32531
32958
  var HTML_CONTENT_TYPE_RE = /text\/html|TEXT_HTML/;
32532
32959
  var FETCH_GLOBAL_RECEIVER_RE = /(?:^|[^.\w])(?:window|globalThis|self|global)\s*\.\s*fetch\s*\(/;
32533
32960
  var FETCH_MEMBER_RECEIVER_RE = /[\w$)\]]\s*\.\s*fetch\s*\(/;
32961
+ var NOSQL_ARRAY_METHODS = /* @__PURE__ */ new Set(["find", "filter", "some", "every", "findIndex", "findLast"]);
32962
+ var FUNCTION_FIRST_ARG_RE = /\.\s*(?:find|filter|some|every|findIndex|findLast)\s*\(\s*(?:async\s+)?(?:function\b|(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>)/;
32963
+ var ORM_BUILDER_KEY_RE = /[{,]\s*(?:where|select|populate|include|orderBy|relations|attributes)\s*:/;
32964
+ var BROWSER_COMPONENT_EXT_RE = /\.(?:jsx|tsx|vue|svelte)$/;
32965
+ var CLIENT_SIGNAL_RE = /["']use client["']|\buse(?:State|Effect|Ref|Callback|Memo|Context|Reducer|LayoutEffect)\s*\(|\b(?:window|document|localStorage|sessionStorage|navigator)\s*\.|\son(?:Click|Change|Submit|Input|KeyDown|KeyUp|MouseOver|Focus|Blur)\s*=/;
32966
+ var SERVER_SIGNAL_RE = /["']use server["']|\b(?:getServerSideProps|getStaticProps|getStaticPaths)\b|from\s+["']next\/server["']|\bNext(?:Api)?(?:Request|Response)\b|from\s+["'](?:node:)?(?:fs|child_process|http|https|net|dns|dgram)["']|\bexport\s+(?:default\s+)?(?:async\s+)?function\s+handler\s*\(/;
32967
+ var FIXED_HOST_TEMPLATE_RE = /`https?:\/\/[^/${}\s`]+[/`]/;
32534
32968
  function javaTraversalRejectGuardedLines(code) {
32535
32969
  const covered = /* @__PURE__ */ new Set();
32536
32970
  const lines = code.split("\n");
@@ -32622,6 +33056,49 @@ function jsSsrfHostGuardedLines(code) {
32622
33056
  }
32623
33057
  return covered;
32624
33058
  }
33059
+ var CSHARP_SANITIZER_RES = [
33060
+ { re: /\b(?:HtmlEncode|JavaScriptStringEncode)\s*\(/, type: "xss" },
33061
+ { re: /\bHtmlEncoder\s*\.\s*Encode\s*\(/, type: "xss" },
33062
+ { re: /\bPath\s*\.\s*GetFileName\s*\(/, type: "path_traversal" }
33063
+ ];
33064
+ function csharpSanitizedVarsByType(code) {
33065
+ const lines = code.split("\n");
33066
+ const byType = /* @__PURE__ */ new Map();
33067
+ const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
33068
+ for (const { type } of CSHARP_SANITIZER_RES) {
33069
+ if (!byType.has(type)) byType.set(type, /* @__PURE__ */ new Set());
33070
+ }
33071
+ for (const line of lines) {
33072
+ const m = assignRe.exec(line);
33073
+ if (!m) continue;
33074
+ const [, lhs, rhs] = m;
33075
+ for (const { re, type } of CSHARP_SANITIZER_RES) {
33076
+ if (re.test(rhs)) byType.get(type).add(lhs);
33077
+ }
33078
+ }
33079
+ let changed = true;
33080
+ let guard = 0;
33081
+ while (changed && guard < lines.length + 2) {
33082
+ changed = false;
33083
+ guard++;
33084
+ for (const line of lines) {
33085
+ const m = assignRe.exec(line);
33086
+ if (!m) continue;
33087
+ const [, lhs, rhs] = m;
33088
+ for (const [type, set] of byType) {
33089
+ if (set.has(lhs)) continue;
33090
+ const refsSanitized = [...set].some(
33091
+ (v) => new RegExp(`(?<![\\w])${escapeRegex(v)}(?![\\w])`).test(rhs)
33092
+ );
33093
+ if (refsSanitized) {
33094
+ set.add(lhs);
33095
+ changed = true;
33096
+ }
33097
+ }
33098
+ }
33099
+ }
33100
+ return byType;
33101
+ }
32625
33102
  var JAVA_INLINE_MATCHES_RE = /\.\s*matches\s*\(\s*"((?:[^"\\]|\\.)*)"\s*\)/g;
32626
33103
  function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
32627
33104
  if (!receiver || !/^[A-Za-z_]\w*$/.test(receiver)) return null;
@@ -33325,6 +33802,37 @@ var SinkFilterPass = class {
33325
33802
  return !FETCH_MEMBER_RECEIVER_RE.test(sinkLineText);
33326
33803
  });
33327
33804
  }
33805
+ if (["javascript", "typescript"].includes(language)) {
33806
+ const sourceLines = ctx.code.split("\n");
33807
+ filtered = filtered.filter((sink) => {
33808
+ if (sink.type !== "nosql_injection") return true;
33809
+ if (!NOSQL_ARRAY_METHODS.has(sink.method ?? "")) return true;
33810
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33811
+ return !FUNCTION_FIRST_ARG_RE.test(sinkLineText);
33812
+ });
33813
+ }
33814
+ if (["javascript", "typescript"].includes(language)) {
33815
+ const sourceLines = ctx.code.split("\n");
33816
+ filtered = filtered.filter((sink) => {
33817
+ if (sink.type !== "sql_injection" && sink.type !== "nosql_injection") return true;
33818
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33819
+ return !ORM_BUILDER_KEY_RE.test(sinkLineText);
33820
+ });
33821
+ }
33822
+ if (["javascript", "typescript", "tsx"].includes(language)) {
33823
+ const file = ctx.graph?.ir?.meta?.file ?? "";
33824
+ if (BROWSER_COMPONENT_EXT_RE.test(file) && CLIENT_SIGNAL_RE.test(ctx.code) && !SERVER_SIGNAL_RE.test(ctx.code)) {
33825
+ filtered = filtered.filter((sink) => sink.type !== "ssrf");
33826
+ }
33827
+ }
33828
+ if (["javascript", "typescript"].includes(language)) {
33829
+ const sourceLines = ctx.code.split("\n");
33830
+ filtered = filtered.filter((sink) => {
33831
+ if (sink.type !== "ssrf") return true;
33832
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33833
+ return !FIXED_HOST_TEMPLATE_RE.test(sinkLineText);
33834
+ });
33835
+ }
33328
33836
  if (["javascript", "typescript"].includes(language)) {
33329
33837
  const guardedLines = jsSsrfHostGuardedLines(ctx.code);
33330
33838
  if (guardedLines.size > 0) {
@@ -33333,6 +33841,23 @@ var SinkFilterPass = class {
33333
33841
  );
33334
33842
  }
33335
33843
  }
33844
+ if (language === "csharp") {
33845
+ const sourceLines = ctx.code.split("\n");
33846
+ const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
33847
+ filtered = filtered.filter((sink) => {
33848
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33849
+ for (const { re, type } of CSHARP_SANITIZER_RES) {
33850
+ if (type === sink.type && re.test(sinkLineText)) return false;
33851
+ }
33852
+ const sanitizedVars = sanitizedByType.get(sink.type);
33853
+ if (sanitizedVars && sanitizedVars.size > 0) {
33854
+ for (const v of sanitizedVars) {
33855
+ if (new RegExp(`(?<![\\w])${escapeRegex(v)}(?![\\w])`).test(sinkLineText)) return false;
33856
+ }
33857
+ }
33858
+ return true;
33859
+ });
33860
+ }
33336
33861
  if (["javascript", "typescript"].includes(language)) {
33337
33862
  const sourceLines = ctx.code.split("\n");
33338
33863
  filtered = filtered.filter((sink) => {
@@ -35781,7 +36306,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
35781
36306
  }
35782
36307
  }
35783
36308
  }
35784
- if (language === "java" && typeof code === "string" && sourcesWithVar.length > 0) {
36309
+ if ((language === "java" || language === "csharp") && typeof code === "string" && sourcesWithVar.length > 0) {
35785
36310
  const seedVars = new Set(sourcesWithVar.map((s) => s.variable));
35786
36311
  const derived = buildJavaTaintedVars(code, seedVars);
35787
36312
  if (derived.size > 0) {