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
|
@@ -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
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|