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.
- package/README.md +15 -1
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +68 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +15 -9
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/note-coalescer.d.ts +0 -4
- package/dist/analysis/note-coalescer.d.ts.map +1 -1
- package/dist/analysis/note-coalescer.js +36 -1
- package/dist/analysis/note-coalescer.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +54 -1
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +178 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +18 -3
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +573 -17
- package/dist/core/circle-ir-core.cjs +338 -4
- package/dist/core/circle-ir-core.js +338 -4
- package/dist/core/extractors/calls.d.ts.map +1 -1
- package/dist/core/extractors/calls.js +133 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/dfg.d.ts.map +1 -1
- package/dist/core/extractors/dfg.js +86 -1
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/core/extractors/types.d.ts.map +1 -1
- package/dist/core/extractors/types.js +89 -0
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/languages/plugins/csharp.d.ts +35 -0
- package/dist/languages/plugins/csharp.d.ts.map +1 -0
- package/dist/languages/plugins/csharp.js +72 -0
- package/dist/languages/plugins/csharp.js.map +1 -0
- package/dist/languages/plugins/index.d.ts +1 -0
- package/dist/languages/plugins/index.d.ts.map +1 -1
- package/dist/languages/plugins/index.js +3 -0
- package/dist/languages/plugins/index.js.map +1 -1
- package/dist/languages/types.d.ts +1 -1
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/wasm/tree-sitter-csharp.wasm +0 -0
- package/docs/SPEC.md +1 -1
- 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,
|
|
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
|