cognium-dev 4.8.2 → 4.9.7
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/cli.js +455 -44
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -3646,6 +3646,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
3646
3646
|
const nameNode = node.childForFieldName("name");
|
|
3647
3647
|
const body2 = node.childForFieldName("body");
|
|
3648
3648
|
const methods = [];
|
|
3649
|
+
const fields = extractCSharpFields(body2);
|
|
3649
3650
|
if (body2) {
|
|
3650
3651
|
for (let i2 = 0;i2 < body2.childCount; i2++) {
|
|
3651
3652
|
const m = body2.child(i2);
|
|
@@ -3665,7 +3666,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
3665
3666
|
parameters.push({
|
|
3666
3667
|
name: getNodeText(pName),
|
|
3667
3668
|
type: pType ? getNodeText(pType) : null,
|
|
3668
|
-
annotations:
|
|
3669
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
3669
3670
|
line: pnode.startPosition.row + 1
|
|
3670
3671
|
});
|
|
3671
3672
|
}
|
|
@@ -3691,7 +3692,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
3691
3692
|
implements: [],
|
|
3692
3693
|
annotations: [],
|
|
3693
3694
|
methods,
|
|
3694
|
-
fields
|
|
3695
|
+
fields,
|
|
3695
3696
|
start_line: node.startPosition.row + 1,
|
|
3696
3697
|
end_line: node.endPosition.row + 1
|
|
3697
3698
|
});
|
|
@@ -3699,6 +3700,67 @@ function extractCSharpTypes(tree, cache) {
|
|
|
3699
3700
|
}
|
|
3700
3701
|
return types;
|
|
3701
3702
|
}
|
|
3703
|
+
function extractCSharpFields(body2) {
|
|
3704
|
+
const fields = [];
|
|
3705
|
+
if (!body2)
|
|
3706
|
+
return fields;
|
|
3707
|
+
for (let i2 = 0;i2 < body2.childCount; i2++) {
|
|
3708
|
+
const c = body2.child(i2);
|
|
3709
|
+
if (!c)
|
|
3710
|
+
continue;
|
|
3711
|
+
if (c.type === "field_declaration") {
|
|
3712
|
+
let varDecl = null;
|
|
3713
|
+
for (let k = 0;k < c.childCount; k++) {
|
|
3714
|
+
const cc = c.child(k);
|
|
3715
|
+
if (cc?.type === "variable_declaration") {
|
|
3716
|
+
varDecl = cc;
|
|
3717
|
+
break;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
if (!varDecl)
|
|
3721
|
+
continue;
|
|
3722
|
+
const typeNode = varDecl.childForFieldName("type");
|
|
3723
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
3724
|
+
const modifiers = extractCSharpModifiers(c);
|
|
3725
|
+
for (let k = 0;k < varDecl.childCount; k++) {
|
|
3726
|
+
const decl = varDecl.child(k);
|
|
3727
|
+
if (decl?.type !== "variable_declarator")
|
|
3728
|
+
continue;
|
|
3729
|
+
const nameNode = decl.childForFieldName("name");
|
|
3730
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
|
|
3731
|
+
}
|
|
3732
|
+
} else if (c.type === "property_declaration") {
|
|
3733
|
+
const nameNode = c.childForFieldName("name");
|
|
3734
|
+
if (!nameNode)
|
|
3735
|
+
continue;
|
|
3736
|
+
const typeNode = c.childForFieldName("type");
|
|
3737
|
+
fields.push({
|
|
3738
|
+
name: getNodeText(nameNode),
|
|
3739
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
3740
|
+
modifiers: extractCSharpModifiers(c),
|
|
3741
|
+
annotations: []
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
return fields;
|
|
3746
|
+
}
|
|
3747
|
+
function extractCSharpParamAnnotations(param) {
|
|
3748
|
+
const out2 = [];
|
|
3749
|
+
for (let i2 = 0;i2 < param.childCount; i2++) {
|
|
3750
|
+
const list = param.child(i2);
|
|
3751
|
+
if (list?.type !== "attribute_list")
|
|
3752
|
+
continue;
|
|
3753
|
+
for (let j = 0;j < list.childCount; j++) {
|
|
3754
|
+
const attr = list.child(j);
|
|
3755
|
+
if (attr?.type !== "attribute")
|
|
3756
|
+
continue;
|
|
3757
|
+
const name2 = attr.childForFieldName("name");
|
|
3758
|
+
if (name2)
|
|
3759
|
+
out2.push(getNodeText(name2));
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
return out2;
|
|
3763
|
+
}
|
|
3702
3764
|
function extractCSharpModifiers(node) {
|
|
3703
3765
|
const mods = [];
|
|
3704
3766
|
for (let i2 = 0;i2 < node.childCount; i2++) {
|
|
@@ -12464,6 +12526,9 @@ var DEFAULT_SINKS = [
|
|
|
12464
12526
|
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12465
12527
|
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12466
12528
|
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12529
|
+
{ method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12530
|
+
{ method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
12531
|
+
{ method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12467
12532
|
{ method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12468
12533
|
{ method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12469
12534
|
{ method: "Deserialize", class: "SoapFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13271,7 +13336,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13271
13336
|
if (skipMethods.includes(method.name))
|
|
13272
13337
|
continue;
|
|
13273
13338
|
for (const param of method.parameters) {
|
|
13274
|
-
const
|
|
13339
|
+
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
13340
|
+
const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
13275
13341
|
if (isTaintable) {
|
|
13276
13342
|
const paramLine = param.line ?? method.start_line;
|
|
13277
13343
|
sources.push({
|
|
@@ -13384,6 +13450,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13384
13450
|
}
|
|
13385
13451
|
return result;
|
|
13386
13452
|
}
|
|
13453
|
+
var CSHARP_BINDING_ATTRS = new Set([
|
|
13454
|
+
"FromBody",
|
|
13455
|
+
"FromQuery",
|
|
13456
|
+
"FromRoute",
|
|
13457
|
+
"FromForm",
|
|
13458
|
+
"FromHeader"
|
|
13459
|
+
]);
|
|
13387
13460
|
function isInterproceduralTaintableType(typeName, language) {
|
|
13388
13461
|
const baseType = typeName.split("<")[0].trim();
|
|
13389
13462
|
const excludedTypes = [
|
|
@@ -13688,6 +13761,171 @@ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
|
13688
13761
|
}
|
|
13689
13762
|
return false;
|
|
13690
13763
|
}
|
|
13764
|
+
function stripCsLiterals(line) {
|
|
13765
|
+
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
13766
|
+
}
|
|
13767
|
+
function csBraceDepthBefore(lines, idx) {
|
|
13768
|
+
let depth = 0;
|
|
13769
|
+
for (let i2 = 0;i2 < idx && i2 < lines.length; i2++) {
|
|
13770
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
13771
|
+
if (ch === "{")
|
|
13772
|
+
depth++;
|
|
13773
|
+
else if (ch === "}")
|
|
13774
|
+
depth--;
|
|
13775
|
+
}
|
|
13776
|
+
}
|
|
13777
|
+
return depth;
|
|
13778
|
+
}
|
|
13779
|
+
function netBraces(fragment) {
|
|
13780
|
+
let n = 0;
|
|
13781
|
+
for (const ch of fragment) {
|
|
13782
|
+
if (ch === "{")
|
|
13783
|
+
n++;
|
|
13784
|
+
else if (ch === "}")
|
|
13785
|
+
n--;
|
|
13786
|
+
}
|
|
13787
|
+
return n;
|
|
13788
|
+
}
|
|
13789
|
+
function splitCsIf(line) {
|
|
13790
|
+
const m = /\bif\s*\(/.exec(line);
|
|
13791
|
+
if (!m)
|
|
13792
|
+
return null;
|
|
13793
|
+
const ifCol = m.index;
|
|
13794
|
+
let i2 = m.index + m[0].length;
|
|
13795
|
+
let depth = 1;
|
|
13796
|
+
let inStr = false;
|
|
13797
|
+
let strCh = "";
|
|
13798
|
+
for (;i2 < line.length; i2++) {
|
|
13799
|
+
const c = line[i2];
|
|
13800
|
+
if (inStr) {
|
|
13801
|
+
if (c === "\\") {
|
|
13802
|
+
i2++;
|
|
13803
|
+
continue;
|
|
13804
|
+
}
|
|
13805
|
+
if (c === strCh)
|
|
13806
|
+
inStr = false;
|
|
13807
|
+
continue;
|
|
13808
|
+
}
|
|
13809
|
+
if (c === '"' || c === "'") {
|
|
13810
|
+
inStr = true;
|
|
13811
|
+
strCh = c;
|
|
13812
|
+
continue;
|
|
13813
|
+
}
|
|
13814
|
+
if (c === "(")
|
|
13815
|
+
depth++;
|
|
13816
|
+
else if (c === ")") {
|
|
13817
|
+
depth--;
|
|
13818
|
+
if (depth === 0)
|
|
13819
|
+
break;
|
|
13820
|
+
}
|
|
13821
|
+
}
|
|
13822
|
+
if (depth !== 0)
|
|
13823
|
+
return null;
|
|
13824
|
+
return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
|
|
13825
|
+
}
|
|
13826
|
+
function csEqualityGuard(cond) {
|
|
13827
|
+
const c = cond.trim();
|
|
13828
|
+
let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
|
|
13829
|
+
if (m)
|
|
13830
|
+
return { op: m[2], exprSide: m[1].trim() };
|
|
13831
|
+
m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
|
|
13832
|
+
if (m)
|
|
13833
|
+
return { op: m[1], exprSide: m[2].trim() };
|
|
13834
|
+
return null;
|
|
13835
|
+
}
|
|
13836
|
+
var CS_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
13837
|
+
function csIdentifiers(expr) {
|
|
13838
|
+
return new Set(expr.match(CS_IDENT_RE) ?? []);
|
|
13839
|
+
}
|
|
13840
|
+
function csThenBlock(lines, ifIdx, rest) {
|
|
13841
|
+
const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
|
|
13842
|
+
let firstIdx;
|
|
13843
|
+
let firstText;
|
|
13844
|
+
if (rest.trim()) {
|
|
13845
|
+
firstIdx = ifIdx;
|
|
13846
|
+
firstText = rest;
|
|
13847
|
+
} else {
|
|
13848
|
+
let j = ifIdx + 1;
|
|
13849
|
+
while (j < lines.length && stripCsLiterals(lines[j]).trim() === "")
|
|
13850
|
+
j++;
|
|
13851
|
+
firstIdx = j;
|
|
13852
|
+
firstText = lines[j] ?? "";
|
|
13853
|
+
}
|
|
13854
|
+
if (firstText.trim().startsWith("{")) {
|
|
13855
|
+
let depth = 0;
|
|
13856
|
+
let started = false;
|
|
13857
|
+
let endLine = lines.length - 1;
|
|
13858
|
+
let body2 = "";
|
|
13859
|
+
for (let i2 = firstIdx;i2 < lines.length; i2++) {
|
|
13860
|
+
const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
|
|
13861
|
+
let broke = false;
|
|
13862
|
+
for (const ch of stripped) {
|
|
13863
|
+
if (ch === "{") {
|
|
13864
|
+
depth++;
|
|
13865
|
+
started = true;
|
|
13866
|
+
if (depth === 1)
|
|
13867
|
+
continue;
|
|
13868
|
+
} else if (ch === "}") {
|
|
13869
|
+
depth--;
|
|
13870
|
+
if (depth === 0) {
|
|
13871
|
+
endLine = i2;
|
|
13872
|
+
broke = true;
|
|
13873
|
+
break;
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
if (started && depth >= 1)
|
|
13877
|
+
body2 += ch;
|
|
13878
|
+
}
|
|
13879
|
+
if (broke)
|
|
13880
|
+
break;
|
|
13881
|
+
if (started)
|
|
13882
|
+
body2 += " ";
|
|
13883
|
+
}
|
|
13884
|
+
return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
|
|
13885
|
+
}
|
|
13886
|
+
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
13887
|
+
}
|
|
13888
|
+
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
13889
|
+
if (language !== "csharp")
|
|
13890
|
+
return false;
|
|
13891
|
+
if (pattern.type !== "ssrf")
|
|
13892
|
+
return false;
|
|
13893
|
+
if (!sourceLines || sourceLines.length === 0)
|
|
13894
|
+
return false;
|
|
13895
|
+
const candidates = new Set;
|
|
13896
|
+
for (const a of call.arguments) {
|
|
13897
|
+
if (a.variable)
|
|
13898
|
+
candidates.add(a.variable);
|
|
13899
|
+
for (const id of csIdentifiers(a.expression ?? ""))
|
|
13900
|
+
candidates.add(id);
|
|
13901
|
+
}
|
|
13902
|
+
if (candidates.size === 0)
|
|
13903
|
+
return false;
|
|
13904
|
+
const sinkIdx = call.location.line - 1;
|
|
13905
|
+
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
13906
|
+
for (let i2 = 0;i2 < sinkIdx; i2++) {
|
|
13907
|
+
const parts2 = splitCsIf(sourceLines[i2]);
|
|
13908
|
+
if (!parts2)
|
|
13909
|
+
continue;
|
|
13910
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
13911
|
+
if (!guard)
|
|
13912
|
+
continue;
|
|
13913
|
+
const guardIds = csIdentifiers(guard.exprSide);
|
|
13914
|
+
if (![...guardIds].some((id) => candidates.has(id)))
|
|
13915
|
+
continue;
|
|
13916
|
+
const block = csThenBlock(sourceLines, i2, parts2.rest);
|
|
13917
|
+
if (guard.op === "==") {
|
|
13918
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end)
|
|
13919
|
+
return true;
|
|
13920
|
+
} else {
|
|
13921
|
+
const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
|
|
13922
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
13923
|
+
return true;
|
|
13924
|
+
}
|
|
13925
|
+
}
|
|
13926
|
+
}
|
|
13927
|
+
return false;
|
|
13928
|
+
}
|
|
13691
13929
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
13692
13930
|
if (language !== "rust")
|
|
13693
13931
|
return false;
|
|
@@ -13981,6 +14219,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
13981
14219
|
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
13982
14220
|
continue;
|
|
13983
14221
|
}
|
|
14222
|
+
if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
|
|
14223
|
+
continue;
|
|
14224
|
+
}
|
|
13984
14225
|
if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
13985
14226
|
continue;
|
|
13986
14227
|
}
|
|
@@ -25211,6 +25452,8 @@ class LanguageSourcesPass {
|
|
|
25211
25452
|
additionalSources.push(...findSetterChainSources(types, code, language));
|
|
25212
25453
|
additionalSources.push(...findJavaScriptAssignmentSources(code, language));
|
|
25213
25454
|
additionalSources.push(...findCSharpRequestSources(code, language));
|
|
25455
|
+
additionalSources.push(...findCSharpBindingAttributeSources(code, language));
|
|
25456
|
+
additionalSources.push(...findCSharpMinimalApiSources(code, language));
|
|
25214
25457
|
additionalSources.push(...findGoArgvSources(code, language));
|
|
25215
25458
|
const jsDOMSinks = findJavaScriptDOMSinks(code, language);
|
|
25216
25459
|
for (const s of jsDOMSinks) {
|
|
@@ -25875,6 +26118,104 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
25875
26118
|
}
|
|
25876
26119
|
return sources;
|
|
25877
26120
|
}
|
|
26121
|
+
var CSHARP_MINIMAL_API_BINDING = new Map([
|
|
26122
|
+
["FromBody", "http_body"],
|
|
26123
|
+
["FromForm", "http_body"],
|
|
26124
|
+
["FromQuery", "http_param"],
|
|
26125
|
+
["FromRoute", "http_path"],
|
|
26126
|
+
["FromHeader", "http_param"]
|
|
26127
|
+
]);
|
|
26128
|
+
var CSHARP_NON_INPUT_TYPES = new Set([
|
|
26129
|
+
"HttpContext",
|
|
26130
|
+
"HttpRequest",
|
|
26131
|
+
"HttpResponse",
|
|
26132
|
+
"CancellationToken",
|
|
26133
|
+
"ClaimsPrincipal",
|
|
26134
|
+
"ILogger",
|
|
26135
|
+
"IFormFileCollection"
|
|
26136
|
+
]);
|
|
26137
|
+
function findCSharpBindingAttributeSources(sourceCode, language) {
|
|
26138
|
+
if (language !== "csharp")
|
|
26139
|
+
return [];
|
|
26140
|
+
const sources = [];
|
|
26141
|
+
const lines = sourceCode.split(`
|
|
26142
|
+
`);
|
|
26143
|
+
const re = /\[\s*(FromQuery|FromBody|FromForm|FromRoute|FromHeader)(?:\s*\([^)]*\))?\s*\]\s*(?:\[[^\]]*\]\s*)*[\w.<>[\]?]+\s+([A-Za-z_]\w*)/g;
|
|
26144
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26145
|
+
const lineRe = new RegExp(re.source, "g");
|
|
26146
|
+
let m;
|
|
26147
|
+
while ((m = lineRe.exec(lines[i2])) !== null) {
|
|
26148
|
+
const attr = m[1];
|
|
26149
|
+
const name2 = m[2];
|
|
26150
|
+
const type = attr === "FromBody" || attr === "FromForm" ? "http_body" : attr === "FromRoute" ? "http_path" : "http_param";
|
|
26151
|
+
if (sources.some((s) => s.line === i2 + 1 && s.variable === name2))
|
|
26152
|
+
continue;
|
|
26153
|
+
sources.push({
|
|
26154
|
+
type,
|
|
26155
|
+
location: `[${attr}] ${name2}`,
|
|
26156
|
+
severity: "high",
|
|
26157
|
+
line: i2 + 1,
|
|
26158
|
+
confidence: 1,
|
|
26159
|
+
variable: name2
|
|
26160
|
+
});
|
|
26161
|
+
}
|
|
26162
|
+
}
|
|
26163
|
+
return sources;
|
|
26164
|
+
}
|
|
26165
|
+
function findCSharpMinimalApiSources(sourceCode, language) {
|
|
26166
|
+
if (language !== "csharp")
|
|
26167
|
+
return [];
|
|
26168
|
+
const sources = [];
|
|
26169
|
+
const lines = sourceCode.split(`
|
|
26170
|
+
`);
|
|
26171
|
+
const mapRe = /\bMap(?:Get|Post|Put|Delete|Patch)\s*\(\s*(?:@?"[^"]*"|[\w.]+)\s*,\s*(?:\[[^\]]*\]\s*)?(?:async\s*)?\(([^)]*)\)\s*=>/;
|
|
26172
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26173
|
+
const m = mapRe.exec(lines[i2]);
|
|
26174
|
+
if (!m || !m[1].trim())
|
|
26175
|
+
continue;
|
|
26176
|
+
for (const rawParam of m[1].split(",")) {
|
|
26177
|
+
const seed = classifyCSharpLambdaParam(rawParam);
|
|
26178
|
+
if (!seed)
|
|
26179
|
+
continue;
|
|
26180
|
+
if (sources.some((s) => s.line === i2 + 1 && s.variable === seed.name))
|
|
26181
|
+
continue;
|
|
26182
|
+
sources.push({
|
|
26183
|
+
type: seed.type,
|
|
26184
|
+
location: `${seed.name} (Minimal API ${seed.via})`,
|
|
26185
|
+
severity: "high",
|
|
26186
|
+
line: i2 + 1,
|
|
26187
|
+
confidence: 1,
|
|
26188
|
+
variable: seed.name
|
|
26189
|
+
});
|
|
26190
|
+
}
|
|
26191
|
+
}
|
|
26192
|
+
return sources;
|
|
26193
|
+
}
|
|
26194
|
+
function classifyCSharpLambdaParam(raw) {
|
|
26195
|
+
const attrs = [...raw.matchAll(/\[([^\]]*)\]/g)].map((a) => a[1].split("(")[0].trim());
|
|
26196
|
+
if (attrs.includes("FromServices"))
|
|
26197
|
+
return null;
|
|
26198
|
+
if (attrs.some((a) => CSHARP_MINIMAL_API_BINDING.has(a)))
|
|
26199
|
+
return null;
|
|
26200
|
+
const noAttr = raw.replace(/\[[^\]]*\]/g, "").trim();
|
|
26201
|
+
const parts2 = noAttr.split(/\s+/).filter(Boolean);
|
|
26202
|
+
if (parts2.length < 2)
|
|
26203
|
+
return null;
|
|
26204
|
+
const name2 = parts2[parts2.length - 1];
|
|
26205
|
+
if (!/^[A-Za-z_]\w*$/.test(name2))
|
|
26206
|
+
return null;
|
|
26207
|
+
const baseType = (parts2[parts2.length - 2] ?? "").replace(/[?\[\]]/g, "").split("<")[0].split(".").pop() ?? "";
|
|
26208
|
+
if (CSHARP_NON_INPUT_TYPES.has(baseType))
|
|
26209
|
+
return null;
|
|
26210
|
+
for (const a of attrs) {
|
|
26211
|
+
const t = CSHARP_MINIMAL_API_BINDING.get(a);
|
|
26212
|
+
if (t)
|
|
26213
|
+
return { type: t, name: name2, via: `[${a}]` };
|
|
26214
|
+
}
|
|
26215
|
+
if (baseType === "string")
|
|
26216
|
+
return { type: "http_param", name: name2, via: "string param" };
|
|
26217
|
+
return null;
|
|
26218
|
+
}
|
|
25878
26219
|
function findJavaScriptAssignmentSources(sourceCode, language) {
|
|
25879
26220
|
if (!["javascript", "typescript"].includes(language))
|
|
25880
26221
|
return [];
|
|
@@ -42307,6 +42648,9 @@ var JAVA_SET_HTTPONLY_TRUE_RE = /\.setHttpOnly\s*\(\s*true\s*\)/;
|
|
|
42307
42648
|
var GO_SECURE_TRUE_RE = /\bSecure\s*:\s*true\b/;
|
|
42308
42649
|
var GO_HTTPONLY_TRUE_RE = /\bHttpOnly\s*:\s*true\b/;
|
|
42309
42650
|
var RUST_SET_COOKIE_MACRO_RE = /(format!|write!|writeln!)\s*\(([^()]*Set-Cookie[^()]*)\)/gis;
|
|
42651
|
+
var CS_COOKIE_OPTIONS_RE = /\bnew\s+CookieOptions\s*\{([^{}]*)\}/gs;
|
|
42652
|
+
var CS_SECURE_FALSE_RE = /\bSecure\s*=\s*false\b/;
|
|
42653
|
+
var CS_HTTPONLY_FALSE_RE = /\bHttpOnly\s*=\s*false\b/;
|
|
42310
42654
|
|
|
42311
42655
|
class InsecureCookiePass {
|
|
42312
42656
|
name = "insecure-cookie";
|
|
@@ -42355,9 +42699,30 @@ class InsecureCookiePass {
|
|
|
42355
42699
|
insecureCookies.push(det);
|
|
42356
42700
|
this.emit(ctx, file, det, "rust");
|
|
42357
42701
|
}
|
|
42702
|
+
} else if (language === "csharp") {
|
|
42703
|
+
for (const det of this.detectCSharpCookieOptions(code)) {
|
|
42704
|
+
insecureCookies.push(det);
|
|
42705
|
+
this.emit(ctx, file, det, "csharp");
|
|
42706
|
+
}
|
|
42358
42707
|
}
|
|
42359
42708
|
return { insecureCookies };
|
|
42360
42709
|
}
|
|
42710
|
+
detectCSharpCookieOptions(code) {
|
|
42711
|
+
const out2 = [];
|
|
42712
|
+
const re = new RegExp(CS_COOKIE_OPTIONS_RE.source, CS_COOKIE_OPTIONS_RE.flags);
|
|
42713
|
+
let m;
|
|
42714
|
+
while ((m = re.exec(code)) !== null) {
|
|
42715
|
+
const body2 = m[1] ?? "";
|
|
42716
|
+
const missingSecure = CS_SECURE_FALSE_RE.test(body2);
|
|
42717
|
+
const missingHttpOnly = CS_HTTPONLY_FALSE_RE.test(body2);
|
|
42718
|
+
if (!missingSecure && !missingHttpOnly)
|
|
42719
|
+
continue;
|
|
42720
|
+
const line = code.slice(0, m.index).split(`
|
|
42721
|
+
`).length;
|
|
42722
|
+
out2.push({ line, receiver: "CookieOptions", missingSecure, missingHttpOnly, optionsPresent: true });
|
|
42723
|
+
}
|
|
42724
|
+
return out2;
|
|
42725
|
+
}
|
|
42361
42726
|
detectJs(call) {
|
|
42362
42727
|
if (call.method_name !== "cookie")
|
|
42363
42728
|
return null;
|
|
@@ -42471,12 +42836,12 @@ class InsecureCookiePass {
|
|
|
42471
42836
|
emit(ctx, file, det, flavor) {
|
|
42472
42837
|
const missing = [];
|
|
42473
42838
|
if (det.missingSecure) {
|
|
42474
|
-
missing.push(flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : "`Secure` attribute");
|
|
42839
|
+
missing.push(flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : flavor === "csharp" ? "`Secure = true`" : "`Secure` attribute");
|
|
42475
42840
|
}
|
|
42476
42841
|
if (det.missingHttpOnly) {
|
|
42477
|
-
missing.push(flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : "`HttpOnly` attribute");
|
|
42842
|
+
missing.push(flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : flavor === "csharp" ? "`HttpOnly = true`" : "`HttpOnly` attribute");
|
|
42478
42843
|
}
|
|
42479
|
-
const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
|
|
42844
|
+
const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : flavor === "csharp" ? "Set `Secure = true` and `HttpOnly = true` on the `CookieOptions` (or enforce them globally via `CookiePolicyOptions`)." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
|
|
42480
42845
|
ctx.addFinding({
|
|
42481
42846
|
id: `${this.name}-${file}-${det.line}`,
|
|
42482
42847
|
pass: this.name,
|
|
@@ -42985,6 +43350,7 @@ var ISSUE_CWE = {
|
|
|
42985
43350
|
"hardcoded-key": "CWE-321",
|
|
42986
43351
|
"weak-rsa-key": "CWE-326"
|
|
42987
43352
|
};
|
|
43353
|
+
var CS_CIPHER_MODE_ECB_RE = /\bCipherMode\s*\.\s*ECB\b/;
|
|
42988
43354
|
|
|
42989
43355
|
class WeakCryptoPass {
|
|
42990
43356
|
name = "weak-crypto";
|
|
@@ -43001,26 +43367,35 @@ class WeakCryptoPass {
|
|
|
43001
43367
|
const findings = [];
|
|
43002
43368
|
const constProp = ctx.hasResult("constant-propagation") ? ctx.getResult("constant-propagation") : null;
|
|
43003
43369
|
const literalBindings = scanLiteralBindings(code, language);
|
|
43370
|
+
const emit = (line, det) => {
|
|
43371
|
+
findings.push({ line, language, ...det });
|
|
43372
|
+
ctx.addFinding({
|
|
43373
|
+
id: `${this.name}-${file}-${line}-${det.issue}`,
|
|
43374
|
+
pass: this.name,
|
|
43375
|
+
category: this.category,
|
|
43376
|
+
rule_id: this.name,
|
|
43377
|
+
cwe: ISSUE_CWE[det.issue],
|
|
43378
|
+
severity: "high",
|
|
43379
|
+
level: "error",
|
|
43380
|
+
message: this.buildMessage(det),
|
|
43381
|
+
file,
|
|
43382
|
+
line,
|
|
43383
|
+
fix: this.buildFix(det.issue),
|
|
43384
|
+
evidence: { ...det, language }
|
|
43385
|
+
});
|
|
43386
|
+
};
|
|
43004
43387
|
for (const call of graph.ir.calls) {
|
|
43005
|
-
const
|
|
43006
|
-
|
|
43007
|
-
|
|
43008
|
-
|
|
43009
|
-
|
|
43010
|
-
|
|
43011
|
-
|
|
43012
|
-
|
|
43013
|
-
|
|
43014
|
-
|
|
43015
|
-
|
|
43016
|
-
severity: "high",
|
|
43017
|
-
level: "error",
|
|
43018
|
-
message,
|
|
43019
|
-
file,
|
|
43020
|
-
line,
|
|
43021
|
-
fix: this.buildFix(det.issue),
|
|
43022
|
-
evidence: { ...det, language }
|
|
43023
|
-
});
|
|
43388
|
+
for (const det of this.detect(call, language, constProp, literalBindings)) {
|
|
43389
|
+
emit(call.location.line, det);
|
|
43390
|
+
}
|
|
43391
|
+
}
|
|
43392
|
+
if (language === "csharp") {
|
|
43393
|
+
const lines = code.split(`
|
|
43394
|
+
`);
|
|
43395
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
43396
|
+
if (CS_CIPHER_MODE_ECB_RE.test(lines[i2])) {
|
|
43397
|
+
emit(i2 + 1, { issue: "ecb-mode", detail: "CipherMode.ECB", api: "SymmetricAlgorithm.Mode" });
|
|
43398
|
+
}
|
|
43024
43399
|
}
|
|
43025
43400
|
}
|
|
43026
43401
|
return { findings };
|
|
@@ -44610,6 +44985,8 @@ var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
|
44610
44985
|
var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
|
|
44611
44986
|
var INSECURE_SKIP_VERIFY_TRUE_RE = /\bInsecureSkipVerify\s*:\s*true\b/;
|
|
44612
44987
|
var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
|
|
44988
|
+
var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
|
|
44989
|
+
var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
|
|
44613
44990
|
var ALLOW_ALL_HOSTNAME_VERIFIERS = new Set([
|
|
44614
44991
|
"NoopHostnameVerifier.INSTANCE",
|
|
44615
44992
|
"new AllowAllHostnameVerifier()",
|
|
@@ -44758,6 +45135,21 @@ class TlsVerifyDisabledPass {
|
|
|
44758
45135
|
}
|
|
44759
45136
|
}
|
|
44760
45137
|
}
|
|
45138
|
+
if (language === "csharp") {
|
|
45139
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
45140
|
+
const l = lines[i2];
|
|
45141
|
+
const m = CS_CERT_CALLBACK_TRUE_RE.exec(l);
|
|
45142
|
+
if (m) {
|
|
45143
|
+
out2.push({ line: i2 + 1, pattern: `${m[1]} => true`, api: m[1] });
|
|
45144
|
+
} else if (CS_DANGEROUS_ACCEPT_RE.test(l)) {
|
|
45145
|
+
out2.push({
|
|
45146
|
+
line: i2 + 1,
|
|
45147
|
+
pattern: "DangerousAcceptAnyServerCertificateValidator",
|
|
45148
|
+
api: "HttpClientHandler"
|
|
45149
|
+
});
|
|
45150
|
+
}
|
|
45151
|
+
}
|
|
45152
|
+
}
|
|
44761
45153
|
return out2;
|
|
44762
45154
|
}
|
|
44763
45155
|
fixFor(language, pattern) {
|
|
@@ -44779,6 +45171,9 @@ class TlsVerifyDisabledPass {
|
|
|
44779
45171
|
if (pattern.includes("ssl._create_unverified_context")) {
|
|
44780
45172
|
return "Do not use `_create_unverified_context()`. Use `ssl.create_default_context()`.";
|
|
44781
45173
|
}
|
|
45174
|
+
if (language === "csharp") {
|
|
45175
|
+
return "Do not accept every certificate. Remove the always-true validation " + "callback (and `DangerousAcceptAnyServerCertificateValidator`); rely on the " + "platform default. To trust a private CA, validate the chain against it in " + "the callback instead of returning true.";
|
|
45176
|
+
}
|
|
44782
45177
|
return "Restore TLS certificate and hostname verification.";
|
|
44783
45178
|
}
|
|
44784
45179
|
}
|
|
@@ -45306,6 +45701,8 @@ var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
|
|
|
45306
45701
|
var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
45307
45702
|
var PY_ALG_NONE_RE = /\balgorithms\s*=\s*[\[\(]\s*["']none["']/i;
|
|
45308
45703
|
var JS_ALG_NONE_RE = /\balgorithms\s*:\s*\[\s*["']none["']/i;
|
|
45704
|
+
var CS_REQUIRE_SIGNED_FALSE_RE = /\bRequireSignedTokens\s*=\s*false\b/;
|
|
45705
|
+
var CS_SIGNATURE_VALIDATOR_BYPASS_RE = /\bSignatureValidator\s*=\s*[^;]*=>\s*new\s+JwtSecurityToken\b/;
|
|
45309
45706
|
|
|
45310
45707
|
class JwtVerifyDisabledPass {
|
|
45311
45708
|
name = "jwt-verify-disabled";
|
|
@@ -45314,25 +45711,36 @@ class JwtVerifyDisabledPass {
|
|
|
45314
45711
|
const { graph, language } = ctx;
|
|
45315
45712
|
const file = graph.ir.meta.file;
|
|
45316
45713
|
const findings = [];
|
|
45714
|
+
const emit = (line, det) => {
|
|
45715
|
+
findings.push({ line, language, ...det });
|
|
45716
|
+
ctx.addFinding({
|
|
45717
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
45718
|
+
pass: this.name,
|
|
45719
|
+
category: this.category,
|
|
45720
|
+
rule_id: this.name,
|
|
45721
|
+
cwe: "CWE-347",
|
|
45722
|
+
severity: "critical",
|
|
45723
|
+
level: "error",
|
|
45724
|
+
message: `JWT signature verification disabled via \`${det.pattern}\` in ` + `\`${det.api}\`. Any attacker can forge a token with arbitrary ` + "claims (user id, roles, expiry) since the signature is not " + "checked.",
|
|
45725
|
+
file,
|
|
45726
|
+
line,
|
|
45727
|
+
fix: this.fixFor(language),
|
|
45728
|
+
evidence: { ...det, language }
|
|
45729
|
+
});
|
|
45730
|
+
};
|
|
45317
45731
|
for (const call of graph.ir.calls) {
|
|
45318
|
-
const
|
|
45319
|
-
|
|
45320
|
-
|
|
45321
|
-
|
|
45322
|
-
|
|
45323
|
-
|
|
45324
|
-
|
|
45325
|
-
|
|
45326
|
-
|
|
45327
|
-
|
|
45328
|
-
|
|
45329
|
-
|
|
45330
|
-
message: `JWT signature verification disabled via \`${det.pattern}\` in ` + `\`${det.api}\`. Any attacker can forge a token with arbitrary ` + "claims (user id, roles, expiry) since the signature is not " + "checked.",
|
|
45331
|
-
file,
|
|
45332
|
-
line,
|
|
45333
|
-
fix: this.fixFor(language),
|
|
45334
|
-
evidence: { ...det, language }
|
|
45335
|
-
});
|
|
45732
|
+
for (const det of this.detect(call, language))
|
|
45733
|
+
emit(call.location.line, det);
|
|
45734
|
+
}
|
|
45735
|
+
if (language === "csharp") {
|
|
45736
|
+
const lines = ctx.code.split(`
|
|
45737
|
+
`);
|
|
45738
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
45739
|
+
if (CS_REQUIRE_SIGNED_FALSE_RE.test(lines[i2])) {
|
|
45740
|
+
emit(i2 + 1, { pattern: "RequireSignedTokens = false", api: "TokenValidationParameters" });
|
|
45741
|
+
} else if (CS_SIGNATURE_VALIDATOR_BYPASS_RE.test(lines[i2])) {
|
|
45742
|
+
emit(i2 + 1, { pattern: "SignatureValidator returns an unvalidated token", api: "TokenValidationParameters" });
|
|
45743
|
+
}
|
|
45336
45744
|
}
|
|
45337
45745
|
}
|
|
45338
45746
|
return { findings };
|
|
@@ -45406,6 +45814,9 @@ class JwtVerifyDisabledPass {
|
|
|
45406
45814
|
if (language === "java") {
|
|
45407
45815
|
return "For auth0/java-jwt: use `JWT.require(Algorithm.HMAC256(secret))` or " + "an RSA algorithm. For jjwt: call `parseClaimsJws(token)` (signature " + "enforced) rather than `parse(token)` (signature ignored).";
|
|
45408
45816
|
}
|
|
45817
|
+
if (language === "csharp") {
|
|
45818
|
+
return "Leave `RequireSignedTokens = true` and do not install a custom " + "`SignatureValidator` that returns the token unverified. Configure " + "`IssuerSigningKey`/`IssuerSigningKeys` and let the handler validate " + "the signature.";
|
|
45819
|
+
}
|
|
45409
45820
|
return "Enforce JWT signature verification with a concrete algorithm " + "(HS256/RS256/ES256). Never accept `alg: none`.";
|
|
45410
45821
|
}
|
|
45411
45822
|
}
|
|
@@ -48320,7 +48731,7 @@ var colors = {
|
|
|
48320
48731
|
};
|
|
48321
48732
|
|
|
48322
48733
|
// src/version.ts
|
|
48323
|
-
var version = "4.
|
|
48734
|
+
var version = "4.9.7";
|
|
48324
48735
|
|
|
48325
48736
|
// src/formatters.ts
|
|
48326
48737
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.7",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^4.
|
|
69
|
+
"circle-ir": "^4.9.7"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|