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
@@ -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") {
@@ -16323,33 +16657,39 @@ function canSourceReachSink(sourceType, sinkType) {
16323
16657
  // disagreed. log_injection (CWE-117) and format_string (CWE-134) are
16324
16658
  // reachable from any user-controlled value that is logged / used as a
16325
16659
  // format string; nosql_injection (CWE-943) mirrors sql_injection's sources.
16326
- http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection"],
16327
- http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16328
- http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16329
- http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16660
+ // prompt_injection (CWE-1427 / OWASP LLM01) added cognium-ai#281: an
16661
+ // attacker-controlled value reaching an LLM prompt sink (CreateChatCompletion
16662
+ // et al.) is prompt injection. The reach map omitted it entirely, so the
16663
+ // scan path (`generateFindings`) dropped every `http_*/io/network/interproc
16664
+ // prompt_injection` flow even though `taint.flows` / the trust pass reported
16665
+ // it — the same "two paths disagree" shape as #129. Now emitted from scan too.
16666
+ http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16667
+ http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16668
+ http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16669
+ http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16330
16670
  // xss added cognium-dev 3.163.0: URL path components (getRequestURI,
16331
16671
  // getRequestURL, getPathInfo, getServletPath) reflected back into HTML
16332
16672
  // output are a classic reflected-XSS vector — cf. Basic35 in
16333
16673
  // SecuriBench Micro where `writer.println(req.getRequestURL())` is
16334
16674
  // annotated `/* BAD */`. Prior to 3.163.0 the reach map omitted xss
16335
16675
  // so http_path → xss inline-colocation flows were silently dropped.
16336
- http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary", "xss", "log_injection", "format_string"],
16337
- http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection"],
16676
+ http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary", "xss", "log_injection", "format_string", "prompt_injection"],
16677
+ http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16338
16678
  // ssrf added Sprint 57 #200: bash CGI/webhook handlers and scripts that
16339
16679
  // take a URL on stdin or as a positional CLI arg (`curl "$1"`,
16340
16680
  // `wget "$(read line)"`) and curl/wget it server-side are textbook SSRF
16341
16681
  // (CVE-2022-41040 ProxyShell-class). Cross-language: `socket.urlopen(input())`
16342
16682
  // (Python), `axios.get(readline())` (JS) etc. also benefit.
16343
- io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf", "log_injection", "format_string"],
16683
+ io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf", "log_injection", "format_string", "prompt_injection"],
16344
16684
  env_input: ["command_injection", "path_traversal"],
16345
16685
  db_input: ["xss", "sql_injection", "log_injection"],
16346
16686
  // Second-order injection
16347
16687
  file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
16348
- network_input: ["sql_injection", "command_injection", "xss", "ssrf", "log_injection", "format_string", "nosql_injection"],
16688
+ network_input: ["sql_injection", "command_injection", "xss", "ssrf", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16349
16689
  config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf", "log_injection", "format_string"],
16350
16690
  // Servlet init params
16351
- interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16352
- // Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added
16691
+ interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16692
+ // Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added; cognium-ai#281 — prompt_injection added
16353
16693
  plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection", "log_injection", "format_string"]
16354
16694
  // Plugin/config parameters
16355
16695
  };
@@ -21301,6 +21641,10 @@ function applyLibraryApiSurfaceDowngrade(findings) {
21301
21641
  }
21302
21642
 
21303
21643
  // src/analysis/note-coalescer.ts
21644
+ var CLICKJACKING_PAIR = /* @__PURE__ */ new Set(["missing-x-frame-options", "missing-csp-frame-ancestors"]);
21645
+ function levelRank(level) {
21646
+ return level === "error" ? 0 : level === "warning" ? 1 : 2;
21647
+ }
21304
21648
  function coalesceNoteLevelFindings(findings) {
21305
21649
  if (findings.length < 2) return [...findings];
21306
21650
  const groups = /* @__PURE__ */ new Map();
@@ -21317,11 +21661,30 @@ function coalesceNoteLevelFindings(findings) {
21317
21661
  }
21318
21662
  const out2 = [];
21319
21663
  for (const key of order) {
21320
- const bucket = groups.get(key);
21664
+ let bucket = groups.get(key);
21321
21665
  if (bucket.length === 1) {
21322
21666
  out2.push(bucket[0]);
21323
21667
  continue;
21324
21668
  }
21669
+ const cj = bucket.filter((f) => CLICKJACKING_PAIR.has(f.rule_id));
21670
+ if (new Set(cj.map((f) => f.rule_id)).size === CLICKJACKING_PAIR.size) {
21671
+ const primary2 = [...cj].sort(
21672
+ (a, b) => levelRank(a.level) - levelRank(b.level) || a.rule_id.localeCompare(b.rule_id)
21673
+ )[0];
21674
+ const labels = Array.from(
21675
+ /* @__PURE__ */ new Set([
21676
+ ...primary2.labels ?? [],
21677
+ ...cj.flatMap((f) => [f.rule_id, ...f.labels ?? []])
21678
+ ])
21679
+ ).filter((l) => l !== primary2.rule_id);
21680
+ out2.push({ ...primary2, labels });
21681
+ bucket = bucket.filter((f) => !CLICKJACKING_PAIR.has(f.rule_id));
21682
+ if (bucket.length === 0) continue;
21683
+ if (bucket.length === 1) {
21684
+ out2.push(bucket[0]);
21685
+ continue;
21686
+ }
21687
+ }
21325
21688
  const allNote = bucket.every((f) => f.level === "note");
21326
21689
  if (!allNote) {
21327
21690
  for (const f of bucket) out2.push(f);
@@ -25014,6 +25377,73 @@ var GoPlugin = class extends BaseLanguagePlugin {
25014
25377
  }
25015
25378
  };
25016
25379
 
25380
+ // src/languages/plugins/csharp.ts
25381
+ var CSharpPlugin = class extends BaseLanguagePlugin {
25382
+ id = "csharp";
25383
+ name = "C#";
25384
+ extensions = [".cs"];
25385
+ wasmPath = "tree-sitter-csharp.wasm";
25386
+ nodeTypes = {
25387
+ // Type declarations
25388
+ classDeclaration: ["class_declaration", "record_declaration", "struct_declaration"],
25389
+ interfaceDeclaration: ["interface_declaration"],
25390
+ enumDeclaration: ["enum_declaration"],
25391
+ functionDeclaration: [],
25392
+ methodDeclaration: ["method_declaration", "constructor_declaration", "local_function_statement"],
25393
+ // Expressions — NB these diverge from Java (invocation_expression vs
25394
+ // method_invocation, local_declaration_statement vs local_variable_declaration).
25395
+ methodCall: ["invocation_expression"],
25396
+ functionCall: [],
25397
+ assignment: ["assignment_expression"],
25398
+ variableDeclaration: ["local_declaration_statement", "field_declaration", "variable_declaration"],
25399
+ // Parameters and arguments
25400
+ parameter: ["parameter"],
25401
+ argument: ["argument_list"],
25402
+ // Attributes (C# analogue of annotations/decorators)
25403
+ annotation: ["attribute", "attribute_list"],
25404
+ decorator: [],
25405
+ // Imports
25406
+ importStatement: ["using_directive"],
25407
+ // Control flow
25408
+ ifStatement: ["if_statement"],
25409
+ forStatement: ["for_statement", "for_each_statement"],
25410
+ whileStatement: ["while_statement"],
25411
+ tryStatement: ["try_statement"],
25412
+ returnStatement: ["return_statement"]
25413
+ };
25414
+ detectFramework(context) {
25415
+ for (const imp of context.imports) {
25416
+ const path = imp.from_package || imp.imported_name;
25417
+ if (path.startsWith("Microsoft.AspNetCore") || path.startsWith("Microsoft.Extensions")) {
25418
+ return { name: "aspnetcore", confidence: 0.9, indicators: [`using: ${path}`] };
25419
+ }
25420
+ if (path.startsWith("Microsoft.EntityFrameworkCore")) {
25421
+ return { name: "efcore", confidence: 0.9, indicators: [`using: ${path}`] };
25422
+ }
25423
+ }
25424
+ return void 0;
25425
+ }
25426
+ // --- LanguagePlugin contract (unused by the main analyze() path; Phase-1) ---
25427
+ extractTypes(_context) {
25428
+ return [];
25429
+ }
25430
+ extractCalls(_context) {
25431
+ return [];
25432
+ }
25433
+ extractImports(_context) {
25434
+ return [];
25435
+ }
25436
+ extractPackage(_context) {
25437
+ return void 0;
25438
+ }
25439
+ getBuiltinSources() {
25440
+ return [];
25441
+ }
25442
+ getBuiltinSinks() {
25443
+ return [];
25444
+ }
25445
+ };
25446
+
25017
25447
  // src/languages/plugins/index.ts
25018
25448
  function registerBuiltinPlugins() {
25019
25449
  registerLanguage(new JavaPlugin());
@@ -25024,6 +25454,7 @@ function registerBuiltinPlugins() {
25024
25454
  registerLanguage(new HtmlPlugin());
25025
25455
  registerLanguage(new VuePlugin());
25026
25456
  registerLanguage(new GoPlugin());
25457
+ registerLanguage(new CSharpPlugin());
25027
25458
  }
25028
25459
 
25029
25460
  // src/analysis/html/html-extractor.ts
@@ -25892,6 +26323,7 @@ var LanguageSourcesPass = class {
25892
26323
  additionalSources.push(...findStaticFieldSources(types, code, language));
25893
26324
  additionalSources.push(...findSetterChainSources(types, code, language));
25894
26325
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
26326
+ additionalSources.push(...findCSharpRequestSources(code, language));
25895
26327
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
25896
26328
  for (const s of jsDOMSinks) {
25897
26329
  const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
@@ -26546,17 +26978,43 @@ function findSetterChainSources(types, sourceCode, language) {
26546
26978
  }
26547
26979
  return sources;
26548
26980
  }
26981
+ function findCSharpRequestSources(sourceCode, language) {
26982
+ if (language !== "csharp") return [];
26983
+ const sources = [];
26984
+ const lines = sourceCode.split("\n");
26985
+ const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
26986
+ const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
26987
+ for (let i2 = 0; i2 < lines.length; i2++) {
26988
+ const m = assignRe.exec(lines[i2]);
26989
+ if (!m) continue;
26990
+ const [, varName, rhs] = m;
26991
+ if (!requestReadRe.test(rhs)) continue;
26992
+ const lineNumber = i2 + 1;
26993
+ if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
26994
+ sources.push({
26995
+ type: "http_param",
26996
+ location: `${varName} = ${rhs.trim().substring(0, 50)}${rhs.length > 50 ? "..." : ""}`,
26997
+ severity: "high",
26998
+ line: lineNumber,
26999
+ confidence: 1,
27000
+ variable: varName
27001
+ });
27002
+ }
27003
+ return sources;
27004
+ }
26549
27005
  function findJavaScriptAssignmentSources(sourceCode, language) {
26550
27006
  if (!["javascript", "typescript"].includes(language)) return [];
26551
27007
  const sources = [];
26552
27008
  const lines = sourceCode.split("\n");
27009
+ const isRouteHandler2 = /export\s+(?:async\s+)?(?:function\s+(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|loader|action)\b|const\s+(?:GET|POST|PUT|PATCH|DELETE|loader|action)\b)/.test(sourceCode) && /\{[^}]*\bparams\b[^}]*\}/.test(sourceCode);
27010
+ const patterns = isRouteHandler2 ? [...JS_TAINTED_PATTERNS, { pattern: /\bparams\s*[.[]/, type: "http_path" }] : JS_TAINTED_PATTERNS;
26553
27011
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
26554
27012
  const line = lines[lineNum];
26555
27013
  const lineNumber = lineNum + 1;
26556
27014
  const assignmentMatch = line.match(/(?:(?:var|let|const)\s+)?(\w+)\s*=\s*(.+)/);
26557
27015
  if (!assignmentMatch) continue;
26558
27016
  const [, varName, rhs] = assignmentMatch;
26559
- for (const { pattern, type } of JS_TAINTED_PATTERNS) {
27017
+ for (const { pattern, type } of patterns) {
26560
27018
  if (pattern.test(rhs)) {
26561
27019
  const alreadyExists = sources.some((s) => s.line === lineNumber && s.type === type);
26562
27020
  if (!alreadyExists) {
@@ -32531,6 +32989,13 @@ var REPLACE_ALL_TO_PLACEHOLDER_RE = /\.replaceAll\s*\([\s\S]*?,\s*"[\s,?]*\?[\s,
32531
32989
  var HTML_CONTENT_TYPE_RE = /text\/html|TEXT_HTML/;
32532
32990
  var FETCH_GLOBAL_RECEIVER_RE = /(?:^|[^.\w])(?:window|globalThis|self|global)\s*\.\s*fetch\s*\(/;
32533
32991
  var FETCH_MEMBER_RECEIVER_RE = /[\w$)\]]\s*\.\s*fetch\s*\(/;
32992
+ var NOSQL_ARRAY_METHODS = /* @__PURE__ */ new Set(["find", "filter", "some", "every", "findIndex", "findLast"]);
32993
+ var FUNCTION_FIRST_ARG_RE = /\.\s*(?:find|filter|some|every|findIndex|findLast)\s*\(\s*(?:async\s+)?(?:function\b|(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>)/;
32994
+ var ORM_BUILDER_KEY_RE = /[{,]\s*(?:where|select|populate|include|orderBy|relations|attributes)\s*:/;
32995
+ var BROWSER_COMPONENT_EXT_RE = /\.(?:jsx|tsx|vue|svelte)$/;
32996
+ 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*=/;
32997
+ 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*\(/;
32998
+ var FIXED_HOST_TEMPLATE_RE = /`https?:\/\/[^/${}\s`]+[/`]/;
32534
32999
  function javaTraversalRejectGuardedLines(code) {
32535
33000
  const covered = /* @__PURE__ */ new Set();
32536
33001
  const lines = code.split("\n");
@@ -32622,6 +33087,49 @@ function jsSsrfHostGuardedLines(code) {
32622
33087
  }
32623
33088
  return covered;
32624
33089
  }
33090
+ var CSHARP_SANITIZER_RES = [
33091
+ { re: /\b(?:HtmlEncode|JavaScriptStringEncode)\s*\(/, type: "xss" },
33092
+ { re: /\bHtmlEncoder\s*\.\s*Encode\s*\(/, type: "xss" },
33093
+ { re: /\bPath\s*\.\s*GetFileName\s*\(/, type: "path_traversal" }
33094
+ ];
33095
+ function csharpSanitizedVarsByType(code) {
33096
+ const lines = code.split("\n");
33097
+ const byType = /* @__PURE__ */ new Map();
33098
+ const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
33099
+ for (const { type } of CSHARP_SANITIZER_RES) {
33100
+ if (!byType.has(type)) byType.set(type, /* @__PURE__ */ new Set());
33101
+ }
33102
+ for (const line of lines) {
33103
+ const m = assignRe.exec(line);
33104
+ if (!m) continue;
33105
+ const [, lhs, rhs] = m;
33106
+ for (const { re, type } of CSHARP_SANITIZER_RES) {
33107
+ if (re.test(rhs)) byType.get(type).add(lhs);
33108
+ }
33109
+ }
33110
+ let changed = true;
33111
+ let guard = 0;
33112
+ while (changed && guard < lines.length + 2) {
33113
+ changed = false;
33114
+ guard++;
33115
+ for (const line of lines) {
33116
+ const m = assignRe.exec(line);
33117
+ if (!m) continue;
33118
+ const [, lhs, rhs] = m;
33119
+ for (const [type, set] of byType) {
33120
+ if (set.has(lhs)) continue;
33121
+ const refsSanitized = [...set].some(
33122
+ (v) => new RegExp(`(?<![\\w])${escapeRegex(v)}(?![\\w])`).test(rhs)
33123
+ );
33124
+ if (refsSanitized) {
33125
+ set.add(lhs);
33126
+ changed = true;
33127
+ }
33128
+ }
33129
+ }
33130
+ }
33131
+ return byType;
33132
+ }
32625
33133
  var JAVA_INLINE_MATCHES_RE = /\.\s*matches\s*\(\s*"((?:[^"\\]|\\.)*)"\s*\)/g;
32626
33134
  function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
32627
33135
  if (!receiver || !/^[A-Za-z_]\w*$/.test(receiver)) return null;
@@ -33325,6 +33833,37 @@ var SinkFilterPass = class {
33325
33833
  return !FETCH_MEMBER_RECEIVER_RE.test(sinkLineText);
33326
33834
  });
33327
33835
  }
33836
+ if (["javascript", "typescript"].includes(language)) {
33837
+ const sourceLines = ctx.code.split("\n");
33838
+ filtered = filtered.filter((sink) => {
33839
+ if (sink.type !== "nosql_injection") return true;
33840
+ if (!NOSQL_ARRAY_METHODS.has(sink.method ?? "")) return true;
33841
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33842
+ return !FUNCTION_FIRST_ARG_RE.test(sinkLineText);
33843
+ });
33844
+ }
33845
+ if (["javascript", "typescript"].includes(language)) {
33846
+ const sourceLines = ctx.code.split("\n");
33847
+ filtered = filtered.filter((sink) => {
33848
+ if (sink.type !== "sql_injection" && sink.type !== "nosql_injection") return true;
33849
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33850
+ return !ORM_BUILDER_KEY_RE.test(sinkLineText);
33851
+ });
33852
+ }
33853
+ if (["javascript", "typescript", "tsx"].includes(language)) {
33854
+ const file = ctx.graph?.ir?.meta?.file ?? "";
33855
+ if (BROWSER_COMPONENT_EXT_RE.test(file) && CLIENT_SIGNAL_RE.test(ctx.code) && !SERVER_SIGNAL_RE.test(ctx.code)) {
33856
+ filtered = filtered.filter((sink) => sink.type !== "ssrf");
33857
+ }
33858
+ }
33859
+ if (["javascript", "typescript"].includes(language)) {
33860
+ const sourceLines = ctx.code.split("\n");
33861
+ filtered = filtered.filter((sink) => {
33862
+ if (sink.type !== "ssrf") return true;
33863
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33864
+ return !FIXED_HOST_TEMPLATE_RE.test(sinkLineText);
33865
+ });
33866
+ }
33328
33867
  if (["javascript", "typescript"].includes(language)) {
33329
33868
  const guardedLines = jsSsrfHostGuardedLines(ctx.code);
33330
33869
  if (guardedLines.size > 0) {
@@ -33333,6 +33872,23 @@ var SinkFilterPass = class {
33333
33872
  );
33334
33873
  }
33335
33874
  }
33875
+ if (language === "csharp") {
33876
+ const sourceLines = ctx.code.split("\n");
33877
+ const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
33878
+ filtered = filtered.filter((sink) => {
33879
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
33880
+ for (const { re, type } of CSHARP_SANITIZER_RES) {
33881
+ if (type === sink.type && re.test(sinkLineText)) return false;
33882
+ }
33883
+ const sanitizedVars = sanitizedByType.get(sink.type);
33884
+ if (sanitizedVars && sanitizedVars.size > 0) {
33885
+ for (const v of sanitizedVars) {
33886
+ if (new RegExp(`(?<![\\w])${escapeRegex(v)}(?![\\w])`).test(sinkLineText)) return false;
33887
+ }
33888
+ }
33889
+ return true;
33890
+ });
33891
+ }
33336
33892
  if (["javascript", "typescript"].includes(language)) {
33337
33893
  const sourceLines = ctx.code.split("\n");
33338
33894
  filtered = filtered.filter((sink) => {
@@ -35781,7 +36337,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
35781
36337
  }
35782
36338
  }
35783
36339
  }
35784
- if (language === "java" && typeof code === "string" && sourcesWithVar.length > 0) {
36340
+ if ((language === "java" || language === "csharp") && typeof code === "string" && sourcesWithVar.length > 0) {
35785
36341
  const seedVars = new Set(sourcesWithVar.map((s) => s.variable));
35786
36342
  const derived = buildJavaTaintedVars(code, seedVars);
35787
36343
  if (derived.size > 0) {