cognium-dev 3.54.0 → 3.56.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/dist/cli.js +535 -14
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -10476,8 +10476,8 @@ var DEFAULT_SINKS = [
|
|
|
10476
10476
|
{ method: "print", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
10477
10477
|
{ method: "println", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
10478
10478
|
{ method: "sendError", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
10479
|
-
{ method: "setHeader", class: "HttpServletResponse", type: "
|
|
10480
|
-
{ method: "addHeader", class: "HttpServletResponse", type: "
|
|
10479
|
+
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10480
|
+
{ method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10481
10481
|
{ method: "setContentType", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
|
|
10482
10482
|
{ method: "setAttribute", class: "PageContext", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
10483
10483
|
{ method: "addAttribute", class: "Model", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
@@ -11163,7 +11163,18 @@ var DEFAULT_SINKS = [
|
|
|
11163
11163
|
{ method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
11164
11164
|
{ method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
11165
11165
|
{ method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
11166
|
-
{ method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] }
|
|
11166
|
+
{ method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
11167
|
+
{ method: "setHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
11168
|
+
{ method: "writeHead", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [2], languages: ["javascript", "typescript"] },
|
|
11169
|
+
{ method: "cookie", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
11170
|
+
{ method: "location", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11171
|
+
{ method: "redirect", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11172
|
+
{ method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
11173
|
+
{ method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
11174
|
+
{ method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11175
|
+
{ method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11176
|
+
{ method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11177
|
+
{ method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] }
|
|
11167
11178
|
];
|
|
11168
11179
|
var DEFAULT_SANITIZERS = [
|
|
11169
11180
|
{ method: "setString", class: "PreparedStatement", removes: ["sql_injection"] },
|
|
@@ -21503,7 +21514,9 @@ var KNOWN_SINK_TYPES = new Set([
|
|
|
21503
21514
|
"code_injection",
|
|
21504
21515
|
"mybatis_mapper_call",
|
|
21505
21516
|
"redos",
|
|
21506
|
-
"format_string"
|
|
21517
|
+
"format_string",
|
|
21518
|
+
"crlf",
|
|
21519
|
+
"mass_assignment"
|
|
21507
21520
|
]);
|
|
21508
21521
|
function checkSanitized(_fromLine, toLine, sinkType, sanitizersByLine) {
|
|
21509
21522
|
const sanitizersAtTarget = sanitizersByLine.get(toLine);
|
|
@@ -21558,19 +21571,19 @@ function buildTaintFlow(source, sink, taintInfo) {
|
|
|
21558
21571
|
// ../circle-ir/dist/analysis/findings.js
|
|
21559
21572
|
function canSourceReachSink(sourceType, sinkType) {
|
|
21560
21573
|
const sourceToSinkMapping = {
|
|
21561
|
-
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection"],
|
|
21562
|
-
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call"],
|
|
21563
|
-
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection"],
|
|
21564
|
-
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection"],
|
|
21574
|
+
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
|
|
21575
|
+
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
|
|
21576
|
+
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf"],
|
|
21577
|
+
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf"],
|
|
21565
21578
|
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call"],
|
|
21566
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection"],
|
|
21579
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
|
|
21567
21580
|
io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss"],
|
|
21568
21581
|
env_input: ["command_injection", "path_traversal"],
|
|
21569
21582
|
db_input: ["xss", "sql_injection"],
|
|
21570
21583
|
file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
|
|
21571
21584
|
network_input: ["sql_injection", "command_injection", "xss", "ssrf"],
|
|
21572
21585
|
config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf"],
|
|
21573
|
-
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call"],
|
|
21586
|
+
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
|
|
21574
21587
|
plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection"]
|
|
21575
21588
|
};
|
|
21576
21589
|
const validSinks = sourceToSinkMapping[sourceType];
|
|
@@ -27501,6 +27514,113 @@ function detectHardcodedKeyJava(call) {
|
|
|
27501
27514
|
return `literal string`;
|
|
27502
27515
|
return null;
|
|
27503
27516
|
}
|
|
27517
|
+
function detectHardcodedKeyPython(call, constProp, literalBindings) {
|
|
27518
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
27519
|
+
if (!arg)
|
|
27520
|
+
return null;
|
|
27521
|
+
const expr = (arg.expression ?? arg.literal ?? "").trim();
|
|
27522
|
+
if (!expr)
|
|
27523
|
+
return null;
|
|
27524
|
+
if (/^[bB][rR]?["'][^"']*["']$/.test(expr) || /^[rR][bB]["'][^"']*["']$/.test(expr)) {
|
|
27525
|
+
return `literal bytes ${expr.slice(0, 24)}${expr.length > 24 ? "…" : ""}`;
|
|
27526
|
+
}
|
|
27527
|
+
if (/^["'][^"']*["']$/.test(expr)) {
|
|
27528
|
+
return `literal string ${expr.slice(0, 24)}${expr.length > 24 ? "…" : ""}`;
|
|
27529
|
+
}
|
|
27530
|
+
if (arg.variable && constProp) {
|
|
27531
|
+
const sym = constProp.symbols.get(arg.variable);
|
|
27532
|
+
if (sym && sym.type === "string" && typeof sym.value === "string") {
|
|
27533
|
+
return `constant-propagated bytes from \`${arg.variable}\``;
|
|
27534
|
+
}
|
|
27535
|
+
}
|
|
27536
|
+
if (arg.variable) {
|
|
27537
|
+
const lit = literalBindings.get(arg.variable);
|
|
27538
|
+
if (lit) {
|
|
27539
|
+
return `literal-bound ${arg.variable} = ${lit.slice(0, 24)}${lit.length > 24 ? "…" : ""}`;
|
|
27540
|
+
}
|
|
27541
|
+
}
|
|
27542
|
+
return null;
|
|
27543
|
+
}
|
|
27544
|
+
function detectHardcodedKeyGo(call, constProp, literalBindings) {
|
|
27545
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
27546
|
+
if (!arg)
|
|
27547
|
+
return null;
|
|
27548
|
+
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
27549
|
+
if (!expr)
|
|
27550
|
+
return null;
|
|
27551
|
+
if (/^\[\s*\]\s*byte\s*\(\s*["'`][^"'`]*["'`]\s*\)$/.test(expr)) {
|
|
27552
|
+
return `literal []byte("…")`;
|
|
27553
|
+
}
|
|
27554
|
+
if (/^\[\s*\]\s*byte\s*\{[^}]*\}$/.test(expr)) {
|
|
27555
|
+
return `literal []byte{…} composite`;
|
|
27556
|
+
}
|
|
27557
|
+
if (arg.variable && constProp) {
|
|
27558
|
+
const sym = constProp.symbols.get(arg.variable);
|
|
27559
|
+
if (sym && sym.type === "string" && typeof sym.value === "string") {
|
|
27560
|
+
return `constant-propagated key from \`${arg.variable}\``;
|
|
27561
|
+
}
|
|
27562
|
+
}
|
|
27563
|
+
if (arg.variable) {
|
|
27564
|
+
const lit = literalBindings.get(arg.variable);
|
|
27565
|
+
if (lit) {
|
|
27566
|
+
return `literal-bound ${arg.variable} = ${lit.slice(0, 24)}${lit.length > 24 ? "…" : ""}`;
|
|
27567
|
+
}
|
|
27568
|
+
}
|
|
27569
|
+
return null;
|
|
27570
|
+
}
|
|
27571
|
+
function parseWeakRsaKeySizePython(call) {
|
|
27572
|
+
for (const arg of call.arguments) {
|
|
27573
|
+
const expr = (arg.expression ?? "").trim();
|
|
27574
|
+
const lit = (arg.literal ?? "").trim();
|
|
27575
|
+
const m = expr.match(/^key_size\s*=\s*(-?\d+)\s*$/);
|
|
27576
|
+
if (m && m[1]) {
|
|
27577
|
+
const n = parseInt(m[1], 10);
|
|
27578
|
+
if (Number.isFinite(n) && n > 0 && n < 2048)
|
|
27579
|
+
return n;
|
|
27580
|
+
return null;
|
|
27581
|
+
}
|
|
27582
|
+
if (/^key_size\s*=/.test(expr) && lit) {
|
|
27583
|
+
const n = parseInt(lit, 10);
|
|
27584
|
+
if (Number.isFinite(n) && n > 0 && n < 2048)
|
|
27585
|
+
return n;
|
|
27586
|
+
}
|
|
27587
|
+
}
|
|
27588
|
+
return null;
|
|
27589
|
+
}
|
|
27590
|
+
function scanLiteralBindings(code, language) {
|
|
27591
|
+
const out2 = new Map;
|
|
27592
|
+
if (!code)
|
|
27593
|
+
return out2;
|
|
27594
|
+
if (language === "python") {
|
|
27595
|
+
const re = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(b[rR]?["'][^"']*["']|[rR]?b["'][^"']*["']|["'][^"']*["'])\s*(?:$|#)/gm;
|
|
27596
|
+
let m;
|
|
27597
|
+
while ((m = re.exec(code)) !== null) {
|
|
27598
|
+
if (m[1] && m[2])
|
|
27599
|
+
out2.set(m[1], m[2]);
|
|
27600
|
+
}
|
|
27601
|
+
return out2;
|
|
27602
|
+
}
|
|
27603
|
+
if (language === "go") {
|
|
27604
|
+
const reByte = /^[ \t]*(?:var\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*(?::=|=)\s*(\[\s*\]\s*byte\s*\(\s*["'`][^"'`]*["'`]\s*\))/gm;
|
|
27605
|
+
let m;
|
|
27606
|
+
while ((m = reByte.exec(code)) !== null) {
|
|
27607
|
+
if (m[1] && m[2])
|
|
27608
|
+
out2.set(m[1], m[2]);
|
|
27609
|
+
}
|
|
27610
|
+
const reStr = /^[ \t]*(?:var|const)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(["'`][^"'`]*["'`])/gm;
|
|
27611
|
+
while ((m = reStr.exec(code)) !== null) {
|
|
27612
|
+
if (m[1] && m[2])
|
|
27613
|
+
out2.set(m[1], m[2]);
|
|
27614
|
+
}
|
|
27615
|
+
const reShort = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)\s*:=\s*(["'`][^"'`]*["'`])/gm;
|
|
27616
|
+
while ((m = reShort.exec(code)) !== null) {
|
|
27617
|
+
if (m[1] && m[2])
|
|
27618
|
+
out2.set(m[1], m[2]);
|
|
27619
|
+
}
|
|
27620
|
+
return out2;
|
|
27621
|
+
}
|
|
27622
|
+
return out2;
|
|
27623
|
+
}
|
|
27504
27624
|
var ISSUE_CWE = {
|
|
27505
27625
|
"weak-cipher": "CWE-327",
|
|
27506
27626
|
"ecb-mode": "CWE-327",
|
|
@@ -27514,11 +27634,13 @@ class WeakCryptoPass {
|
|
|
27514
27634
|
name = "weak-crypto";
|
|
27515
27635
|
category = "security";
|
|
27516
27636
|
run(ctx) {
|
|
27517
|
-
const { graph, language } = ctx;
|
|
27637
|
+
const { graph, language, code } = ctx;
|
|
27518
27638
|
const file = graph.ir.meta.file;
|
|
27519
27639
|
const findings = [];
|
|
27640
|
+
const constProp = ctx.hasResult("constant-propagation") ? ctx.getResult("constant-propagation") : null;
|
|
27641
|
+
const literalBindings = scanLiteralBindings(code, language);
|
|
27520
27642
|
for (const call of graph.ir.calls) {
|
|
27521
|
-
const detections = this.detect(call, language);
|
|
27643
|
+
const detections = this.detect(call, language, constProp, literalBindings);
|
|
27522
27644
|
for (const det of detections) {
|
|
27523
27645
|
const line = call.location.line;
|
|
27524
27646
|
findings.push({ line, language, ...det });
|
|
@@ -27571,7 +27693,7 @@ class WeakCryptoPass {
|
|
|
27571
27693
|
return "Use AES-GCM (authenticated) or ChaCha20-Poly1305. Avoid DES, " + "3DES, RC2, RC4, Blowfish, and ECB mode. For asymmetric encryption " + "use RSA-OAEP with ≥2048-bit keys or modern curve-based schemes.";
|
|
27572
27694
|
}
|
|
27573
27695
|
}
|
|
27574
|
-
detect(call, language) {
|
|
27696
|
+
detect(call, language, constProp, literalBindings) {
|
|
27575
27697
|
const method = call.method_name;
|
|
27576
27698
|
const receiver = call.receiver ?? "";
|
|
27577
27699
|
const out2 = [];
|
|
@@ -27631,6 +27753,12 @@ class WeakCryptoPass {
|
|
|
27631
27753
|
out2.push({ issue: "ecb-mode", detail: "AES.MODE_ECB", api: `${receiver}.new` });
|
|
27632
27754
|
}
|
|
27633
27755
|
}
|
|
27756
|
+
if (lastSeg === "aes" || lastSeg.endsWith(".aes") || WEAK_CIPHER_BASES.has(lastSeg)) {
|
|
27757
|
+
const keyDetail = detectHardcodedKeyPython(call, constProp, literalBindings);
|
|
27758
|
+
if (keyDetail) {
|
|
27759
|
+
out2.push({ issue: "hardcoded-key", detail: keyDetail, api: `${receiver}.new` });
|
|
27760
|
+
}
|
|
27761
|
+
}
|
|
27634
27762
|
}
|
|
27635
27763
|
const isHazmatAlgos = receiver === "algorithms" || receiver.endsWith(".algorithms");
|
|
27636
27764
|
if (isHazmatAlgos) {
|
|
@@ -27639,6 +27767,25 @@ class WeakCryptoPass {
|
|
|
27639
27767
|
if (WEAK_CIPHER_BASES.has(normalized)) {
|
|
27640
27768
|
out2.push({ issue: "weak-cipher", detail: normalized, api: `algorithms.${method}` });
|
|
27641
27769
|
}
|
|
27770
|
+
if (m === "aes") {
|
|
27771
|
+
const keyDetail = detectHardcodedKeyPython(call, constProp, literalBindings);
|
|
27772
|
+
if (keyDetail) {
|
|
27773
|
+
out2.push({ issue: "hardcoded-key", detail: keyDetail, api: `algorithms.${method}` });
|
|
27774
|
+
}
|
|
27775
|
+
}
|
|
27776
|
+
}
|
|
27777
|
+
if (method === "ECB" && (receiver === "modes" || receiver.endsWith(".modes"))) {
|
|
27778
|
+
out2.push({ issue: "ecb-mode", detail: "modes.ECB()", api: `${receiver}.ECB` });
|
|
27779
|
+
}
|
|
27780
|
+
if (method === "generate_private_key" && (receiver === "rsa" || receiver === "dsa" || receiver.endsWith(".rsa") || receiver.endsWith(".dsa"))) {
|
|
27781
|
+
const n = parseWeakRsaKeySizePython(call);
|
|
27782
|
+
if (n !== null) {
|
|
27783
|
+
out2.push({
|
|
27784
|
+
issue: "weak-rsa-key",
|
|
27785
|
+
detail: String(n),
|
|
27786
|
+
api: `${receiver}.generate_private_key`
|
|
27787
|
+
});
|
|
27788
|
+
}
|
|
27642
27789
|
}
|
|
27643
27790
|
return out2;
|
|
27644
27791
|
}
|
|
@@ -27680,6 +27827,24 @@ class WeakCryptoPass {
|
|
|
27680
27827
|
if ((method === "NewECBEncrypter" || method === "NewECBDecrypter") && receiver === "cipher") {
|
|
27681
27828
|
out2.push({ issue: "ecb-mode", detail: method, api: `cipher.${method}` });
|
|
27682
27829
|
}
|
|
27830
|
+
if (receiver === "aes" && method === "NewCipher" || receiver === "des" && (method === "NewCipher" || method === "NewTripleDESCipher") || receiver === "rc4" && method === "NewCipher") {
|
|
27831
|
+
const keyDetail = detectHardcodedKeyGo(call, constProp, literalBindings);
|
|
27832
|
+
if (keyDetail) {
|
|
27833
|
+
out2.push({ issue: "hardcoded-key", detail: keyDetail, api: `${receiver}.${method}` });
|
|
27834
|
+
}
|
|
27835
|
+
}
|
|
27836
|
+
if (receiver === "rsa" && method === "GenerateKey") {
|
|
27837
|
+
const bitsArg = call.arguments.find((a) => a.position === 1);
|
|
27838
|
+
const expr = (bitsArg?.literal ?? bitsArg?.expression ?? "").trim();
|
|
27839
|
+
const n = parseInt(expr, 10);
|
|
27840
|
+
if (Number.isFinite(n) && n > 0 && n < 2048) {
|
|
27841
|
+
out2.push({
|
|
27842
|
+
issue: "weak-rsa-key",
|
|
27843
|
+
detail: String(n),
|
|
27844
|
+
api: "rsa.GenerateKey"
|
|
27845
|
+
});
|
|
27846
|
+
}
|
|
27847
|
+
}
|
|
27683
27848
|
return out2;
|
|
27684
27849
|
}
|
|
27685
27850
|
return out2;
|
|
@@ -28154,6 +28319,356 @@ class JwtVerifyDisabledPass {
|
|
|
28154
28319
|
}
|
|
28155
28320
|
}
|
|
28156
28321
|
|
|
28322
|
+
// ../circle-ir/dist/analysis/passes/csrf-protection-disabled-pass.js
|
|
28323
|
+
var JAVA_CSRF_DISABLE_RE = /\.csrf\s*\([^)]*\)\s*\.\s*disable\b/;
|
|
28324
|
+
var JAVA_CSRF_LAMBDA_DISABLE_RE = /\bcsrf\s*\(\s*\w+\s*->\s*\w+\s*\.\s*disable\s*\(/;
|
|
28325
|
+
var JAVA_CSRF_METHODREF_RE = /\bcsrf\s*\(\s*[\w.]+::disable\s*\)/;
|
|
28326
|
+
var JAVA_CSRF_NULL_REPO_RE = /\.csrfTokenRepository\s*\(\s*null\s*\)/;
|
|
28327
|
+
|
|
28328
|
+
class CsrfProtectionDisabledPass {
|
|
28329
|
+
name = "csrf-protection-disabled";
|
|
28330
|
+
category = "security";
|
|
28331
|
+
run(ctx) {
|
|
28332
|
+
const { graph, language } = ctx;
|
|
28333
|
+
const file = graph.ir.meta.file;
|
|
28334
|
+
const findings = [];
|
|
28335
|
+
for (const call of graph.ir.calls) {
|
|
28336
|
+
const detections = this.detectCall(call, language);
|
|
28337
|
+
for (const det of detections) {
|
|
28338
|
+
const line = call.location.line;
|
|
28339
|
+
findings.push({ line, language, ...det });
|
|
28340
|
+
ctx.addFinding({
|
|
28341
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28342
|
+
pass: this.name,
|
|
28343
|
+
category: this.category,
|
|
28344
|
+
rule_id: this.name,
|
|
28345
|
+
cwe: "CWE-352",
|
|
28346
|
+
severity: "critical",
|
|
28347
|
+
level: "error",
|
|
28348
|
+
message: `CSRF protection explicitly disabled via \`${det.pattern}\` ` + `(${det.api}). Any browser session can be silently used to ` + "perform state-changing requests from a malicious origin.",
|
|
28349
|
+
file,
|
|
28350
|
+
line,
|
|
28351
|
+
fix: this.fixFor(language),
|
|
28352
|
+
evidence: { ...det, language }
|
|
28353
|
+
});
|
|
28354
|
+
}
|
|
28355
|
+
}
|
|
28356
|
+
if (language === "java") {
|
|
28357
|
+
const src = ctx.code ?? "";
|
|
28358
|
+
if (src) {
|
|
28359
|
+
const lines = src.split(`
|
|
28360
|
+
`);
|
|
28361
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28362
|
+
const line = i2 + 1;
|
|
28363
|
+
const text = lines[i2] ?? "";
|
|
28364
|
+
let det = null;
|
|
28365
|
+
if (JAVA_CSRF_LAMBDA_DISABLE_RE.test(text)) {
|
|
28366
|
+
det = { pattern: "csrf(c -> c.disable())", api: "HttpSecurity.csrf" };
|
|
28367
|
+
} else if (JAVA_CSRF_METHODREF_RE.test(text)) {
|
|
28368
|
+
det = { pattern: "csrf(::disable)", api: "HttpSecurity.csrf" };
|
|
28369
|
+
} else if (JAVA_CSRF_NULL_REPO_RE.test(text)) {
|
|
28370
|
+
det = { pattern: "csrfTokenRepository(null)", api: "HttpSecurity.csrfTokenRepository" };
|
|
28371
|
+
} else if (JAVA_CSRF_DISABLE_RE.test(text)) {
|
|
28372
|
+
det = { pattern: "csrf().disable()", api: "HttpSecurity.csrf" };
|
|
28373
|
+
}
|
|
28374
|
+
if (det && !findings.some((f) => f.line === line && f.pattern === det.pattern)) {
|
|
28375
|
+
findings.push({ line, language, ...det });
|
|
28376
|
+
ctx.addFinding({
|
|
28377
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28378
|
+
pass: this.name,
|
|
28379
|
+
category: this.category,
|
|
28380
|
+
rule_id: this.name,
|
|
28381
|
+
cwe: "CWE-352",
|
|
28382
|
+
severity: "critical",
|
|
28383
|
+
level: "error",
|
|
28384
|
+
message: `CSRF protection explicitly disabled via \`${det.pattern}\` ` + `(${det.api}). Any browser session can be silently used to ` + "perform state-changing requests from a malicious origin.",
|
|
28385
|
+
file,
|
|
28386
|
+
line,
|
|
28387
|
+
fix: this.fixFor(language),
|
|
28388
|
+
evidence: { ...det, language }
|
|
28389
|
+
});
|
|
28390
|
+
}
|
|
28391
|
+
}
|
|
28392
|
+
}
|
|
28393
|
+
}
|
|
28394
|
+
if (language === "python") {
|
|
28395
|
+
const src = ctx.code ?? "";
|
|
28396
|
+
if (src) {
|
|
28397
|
+
const lines = src.split(`
|
|
28398
|
+
`);
|
|
28399
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28400
|
+
const text = lines[i2] ?? "";
|
|
28401
|
+
if (/^\s*@csrf_exempt\b/.test(text)) {
|
|
28402
|
+
const line = i2 + 1;
|
|
28403
|
+
const det = { pattern: "@csrf_exempt", api: "django.views.decorators.csrf" };
|
|
28404
|
+
findings.push({ line, language, ...det });
|
|
28405
|
+
ctx.addFinding({
|
|
28406
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
28407
|
+
pass: this.name,
|
|
28408
|
+
category: this.category,
|
|
28409
|
+
rule_id: this.name,
|
|
28410
|
+
cwe: "CWE-352",
|
|
28411
|
+
severity: "critical",
|
|
28412
|
+
level: "error",
|
|
28413
|
+
message: "Django view is decorated with `@csrf_exempt`, bypassing the " + "framework CSRF middleware for this endpoint. Any browser " + "session can be silently used to invoke this handler from " + "a malicious origin.",
|
|
28414
|
+
file,
|
|
28415
|
+
line,
|
|
28416
|
+
fix: this.fixFor(language),
|
|
28417
|
+
evidence: { ...det, language }
|
|
28418
|
+
});
|
|
28419
|
+
}
|
|
28420
|
+
}
|
|
28421
|
+
}
|
|
28422
|
+
}
|
|
28423
|
+
return { findings };
|
|
28424
|
+
}
|
|
28425
|
+
detectCall(call, language) {
|
|
28426
|
+
const out2 = [];
|
|
28427
|
+
if (language !== "java")
|
|
28428
|
+
return out2;
|
|
28429
|
+
if (call.method_name === "disable") {
|
|
28430
|
+
const recv = call.receiver ?? "";
|
|
28431
|
+
if (/\bcsrf\s*\(\s*\)\s*$/.test(recv) || recv.endsWith(".csrf()")) {
|
|
28432
|
+
out2.push({ pattern: "csrf().disable()", api: "HttpSecurity.csrf" });
|
|
28433
|
+
}
|
|
28434
|
+
}
|
|
28435
|
+
if (call.method_name === "csrfTokenRepository") {
|
|
28436
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
28437
|
+
const expr = (arg?.expression ?? arg?.literal ?? "").trim();
|
|
28438
|
+
if (expr === "null") {
|
|
28439
|
+
out2.push({
|
|
28440
|
+
pattern: "csrfTokenRepository(null)",
|
|
28441
|
+
api: "HttpSecurity.csrfTokenRepository"
|
|
28442
|
+
});
|
|
28443
|
+
}
|
|
28444
|
+
}
|
|
28445
|
+
return out2;
|
|
28446
|
+
}
|
|
28447
|
+
fixFor(language) {
|
|
28448
|
+
if (language === "java") {
|
|
28449
|
+
return "Leave Spring Security CSRF protection enabled. If you need to " + "exempt a specific endpoint (e.g. webhook), use " + '`.csrf(c -> c.ignoringRequestMatchers("/webhook"))` rather than ' + "`.disable()`. For stateless APIs, prefer a per-request token over " + "disabling CSRF entirely.";
|
|
28450
|
+
}
|
|
28451
|
+
if (language === "python") {
|
|
28452
|
+
return "Remove `@csrf_exempt`. For stateless API endpoints, use Django REST " + "Framework with a token / session auth backend that does not rely on " + "cookies. For webhook receivers, verify a shared-secret signature " + "instead of disabling CSRF.";
|
|
28453
|
+
}
|
|
28454
|
+
return "Re-enable framework CSRF protection or replace with origin / token validation.";
|
|
28455
|
+
}
|
|
28456
|
+
}
|
|
28457
|
+
|
|
28458
|
+
// ../circle-ir/dist/analysis/passes/xml-entity-expansion-pass.js
|
|
28459
|
+
var JAVA_FACTORIES = new Set([
|
|
28460
|
+
"SAXParserFactory",
|
|
28461
|
+
"DocumentBuilderFactory",
|
|
28462
|
+
"XMLInputFactory",
|
|
28463
|
+
"SchemaFactory",
|
|
28464
|
+
"TransformerFactory"
|
|
28465
|
+
]);
|
|
28466
|
+
var JAVA_SAFE_EVIDENCE_RE = /(disallow-doctype-decl|external-general-entities|external-parameter-entities|SUPPORT_DTD|ACCESS_EXTERNAL_DTD|ACCESS_EXTERNAL_SCHEMA|setXIncludeAware\s*\(\s*false\s*\)|setExpandEntityReferences\s*\(\s*false\s*\))/;
|
|
28467
|
+
var PY_LXML_PARSER_INSECURE_DEFAULT_RE = /\bresolve_entities\s*=\s*False\b/;
|
|
28468
|
+
|
|
28469
|
+
class XmlEntityExpansionPass {
|
|
28470
|
+
name = "xml-entity-expansion";
|
|
28471
|
+
category = "security";
|
|
28472
|
+
run(ctx) {
|
|
28473
|
+
const { graph, language } = ctx;
|
|
28474
|
+
const file = graph.ir.meta.file;
|
|
28475
|
+
const findings = [];
|
|
28476
|
+
const code = ctx.code ?? "";
|
|
28477
|
+
if (language === "java") {
|
|
28478
|
+
const safeInFile = JAVA_SAFE_EVIDENCE_RE.test(code);
|
|
28479
|
+
if (safeInFile)
|
|
28480
|
+
return { findings };
|
|
28481
|
+
for (const call of graph.ir.calls) {
|
|
28482
|
+
const det = this.detectJavaCall(call);
|
|
28483
|
+
if (!det)
|
|
28484
|
+
continue;
|
|
28485
|
+
const line = call.location.line;
|
|
28486
|
+
findings.push({ line, language, ...det });
|
|
28487
|
+
ctx.addFinding({
|
|
28488
|
+
id: `${this.name}-${file}-${line}-${det.api}`,
|
|
28489
|
+
pass: this.name,
|
|
28490
|
+
category: this.category,
|
|
28491
|
+
rule_id: this.name,
|
|
28492
|
+
cwe: det.cwe,
|
|
28493
|
+
severity: "high",
|
|
28494
|
+
level: "error",
|
|
28495
|
+
message: `${det.api} created without disabling DTD / external-entity ` + "processing. Vulnerable to billion-laughs / quadratic " + "blow-up DoS (CWE-776) and external-entity disclosure " + '(CWE-611). Add `setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` (or the equivalent) before ' + "parsing.",
|
|
28496
|
+
file,
|
|
28497
|
+
line,
|
|
28498
|
+
fix: this.fixForJava(det.api),
|
|
28499
|
+
evidence: { ...det, language, safeFeatureInFile: false }
|
|
28500
|
+
});
|
|
28501
|
+
}
|
|
28502
|
+
return { findings };
|
|
28503
|
+
}
|
|
28504
|
+
if (language === "python") {
|
|
28505
|
+
const safeInFile = PY_LXML_PARSER_INSECURE_DEFAULT_RE.test(code) || /\bdefusedxml\b/.test(code);
|
|
28506
|
+
if (safeInFile)
|
|
28507
|
+
return { findings };
|
|
28508
|
+
for (const call of graph.ir.calls) {
|
|
28509
|
+
const det = this.detectPythonCall(call);
|
|
28510
|
+
if (!det)
|
|
28511
|
+
continue;
|
|
28512
|
+
const line = call.location.line;
|
|
28513
|
+
findings.push({ line, language, ...det });
|
|
28514
|
+
ctx.addFinding({
|
|
28515
|
+
id: `${this.name}-${file}-${line}-${det.api}`,
|
|
28516
|
+
pass: this.name,
|
|
28517
|
+
category: this.category,
|
|
28518
|
+
rule_id: this.name,
|
|
28519
|
+
cwe: det.cwe,
|
|
28520
|
+
severity: "high",
|
|
28521
|
+
level: "error",
|
|
28522
|
+
message: `${det.api} called without an entity-safe parser. Vulnerable ` + "to billion-laughs / quadratic blow-up DoS (CWE-776) and " + "external-entity disclosure (CWE-611). Use `defusedxml` or pass " + "an `XMLParser(resolve_entities=False)` to lxml.",
|
|
28523
|
+
file,
|
|
28524
|
+
line,
|
|
28525
|
+
fix: this.fixForPython(det.api),
|
|
28526
|
+
evidence: { ...det, language, safeFeatureInFile: false }
|
|
28527
|
+
});
|
|
28528
|
+
}
|
|
28529
|
+
return { findings };
|
|
28530
|
+
}
|
|
28531
|
+
return { findings };
|
|
28532
|
+
}
|
|
28533
|
+
detectJavaCall(call) {
|
|
28534
|
+
if (call.method_name !== "newInstance")
|
|
28535
|
+
return null;
|
|
28536
|
+
const recv = call.receiver ?? "";
|
|
28537
|
+
const recvType = call.receiver_type ?? "";
|
|
28538
|
+
for (const factory of JAVA_FACTORIES) {
|
|
28539
|
+
if (recv === factory || recvType === factory || recv.endsWith("." + factory) || recvType.endsWith("." + factory)) {
|
|
28540
|
+
return {
|
|
28541
|
+
pattern: `${factory}.newInstance()`,
|
|
28542
|
+
api: factory,
|
|
28543
|
+
cwe: "CWE-776"
|
|
28544
|
+
};
|
|
28545
|
+
}
|
|
28546
|
+
}
|
|
28547
|
+
return null;
|
|
28548
|
+
}
|
|
28549
|
+
detectPythonCall(call) {
|
|
28550
|
+
const recv = call.receiver ?? "";
|
|
28551
|
+
const method = call.method_name;
|
|
28552
|
+
if ((method === "parse" || method === "fromstring" || method === "XML") && (recv === "etree" || recv.endsWith(".etree"))) {
|
|
28553
|
+
return {
|
|
28554
|
+
pattern: `etree.${method}`,
|
|
28555
|
+
api: `lxml.etree.${method}`,
|
|
28556
|
+
cwe: "CWE-776"
|
|
28557
|
+
};
|
|
28558
|
+
}
|
|
28559
|
+
if ((method === "parse" || method === "fromstring") && (recv === "ET" || recv === "ElementTree" || recv.endsWith(".ElementTree"))) {
|
|
28560
|
+
return {
|
|
28561
|
+
pattern: `ElementTree.${method}`,
|
|
28562
|
+
api: `xml.etree.ElementTree.${method}`,
|
|
28563
|
+
cwe: "CWE-776"
|
|
28564
|
+
};
|
|
28565
|
+
}
|
|
28566
|
+
return null;
|
|
28567
|
+
}
|
|
28568
|
+
fixForJava(api) {
|
|
28569
|
+
if (api === "SAXParserFactory") {
|
|
28570
|
+
return 'Call `factory.setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` and ' + "`factory.setXIncludeAware(false)` before `newSAXParser()`.";
|
|
28571
|
+
}
|
|
28572
|
+
if (api === "DocumentBuilderFactory") {
|
|
28573
|
+
return 'Call `factory.setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` and ' + "`factory.setExpandEntityReferences(false)` before " + "`newDocumentBuilder()`.";
|
|
28574
|
+
}
|
|
28575
|
+
if (api === "XMLInputFactory") {
|
|
28576
|
+
return "Call `factory.setProperty(XMLInputFactory.SUPPORT_DTD, false)` " + "and `factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_" + "ENTITIES, false)` before `createXMLStreamReader`.";
|
|
28577
|
+
}
|
|
28578
|
+
return "Use `XMLConstants.FEATURE_SECURE_PROCESSING` and explicitly disable " + "DTD / external-entity loading on the factory before parsing.";
|
|
28579
|
+
}
|
|
28580
|
+
fixForPython(api) {
|
|
28581
|
+
if (api.startsWith("lxml.etree")) {
|
|
28582
|
+
return "Pass an explicit parser: " + "`etree.parse(src, parser=etree.XMLParser(resolve_entities=False, " + "no_network=True))`. Even better, use the `defusedxml.lxml` wrapper.";
|
|
28583
|
+
}
|
|
28584
|
+
return "Replace `xml.etree.ElementTree` with `defusedxml.ElementTree`, which " + "disables DTD / entity processing by default.";
|
|
28585
|
+
}
|
|
28586
|
+
}
|
|
28587
|
+
|
|
28588
|
+
// ../circle-ir/dist/analysis/passes/mass-assignment-pass.js
|
|
28589
|
+
var PY_KWARGS_SPLAT_RE = /\*\*\s*(?:request|self\.request|flask\.request|ctx|self)\s*\.\s*(?:form|args|values|json|get_json\s*\(\s*\)|files|data)/;
|
|
28590
|
+
var JS_OBJECT_SPREAD_RE = /\{\s*\.\.\.\s*(?:req|request|ctx|context)(?:\.request)?\s*\.\s*(?:body|query|params|form)\b/;
|
|
28591
|
+
|
|
28592
|
+
class MassAssignmentPass {
|
|
28593
|
+
name = "mass-assignment";
|
|
28594
|
+
category = "security";
|
|
28595
|
+
run(ctx) {
|
|
28596
|
+
const { graph, language } = ctx;
|
|
28597
|
+
const file = graph.ir.meta.file;
|
|
28598
|
+
const findings = [];
|
|
28599
|
+
const code = ctx.code ?? "";
|
|
28600
|
+
if (!code)
|
|
28601
|
+
return { findings };
|
|
28602
|
+
const lines = code.split(`
|
|
28603
|
+
`);
|
|
28604
|
+
if (language === "python") {
|
|
28605
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28606
|
+
const text = lines[i2] ?? "";
|
|
28607
|
+
const m = PY_KWARGS_SPLAT_RE.exec(text);
|
|
28608
|
+
if (!m)
|
|
28609
|
+
continue;
|
|
28610
|
+
const line = i2 + 1;
|
|
28611
|
+
const det = {
|
|
28612
|
+
pattern: "**request.<bag>",
|
|
28613
|
+
match: m[0]
|
|
28614
|
+
};
|
|
28615
|
+
findings.push({
|
|
28616
|
+
line,
|
|
28617
|
+
language,
|
|
28618
|
+
pattern: det.pattern,
|
|
28619
|
+
snippet: text.trim().slice(0, 200)
|
|
28620
|
+
});
|
|
28621
|
+
ctx.addFinding({
|
|
28622
|
+
id: `${this.name}-${file}-${line}`,
|
|
28623
|
+
pass: this.name,
|
|
28624
|
+
category: this.category,
|
|
28625
|
+
rule_id: this.name,
|
|
28626
|
+
cwe: "CWE-915",
|
|
28627
|
+
severity: "high",
|
|
28628
|
+
level: "error",
|
|
28629
|
+
message: `HTTP request bag splatted into constructor / ORM helper via ` + `\`${det.match}\`. Every form field becomes a settable attribute ` + "on the domain object, including ones the endpoint did not " + "intend to expose (e.g. `is_admin`, `role`, `owner_id`).",
|
|
28630
|
+
file,
|
|
28631
|
+
line,
|
|
28632
|
+
fix: "Replace the `**` splat with an explicit allow-list: " + "`Model(name=request.form['name'], email=request.form['email'])`. " + "For Django, use a `ModelForm` / serializer with `fields = [...]`.",
|
|
28633
|
+
evidence: { pattern: det.pattern, match: det.match, language }
|
|
28634
|
+
});
|
|
28635
|
+
}
|
|
28636
|
+
return { findings };
|
|
28637
|
+
}
|
|
28638
|
+
if (language === "javascript" || language === "typescript") {
|
|
28639
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28640
|
+
const text = lines[i2] ?? "";
|
|
28641
|
+
const m = JS_OBJECT_SPREAD_RE.exec(text);
|
|
28642
|
+
if (!m)
|
|
28643
|
+
continue;
|
|
28644
|
+
const line = i2 + 1;
|
|
28645
|
+
findings.push({
|
|
28646
|
+
line,
|
|
28647
|
+
language,
|
|
28648
|
+
pattern: "{...req.<bag>}",
|
|
28649
|
+
snippet: text.trim().slice(0, 200)
|
|
28650
|
+
});
|
|
28651
|
+
ctx.addFinding({
|
|
28652
|
+
id: `${this.name}-${file}-${line}`,
|
|
28653
|
+
pass: this.name,
|
|
28654
|
+
category: this.category,
|
|
28655
|
+
rule_id: this.name,
|
|
28656
|
+
cwe: "CWE-915",
|
|
28657
|
+
severity: "high",
|
|
28658
|
+
level: "error",
|
|
28659
|
+
message: `HTTP request bag spread into object literal via \`${m[0]}\`. ` + "Every body field becomes a settable property on the resulting " + "object, including ones the endpoint did not intend to expose " + "(e.g. `isAdmin`, `role`, `ownerId`).",
|
|
28660
|
+
file,
|
|
28661
|
+
line,
|
|
28662
|
+
fix: "Replace the spread with an explicit pick: " + "`const { name, email } = req.body; const user = { name, email };`. " + "For ORMs, use a DTO / Zod schema with `.pick(...)` or " + "allow-list serializers.",
|
|
28663
|
+
evidence: { pattern: "{...req.<bag>}", match: m[0], language }
|
|
28664
|
+
});
|
|
28665
|
+
}
|
|
28666
|
+
return { findings };
|
|
28667
|
+
}
|
|
28668
|
+
return { findings };
|
|
28669
|
+
}
|
|
28670
|
+
}
|
|
28671
|
+
|
|
28157
28672
|
// ../circle-ir/dist/graph/import-graph.js
|
|
28158
28673
|
function dirname(filePath) {
|
|
28159
28674
|
const idx = filePath.lastIndexOf("/");
|
|
@@ -29276,6 +29791,12 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
29276
29791
|
pipeline.add(new TlsVerifyDisabledPass);
|
|
29277
29792
|
if (!disabledPasses.has("jwt-verify-disabled"))
|
|
29278
29793
|
pipeline.add(new JwtVerifyDisabledPass);
|
|
29794
|
+
if (!disabledPasses.has("csrf-protection-disabled"))
|
|
29795
|
+
pipeline.add(new CsrfProtectionDisabledPass);
|
|
29796
|
+
if (!disabledPasses.has("xml-entity-expansion"))
|
|
29797
|
+
pipeline.add(new XmlEntityExpansionPass);
|
|
29798
|
+
if (!disabledPasses.has("mass-assignment"))
|
|
29799
|
+
pipeline.add(new MassAssignmentPass);
|
|
29279
29800
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
29280
29801
|
const sinkFilter = results.get("sink-filter");
|
|
29281
29802
|
const interProc = results.get("interprocedural");
|
|
@@ -29469,7 +29990,7 @@ var colors = {
|
|
|
29469
29990
|
};
|
|
29470
29991
|
|
|
29471
29992
|
// src/version.ts
|
|
29472
|
-
var version = "3.
|
|
29993
|
+
var version = "3.56.0";
|
|
29473
29994
|
|
|
29474
29995
|
// src/formatters.ts
|
|
29475
29996
|
var SINK_SEVERITY = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.56.0",
|
|
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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"circle-ir": "^3.
|
|
68
|
+
"circle-ir": "^3.56.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|