circle-ir 4.8.2 → 4.9.8
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/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +11 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +1 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +37 -3
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/jwt-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/jwt-verify-disabled-pass.js +48 -21
- package/dist/analysis/passes/jwt-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +131 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.js +34 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.js +32 -19
- package/dist/analysis/passes/weak-crypto-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +237 -3
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +409 -43
- package/dist/core/circle-ir-core.cjs +218 -3
- package/dist/core/circle-ir-core.js +218 -3
- package/dist/core/extractors/types.js +77 -2
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/package.json +1 -1
|
@@ -4421,6 +4421,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4421
4421
|
const nameNode = node.childForFieldName("name");
|
|
4422
4422
|
const body2 = node.childForFieldName("body");
|
|
4423
4423
|
const methods = [];
|
|
4424
|
+
const fields = extractCSharpFields(body2);
|
|
4424
4425
|
if (body2) {
|
|
4425
4426
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4426
4427
|
const m = body2.child(i2);
|
|
@@ -4438,7 +4439,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4438
4439
|
parameters.push({
|
|
4439
4440
|
name: getNodeText(pName),
|
|
4440
4441
|
type: pType ? getNodeText(pType) : null,
|
|
4441
|
-
annotations:
|
|
4442
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
4442
4443
|
line: pnode.startPosition.row + 1
|
|
4443
4444
|
});
|
|
4444
4445
|
}
|
|
@@ -4464,7 +4465,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4464
4465
|
implements: [],
|
|
4465
4466
|
annotations: [],
|
|
4466
4467
|
methods,
|
|
4467
|
-
fields
|
|
4468
|
+
fields,
|
|
4468
4469
|
start_line: node.startPosition.row + 1,
|
|
4469
4470
|
end_line: node.endPosition.row + 1
|
|
4470
4471
|
});
|
|
@@ -4472,6 +4473,59 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4472
4473
|
}
|
|
4473
4474
|
return types;
|
|
4474
4475
|
}
|
|
4476
|
+
function extractCSharpFields(body2) {
|
|
4477
|
+
const fields = [];
|
|
4478
|
+
if (!body2) return fields;
|
|
4479
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4480
|
+
const c = body2.child(i2);
|
|
4481
|
+
if (!c) continue;
|
|
4482
|
+
if (c.type === "field_declaration") {
|
|
4483
|
+
let varDecl = null;
|
|
4484
|
+
for (let k = 0; k < c.childCount; k++) {
|
|
4485
|
+
const cc = c.child(k);
|
|
4486
|
+
if (cc?.type === "variable_declaration") {
|
|
4487
|
+
varDecl = cc;
|
|
4488
|
+
break;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
if (!varDecl) continue;
|
|
4492
|
+
const typeNode = varDecl.childForFieldName("type");
|
|
4493
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
4494
|
+
const modifiers = extractCSharpModifiers(c);
|
|
4495
|
+
for (let k = 0; k < varDecl.childCount; k++) {
|
|
4496
|
+
const decl = varDecl.child(k);
|
|
4497
|
+
if (decl?.type !== "variable_declarator") continue;
|
|
4498
|
+
const nameNode = decl.childForFieldName("name");
|
|
4499
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
|
|
4500
|
+
}
|
|
4501
|
+
} else if (c.type === "property_declaration") {
|
|
4502
|
+
const nameNode = c.childForFieldName("name");
|
|
4503
|
+
if (!nameNode) continue;
|
|
4504
|
+
const typeNode = c.childForFieldName("type");
|
|
4505
|
+
fields.push({
|
|
4506
|
+
name: getNodeText(nameNode),
|
|
4507
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
4508
|
+
modifiers: extractCSharpModifiers(c),
|
|
4509
|
+
annotations: []
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
return fields;
|
|
4514
|
+
}
|
|
4515
|
+
function extractCSharpParamAnnotations(param) {
|
|
4516
|
+
const out2 = [];
|
|
4517
|
+
for (let i2 = 0; i2 < param.childCount; i2++) {
|
|
4518
|
+
const list = param.child(i2);
|
|
4519
|
+
if (list?.type !== "attribute_list") continue;
|
|
4520
|
+
for (let j = 0; j < list.childCount; j++) {
|
|
4521
|
+
const attr = list.child(j);
|
|
4522
|
+
if (attr?.type !== "attribute") continue;
|
|
4523
|
+
const name2 = attr.childForFieldName("name");
|
|
4524
|
+
if (name2) out2.push(getNodeText(name2));
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
return out2;
|
|
4528
|
+
}
|
|
4475
4529
|
function extractCSharpModifiers(node) {
|
|
4476
4530
|
const mods = [];
|
|
4477
4531
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
@@ -13345,6 +13399,17 @@ var DEFAULT_SINKS = [
|
|
|
13345
13399
|
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13346
13400
|
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13347
13401
|
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13402
|
+
// C# server-side template injection (SSTI). Compiling an attacker-controlled
|
|
13403
|
+
// template string is arbitrary code execution — classified as code_injection
|
|
13404
|
+
// (CWE-94), matching how Python (Jinja2/Mako) and Node SSTI are modelled.
|
|
13405
|
+
// Restricted to DISTINCTIVE template-compile APIs so the generic
|
|
13406
|
+
// `Template.Parse` / `.Compile` names (int.Parse, Regex.Compile, …) are not
|
|
13407
|
+
// over-matched. Taint-gated: a constant template never fires. `RunCompile`
|
|
13408
|
+
// = RazorEngine; `CompileRenderStringAsync` = RazorLight; `Compile` is
|
|
13409
|
+
// class-scoped to Handlebars. (cognium-dev#273)
|
|
13410
|
+
{ method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13411
|
+
{ method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13412
|
+
{ method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13348
13413
|
// C# insecure deserialization — the polymorphic BCL formatters that can
|
|
13349
13414
|
// instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
|
|
13350
13415
|
// Each of these classes exists only to deserialize and is unsafe on untrusted
|
|
@@ -14377,7 +14442,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14377
14442
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
|
|
14378
14443
|
if (skipMethods.includes(method.name)) continue;
|
|
14379
14444
|
for (const param of method.parameters) {
|
|
14380
|
-
const
|
|
14445
|
+
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
14446
|
+
const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
14381
14447
|
if (isTaintable) {
|
|
14382
14448
|
const paramLine = param.line ?? method.start_line;
|
|
14383
14449
|
sources.push({
|
|
@@ -14497,6 +14563,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14497
14563
|
}
|
|
14498
14564
|
return result;
|
|
14499
14565
|
}
|
|
14566
|
+
var CSHARP_BINDING_ATTRS = /* @__PURE__ */ new Set([
|
|
14567
|
+
"FromBody",
|
|
14568
|
+
"FromQuery",
|
|
14569
|
+
"FromRoute",
|
|
14570
|
+
"FromForm",
|
|
14571
|
+
"FromHeader"
|
|
14572
|
+
]);
|
|
14500
14573
|
function isInterproceduralTaintableType(typeName, language) {
|
|
14501
14574
|
const baseType = typeName.split("<")[0].trim();
|
|
14502
14575
|
const excludedTypes = [
|
|
@@ -14786,6 +14859,145 @@ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
|
14786
14859
|
}
|
|
14787
14860
|
return false;
|
|
14788
14861
|
}
|
|
14862
|
+
function stripCsLiterals(line) {
|
|
14863
|
+
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
14864
|
+
}
|
|
14865
|
+
function csBraceDepthBefore(lines, idx) {
|
|
14866
|
+
let depth = 0;
|
|
14867
|
+
for (let i2 = 0; i2 < idx && i2 < lines.length; i2++) {
|
|
14868
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
14869
|
+
if (ch === "{") depth++;
|
|
14870
|
+
else if (ch === "}") depth--;
|
|
14871
|
+
}
|
|
14872
|
+
}
|
|
14873
|
+
return depth;
|
|
14874
|
+
}
|
|
14875
|
+
function netBraces(fragment) {
|
|
14876
|
+
let n = 0;
|
|
14877
|
+
for (const ch of fragment) {
|
|
14878
|
+
if (ch === "{") n++;
|
|
14879
|
+
else if (ch === "}") n--;
|
|
14880
|
+
}
|
|
14881
|
+
return n;
|
|
14882
|
+
}
|
|
14883
|
+
function splitCsIf(line) {
|
|
14884
|
+
const m = /\bif\s*\(/.exec(line);
|
|
14885
|
+
if (!m) return null;
|
|
14886
|
+
const ifCol = m.index;
|
|
14887
|
+
let i2 = m.index + m[0].length;
|
|
14888
|
+
let depth = 1;
|
|
14889
|
+
let inStr = false;
|
|
14890
|
+
let strCh = "";
|
|
14891
|
+
for (; i2 < line.length; i2++) {
|
|
14892
|
+
const c = line[i2];
|
|
14893
|
+
if (inStr) {
|
|
14894
|
+
if (c === "\\") {
|
|
14895
|
+
i2++;
|
|
14896
|
+
continue;
|
|
14897
|
+
}
|
|
14898
|
+
if (c === strCh) inStr = false;
|
|
14899
|
+
continue;
|
|
14900
|
+
}
|
|
14901
|
+
if (c === '"' || c === "'") {
|
|
14902
|
+
inStr = true;
|
|
14903
|
+
strCh = c;
|
|
14904
|
+
continue;
|
|
14905
|
+
}
|
|
14906
|
+
if (c === "(") depth++;
|
|
14907
|
+
else if (c === ")") {
|
|
14908
|
+
depth--;
|
|
14909
|
+
if (depth === 0) break;
|
|
14910
|
+
}
|
|
14911
|
+
}
|
|
14912
|
+
if (depth !== 0) return null;
|
|
14913
|
+
return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
|
|
14914
|
+
}
|
|
14915
|
+
function csEqualityGuard(cond) {
|
|
14916
|
+
const c = cond.trim();
|
|
14917
|
+
let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
|
|
14918
|
+
if (m) return { op: m[2], exprSide: m[1].trim() };
|
|
14919
|
+
m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
|
|
14920
|
+
if (m) return { op: m[1], exprSide: m[2].trim() };
|
|
14921
|
+
return null;
|
|
14922
|
+
}
|
|
14923
|
+
var CS_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
14924
|
+
function csIdentifiers(expr) {
|
|
14925
|
+
return new Set(expr.match(CS_IDENT_RE) ?? []);
|
|
14926
|
+
}
|
|
14927
|
+
function csThenBlock(lines, ifIdx, rest) {
|
|
14928
|
+
const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
|
|
14929
|
+
let firstIdx;
|
|
14930
|
+
let firstText;
|
|
14931
|
+
if (rest.trim()) {
|
|
14932
|
+
firstIdx = ifIdx;
|
|
14933
|
+
firstText = rest;
|
|
14934
|
+
} else {
|
|
14935
|
+
let j = ifIdx + 1;
|
|
14936
|
+
while (j < lines.length && stripCsLiterals(lines[j]).trim() === "") j++;
|
|
14937
|
+
firstIdx = j;
|
|
14938
|
+
firstText = lines[j] ?? "";
|
|
14939
|
+
}
|
|
14940
|
+
if (firstText.trim().startsWith("{")) {
|
|
14941
|
+
let depth = 0;
|
|
14942
|
+
let started = false;
|
|
14943
|
+
let endLine = lines.length - 1;
|
|
14944
|
+
let body2 = "";
|
|
14945
|
+
for (let i2 = firstIdx; i2 < lines.length; i2++) {
|
|
14946
|
+
const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
|
|
14947
|
+
let broke = false;
|
|
14948
|
+
for (const ch of stripped) {
|
|
14949
|
+
if (ch === "{") {
|
|
14950
|
+
depth++;
|
|
14951
|
+
started = true;
|
|
14952
|
+
if (depth === 1) continue;
|
|
14953
|
+
} else if (ch === "}") {
|
|
14954
|
+
depth--;
|
|
14955
|
+
if (depth === 0) {
|
|
14956
|
+
endLine = i2;
|
|
14957
|
+
broke = true;
|
|
14958
|
+
break;
|
|
14959
|
+
}
|
|
14960
|
+
}
|
|
14961
|
+
if (started && depth >= 1) body2 += ch;
|
|
14962
|
+
}
|
|
14963
|
+
if (broke) break;
|
|
14964
|
+
if (started) body2 += " ";
|
|
14965
|
+
}
|
|
14966
|
+
return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
|
|
14967
|
+
}
|
|
14968
|
+
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
14969
|
+
}
|
|
14970
|
+
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
14971
|
+
if (language !== "csharp") return false;
|
|
14972
|
+
if (pattern.type !== "ssrf") return false;
|
|
14973
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
14974
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
14975
|
+
for (const a of call.arguments) {
|
|
14976
|
+
if (a.variable) candidates.add(a.variable);
|
|
14977
|
+
for (const id of csIdentifiers(a.expression ?? "")) candidates.add(id);
|
|
14978
|
+
}
|
|
14979
|
+
if (candidates.size === 0) return false;
|
|
14980
|
+
const sinkIdx = call.location.line - 1;
|
|
14981
|
+
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
14982
|
+
for (let i2 = 0; i2 < sinkIdx; i2++) {
|
|
14983
|
+
const parts2 = splitCsIf(sourceLines[i2]);
|
|
14984
|
+
if (!parts2) continue;
|
|
14985
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
14986
|
+
if (!guard) continue;
|
|
14987
|
+
const guardIds = csIdentifiers(guard.exprSide);
|
|
14988
|
+
if (![...guardIds].some((id) => candidates.has(id))) continue;
|
|
14989
|
+
const block = csThenBlock(sourceLines, i2, parts2.rest);
|
|
14990
|
+
if (guard.op === "==") {
|
|
14991
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
14992
|
+
} else {
|
|
14993
|
+
const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
|
|
14994
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
14995
|
+
return true;
|
|
14996
|
+
}
|
|
14997
|
+
}
|
|
14998
|
+
}
|
|
14999
|
+
return false;
|
|
15000
|
+
}
|
|
14789
15001
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
14790
15002
|
if (language !== "rust") return false;
|
|
14791
15003
|
if (pattern.type !== "command_injection") return false;
|
|
@@ -15044,6 +15256,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15044
15256
|
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
15045
15257
|
continue;
|
|
15046
15258
|
}
|
|
15259
|
+
if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
|
|
15260
|
+
continue;
|
|
15261
|
+
}
|
|
15047
15262
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
15048
15263
|
continue;
|
|
15049
15264
|
}
|
|
@@ -4355,6 +4355,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4355
4355
|
const nameNode = node.childForFieldName("name");
|
|
4356
4356
|
const body2 = node.childForFieldName("body");
|
|
4357
4357
|
const methods = [];
|
|
4358
|
+
const fields = extractCSharpFields(body2);
|
|
4358
4359
|
if (body2) {
|
|
4359
4360
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4360
4361
|
const m = body2.child(i2);
|
|
@@ -4372,7 +4373,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4372
4373
|
parameters.push({
|
|
4373
4374
|
name: getNodeText(pName),
|
|
4374
4375
|
type: pType ? getNodeText(pType) : null,
|
|
4375
|
-
annotations:
|
|
4376
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
4376
4377
|
line: pnode.startPosition.row + 1
|
|
4377
4378
|
});
|
|
4378
4379
|
}
|
|
@@ -4398,7 +4399,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4398
4399
|
implements: [],
|
|
4399
4400
|
annotations: [],
|
|
4400
4401
|
methods,
|
|
4401
|
-
fields
|
|
4402
|
+
fields,
|
|
4402
4403
|
start_line: node.startPosition.row + 1,
|
|
4403
4404
|
end_line: node.endPosition.row + 1
|
|
4404
4405
|
});
|
|
@@ -4406,6 +4407,59 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4406
4407
|
}
|
|
4407
4408
|
return types;
|
|
4408
4409
|
}
|
|
4410
|
+
function extractCSharpFields(body2) {
|
|
4411
|
+
const fields = [];
|
|
4412
|
+
if (!body2) return fields;
|
|
4413
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4414
|
+
const c = body2.child(i2);
|
|
4415
|
+
if (!c) continue;
|
|
4416
|
+
if (c.type === "field_declaration") {
|
|
4417
|
+
let varDecl = null;
|
|
4418
|
+
for (let k = 0; k < c.childCount; k++) {
|
|
4419
|
+
const cc = c.child(k);
|
|
4420
|
+
if (cc?.type === "variable_declaration") {
|
|
4421
|
+
varDecl = cc;
|
|
4422
|
+
break;
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
if (!varDecl) continue;
|
|
4426
|
+
const typeNode = varDecl.childForFieldName("type");
|
|
4427
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
4428
|
+
const modifiers = extractCSharpModifiers(c);
|
|
4429
|
+
for (let k = 0; k < varDecl.childCount; k++) {
|
|
4430
|
+
const decl = varDecl.child(k);
|
|
4431
|
+
if (decl?.type !== "variable_declarator") continue;
|
|
4432
|
+
const nameNode = decl.childForFieldName("name");
|
|
4433
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
|
|
4434
|
+
}
|
|
4435
|
+
} else if (c.type === "property_declaration") {
|
|
4436
|
+
const nameNode = c.childForFieldName("name");
|
|
4437
|
+
if (!nameNode) continue;
|
|
4438
|
+
const typeNode = c.childForFieldName("type");
|
|
4439
|
+
fields.push({
|
|
4440
|
+
name: getNodeText(nameNode),
|
|
4441
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
4442
|
+
modifiers: extractCSharpModifiers(c),
|
|
4443
|
+
annotations: []
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
return fields;
|
|
4448
|
+
}
|
|
4449
|
+
function extractCSharpParamAnnotations(param) {
|
|
4450
|
+
const out2 = [];
|
|
4451
|
+
for (let i2 = 0; i2 < param.childCount; i2++) {
|
|
4452
|
+
const list = param.child(i2);
|
|
4453
|
+
if (list?.type !== "attribute_list") continue;
|
|
4454
|
+
for (let j = 0; j < list.childCount; j++) {
|
|
4455
|
+
const attr = list.child(j);
|
|
4456
|
+
if (attr?.type !== "attribute") continue;
|
|
4457
|
+
const name2 = attr.childForFieldName("name");
|
|
4458
|
+
if (name2) out2.push(getNodeText(name2));
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
return out2;
|
|
4462
|
+
}
|
|
4409
4463
|
function extractCSharpModifiers(node) {
|
|
4410
4464
|
const mods = [];
|
|
4411
4465
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
@@ -13279,6 +13333,17 @@ var DEFAULT_SINKS = [
|
|
|
13279
13333
|
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13280
13334
|
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13281
13335
|
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13336
|
+
// C# server-side template injection (SSTI). Compiling an attacker-controlled
|
|
13337
|
+
// template string is arbitrary code execution — classified as code_injection
|
|
13338
|
+
// (CWE-94), matching how Python (Jinja2/Mako) and Node SSTI are modelled.
|
|
13339
|
+
// Restricted to DISTINCTIVE template-compile APIs so the generic
|
|
13340
|
+
// `Template.Parse` / `.Compile` names (int.Parse, Regex.Compile, …) are not
|
|
13341
|
+
// over-matched. Taint-gated: a constant template never fires. `RunCompile`
|
|
13342
|
+
// = RazorEngine; `CompileRenderStringAsync` = RazorLight; `Compile` is
|
|
13343
|
+
// class-scoped to Handlebars. (cognium-dev#273)
|
|
13344
|
+
{ method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13345
|
+
{ method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13346
|
+
{ method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13282
13347
|
// C# insecure deserialization — the polymorphic BCL formatters that can
|
|
13283
13348
|
// instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
|
|
13284
13349
|
// Each of these classes exists only to deserialize and is unsafe on untrusted
|
|
@@ -14311,7 +14376,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14311
14376
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
|
|
14312
14377
|
if (skipMethods.includes(method.name)) continue;
|
|
14313
14378
|
for (const param of method.parameters) {
|
|
14314
|
-
const
|
|
14379
|
+
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
14380
|
+
const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
14315
14381
|
if (isTaintable) {
|
|
14316
14382
|
const paramLine = param.line ?? method.start_line;
|
|
14317
14383
|
sources.push({
|
|
@@ -14431,6 +14497,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14431
14497
|
}
|
|
14432
14498
|
return result;
|
|
14433
14499
|
}
|
|
14500
|
+
var CSHARP_BINDING_ATTRS = /* @__PURE__ */ new Set([
|
|
14501
|
+
"FromBody",
|
|
14502
|
+
"FromQuery",
|
|
14503
|
+
"FromRoute",
|
|
14504
|
+
"FromForm",
|
|
14505
|
+
"FromHeader"
|
|
14506
|
+
]);
|
|
14434
14507
|
function isInterproceduralTaintableType(typeName, language) {
|
|
14435
14508
|
const baseType = typeName.split("<")[0].trim();
|
|
14436
14509
|
const excludedTypes = [
|
|
@@ -14720,6 +14793,145 @@ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
|
14720
14793
|
}
|
|
14721
14794
|
return false;
|
|
14722
14795
|
}
|
|
14796
|
+
function stripCsLiterals(line) {
|
|
14797
|
+
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
14798
|
+
}
|
|
14799
|
+
function csBraceDepthBefore(lines, idx) {
|
|
14800
|
+
let depth = 0;
|
|
14801
|
+
for (let i2 = 0; i2 < idx && i2 < lines.length; i2++) {
|
|
14802
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
14803
|
+
if (ch === "{") depth++;
|
|
14804
|
+
else if (ch === "}") depth--;
|
|
14805
|
+
}
|
|
14806
|
+
}
|
|
14807
|
+
return depth;
|
|
14808
|
+
}
|
|
14809
|
+
function netBraces(fragment) {
|
|
14810
|
+
let n = 0;
|
|
14811
|
+
for (const ch of fragment) {
|
|
14812
|
+
if (ch === "{") n++;
|
|
14813
|
+
else if (ch === "}") n--;
|
|
14814
|
+
}
|
|
14815
|
+
return n;
|
|
14816
|
+
}
|
|
14817
|
+
function splitCsIf(line) {
|
|
14818
|
+
const m = /\bif\s*\(/.exec(line);
|
|
14819
|
+
if (!m) return null;
|
|
14820
|
+
const ifCol = m.index;
|
|
14821
|
+
let i2 = m.index + m[0].length;
|
|
14822
|
+
let depth = 1;
|
|
14823
|
+
let inStr = false;
|
|
14824
|
+
let strCh = "";
|
|
14825
|
+
for (; i2 < line.length; i2++) {
|
|
14826
|
+
const c = line[i2];
|
|
14827
|
+
if (inStr) {
|
|
14828
|
+
if (c === "\\") {
|
|
14829
|
+
i2++;
|
|
14830
|
+
continue;
|
|
14831
|
+
}
|
|
14832
|
+
if (c === strCh) inStr = false;
|
|
14833
|
+
continue;
|
|
14834
|
+
}
|
|
14835
|
+
if (c === '"' || c === "'") {
|
|
14836
|
+
inStr = true;
|
|
14837
|
+
strCh = c;
|
|
14838
|
+
continue;
|
|
14839
|
+
}
|
|
14840
|
+
if (c === "(") depth++;
|
|
14841
|
+
else if (c === ")") {
|
|
14842
|
+
depth--;
|
|
14843
|
+
if (depth === 0) break;
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14846
|
+
if (depth !== 0) return null;
|
|
14847
|
+
return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
|
|
14848
|
+
}
|
|
14849
|
+
function csEqualityGuard(cond) {
|
|
14850
|
+
const c = cond.trim();
|
|
14851
|
+
let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
|
|
14852
|
+
if (m) return { op: m[2], exprSide: m[1].trim() };
|
|
14853
|
+
m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
|
|
14854
|
+
if (m) return { op: m[1], exprSide: m[2].trim() };
|
|
14855
|
+
return null;
|
|
14856
|
+
}
|
|
14857
|
+
var CS_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
14858
|
+
function csIdentifiers(expr) {
|
|
14859
|
+
return new Set(expr.match(CS_IDENT_RE) ?? []);
|
|
14860
|
+
}
|
|
14861
|
+
function csThenBlock(lines, ifIdx, rest) {
|
|
14862
|
+
const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
|
|
14863
|
+
let firstIdx;
|
|
14864
|
+
let firstText;
|
|
14865
|
+
if (rest.trim()) {
|
|
14866
|
+
firstIdx = ifIdx;
|
|
14867
|
+
firstText = rest;
|
|
14868
|
+
} else {
|
|
14869
|
+
let j = ifIdx + 1;
|
|
14870
|
+
while (j < lines.length && stripCsLiterals(lines[j]).trim() === "") j++;
|
|
14871
|
+
firstIdx = j;
|
|
14872
|
+
firstText = lines[j] ?? "";
|
|
14873
|
+
}
|
|
14874
|
+
if (firstText.trim().startsWith("{")) {
|
|
14875
|
+
let depth = 0;
|
|
14876
|
+
let started = false;
|
|
14877
|
+
let endLine = lines.length - 1;
|
|
14878
|
+
let body2 = "";
|
|
14879
|
+
for (let i2 = firstIdx; i2 < lines.length; i2++) {
|
|
14880
|
+
const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
|
|
14881
|
+
let broke = false;
|
|
14882
|
+
for (const ch of stripped) {
|
|
14883
|
+
if (ch === "{") {
|
|
14884
|
+
depth++;
|
|
14885
|
+
started = true;
|
|
14886
|
+
if (depth === 1) continue;
|
|
14887
|
+
} else if (ch === "}") {
|
|
14888
|
+
depth--;
|
|
14889
|
+
if (depth === 0) {
|
|
14890
|
+
endLine = i2;
|
|
14891
|
+
broke = true;
|
|
14892
|
+
break;
|
|
14893
|
+
}
|
|
14894
|
+
}
|
|
14895
|
+
if (started && depth >= 1) body2 += ch;
|
|
14896
|
+
}
|
|
14897
|
+
if (broke) break;
|
|
14898
|
+
if (started) body2 += " ";
|
|
14899
|
+
}
|
|
14900
|
+
return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
|
|
14901
|
+
}
|
|
14902
|
+
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
14903
|
+
}
|
|
14904
|
+
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
14905
|
+
if (language !== "csharp") return false;
|
|
14906
|
+
if (pattern.type !== "ssrf") return false;
|
|
14907
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
14908
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
14909
|
+
for (const a of call.arguments) {
|
|
14910
|
+
if (a.variable) candidates.add(a.variable);
|
|
14911
|
+
for (const id of csIdentifiers(a.expression ?? "")) candidates.add(id);
|
|
14912
|
+
}
|
|
14913
|
+
if (candidates.size === 0) return false;
|
|
14914
|
+
const sinkIdx = call.location.line - 1;
|
|
14915
|
+
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
14916
|
+
for (let i2 = 0; i2 < sinkIdx; i2++) {
|
|
14917
|
+
const parts2 = splitCsIf(sourceLines[i2]);
|
|
14918
|
+
if (!parts2) continue;
|
|
14919
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
14920
|
+
if (!guard) continue;
|
|
14921
|
+
const guardIds = csIdentifiers(guard.exprSide);
|
|
14922
|
+
if (![...guardIds].some((id) => candidates.has(id))) continue;
|
|
14923
|
+
const block = csThenBlock(sourceLines, i2, parts2.rest);
|
|
14924
|
+
if (guard.op === "==") {
|
|
14925
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
14926
|
+
} else {
|
|
14927
|
+
const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
|
|
14928
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
14929
|
+
return true;
|
|
14930
|
+
}
|
|
14931
|
+
}
|
|
14932
|
+
}
|
|
14933
|
+
return false;
|
|
14934
|
+
}
|
|
14723
14935
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
14724
14936
|
if (language !== "rust") return false;
|
|
14725
14937
|
if (pattern.type !== "command_injection") return false;
|
|
@@ -14978,6 +15190,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
14978
15190
|
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
14979
15191
|
continue;
|
|
14980
15192
|
}
|
|
15193
|
+
if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
|
|
15194
|
+
continue;
|
|
15195
|
+
}
|
|
14981
15196
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
14982
15197
|
continue;
|
|
14983
15198
|
}
|
|
@@ -94,6 +94,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
94
94
|
const nameNode = node.childForFieldName('name');
|
|
95
95
|
const body = node.childForFieldName('body');
|
|
96
96
|
const methods = [];
|
|
97
|
+
const fields = extractCSharpFields(body);
|
|
97
98
|
if (body) {
|
|
98
99
|
for (let i = 0; i < body.childCount; i++) {
|
|
99
100
|
const m = body.child(i);
|
|
@@ -113,7 +114,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
113
114
|
parameters.push({
|
|
114
115
|
name: getNodeText(pName),
|
|
115
116
|
type: pType ? getNodeText(pType) : null,
|
|
116
|
-
annotations:
|
|
117
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
117
118
|
line: pnode.startPosition.row + 1,
|
|
118
119
|
});
|
|
119
120
|
}
|
|
@@ -139,7 +140,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
139
140
|
implements: [],
|
|
140
141
|
annotations: [],
|
|
141
142
|
methods,
|
|
142
|
-
fields
|
|
143
|
+
fields,
|
|
143
144
|
start_line: node.startPosition.row + 1,
|
|
144
145
|
end_line: node.endPosition.row + 1,
|
|
145
146
|
});
|
|
@@ -147,6 +148,80 @@ function extractCSharpTypes(tree, cache) {
|
|
|
147
148
|
}
|
|
148
149
|
return types;
|
|
149
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Extract C# instance state — `field_declaration`s (incl. multi-declarator
|
|
153
|
+
* `int a, b;`) and `property_declaration`s (auto-properties hold state too).
|
|
154
|
+
* Both are class members that methods reference by name, so both count toward
|
|
155
|
+
* cohesion metrics (LCOM). Returns [] for a null body (e.g. expression-bodied
|
|
156
|
+
* or partial declarations without an inline body).
|
|
157
|
+
*/
|
|
158
|
+
function extractCSharpFields(body) {
|
|
159
|
+
const fields = [];
|
|
160
|
+
if (!body)
|
|
161
|
+
return fields;
|
|
162
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
163
|
+
const c = body.child(i);
|
|
164
|
+
if (!c)
|
|
165
|
+
continue;
|
|
166
|
+
if (c.type === 'field_declaration') {
|
|
167
|
+
let varDecl = null;
|
|
168
|
+
for (let k = 0; k < c.childCount; k++) {
|
|
169
|
+
const cc = c.child(k);
|
|
170
|
+
if (cc?.type === 'variable_declaration') {
|
|
171
|
+
varDecl = cc;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!varDecl)
|
|
176
|
+
continue;
|
|
177
|
+
const typeNode = varDecl.childForFieldName('type');
|
|
178
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
179
|
+
const modifiers = extractCSharpModifiers(c);
|
|
180
|
+
for (let k = 0; k < varDecl.childCount; k++) {
|
|
181
|
+
const decl = varDecl.child(k);
|
|
182
|
+
if (decl?.type !== 'variable_declarator')
|
|
183
|
+
continue;
|
|
184
|
+
const nameNode = decl.childForFieldName('name');
|
|
185
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : 'unknown', type, modifiers, annotations: [] });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else if (c.type === 'property_declaration') {
|
|
189
|
+
const nameNode = c.childForFieldName('name');
|
|
190
|
+
if (!nameNode)
|
|
191
|
+
continue;
|
|
192
|
+
const typeNode = c.childForFieldName('type');
|
|
193
|
+
fields.push({
|
|
194
|
+
name: getNodeText(nameNode),
|
|
195
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
196
|
+
modifiers: extractCSharpModifiers(c),
|
|
197
|
+
annotations: [],
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return fields;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Attribute names on a C# `parameter` node — e.g. `[FromBody]` / `[FromQuery]`
|
|
205
|
+
* → `['FromBody']` / `['FromQuery']`. Used to recognise ASP.NET model-binding
|
|
206
|
+
* parameters (including DTO types) as taint sources.
|
|
207
|
+
*/
|
|
208
|
+
function extractCSharpParamAnnotations(param) {
|
|
209
|
+
const out = [];
|
|
210
|
+
for (let i = 0; i < param.childCount; i++) {
|
|
211
|
+
const list = param.child(i);
|
|
212
|
+
if (list?.type !== 'attribute_list')
|
|
213
|
+
continue;
|
|
214
|
+
for (let j = 0; j < list.childCount; j++) {
|
|
215
|
+
const attr = list.child(j);
|
|
216
|
+
if (attr?.type !== 'attribute')
|
|
217
|
+
continue;
|
|
218
|
+
const name = attr.childForFieldName('name');
|
|
219
|
+
if (name)
|
|
220
|
+
out.push(getNodeText(name));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
150
225
|
/** Leading `modifier` tokens (public/static/…) of a C# declaration node. */
|
|
151
226
|
function extractCSharpModifiers(node) {
|
|
152
227
|
const mods = [];
|