cognium-dev 3.108.0 → 3.111.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 +502 -20
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -10510,10 +10510,15 @@ var DEFAULT_SINKS = [
|
|
|
10510
10510
|
{ method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10511
10511
|
{ method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10512
10512
|
{ method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10513
|
-
{ method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10513
|
+
{ method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10514
|
+
{ method: "spawnSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10515
|
+
{ method: "execFile", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10516
|
+
{ method: "execFileSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10514
10517
|
{ method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10515
10518
|
{ method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10516
10519
|
{ method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10520
|
+
{ method: "command", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10521
|
+
{ method: "commandSync", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10517
10522
|
{ method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10518
10523
|
{ method: "parse", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10519
10524
|
{ method: "addArgument", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
@@ -11211,6 +11216,8 @@ var DEFAULT_SINKS = [
|
|
|
11211
11216
|
{ method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
|
|
11212
11217
|
{ method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
|
|
11213
11218
|
{ method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
|
|
11219
|
+
{ method: "find", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11220
|
+
{ method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11214
11221
|
{ method: "find_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11215
11222
|
{ method: "update_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
|
|
11216
11223
|
{ method: "update_many", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
|
|
@@ -11221,18 +11228,24 @@ var DEFAULT_SINKS = [
|
|
|
11221
11228
|
{ method: "from_string", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11222
11229
|
{ method: "Template", class: "jinja2", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11223
11230
|
{ method: "Template", class: "mako", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11224
|
-
{ method: "info", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
11225
|
-
{ method: "warning", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
11226
|
-
{ method: "
|
|
11227
|
-
{ method: "
|
|
11228
|
-
{ method: "
|
|
11229
|
-
{ method: "
|
|
11230
|
-
{ method: "
|
|
11231
|
-
{ method: "
|
|
11232
|
-
{ method: "
|
|
11233
|
-
{ method: "
|
|
11234
|
-
{ method: "
|
|
11235
|
-
{ method: "
|
|
11231
|
+
{ method: "info", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11232
|
+
{ method: "warning", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11233
|
+
{ method: "warn", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11234
|
+
{ method: "error", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11235
|
+
{ method: "debug", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11236
|
+
{ method: "critical", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11237
|
+
{ method: "fatal", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11238
|
+
{ method: "exception", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11239
|
+
{ method: "log", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
11240
|
+
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11241
|
+
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11242
|
+
{ method: "warn", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11243
|
+
{ method: "error", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11244
|
+
{ method: "debug", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11245
|
+
{ method: "critical", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11246
|
+
{ method: "fatal", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11247
|
+
{ method: "log", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
11248
|
+
{ method: "exception", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11236
11249
|
{ method: "command", class: "ProcessBuilder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11237
11250
|
{ method: "step", class: "StepExecution", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11238
11251
|
{ method: "invokeMethod", class: "Script", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
@@ -11663,10 +11676,61 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
11663
11676
|
const sourceLines = code !== undefined ? code.split(`
|
|
11664
11677
|
`) : undefined;
|
|
11665
11678
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
11666
|
-
const
|
|
11679
|
+
const sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
11680
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
11667
11681
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
11668
11682
|
return { sources, sinks, sanitizers };
|
|
11669
11683
|
}
|
|
11684
|
+
function expandPromisifyAliases(patterns, sourceLines, language) {
|
|
11685
|
+
if (!sourceLines || sourceLines.length === 0)
|
|
11686
|
+
return patterns;
|
|
11687
|
+
if (language !== "javascript" && language !== "typescript")
|
|
11688
|
+
return patterns;
|
|
11689
|
+
const PROMISIFY_RE = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:util\s*\.\s*)?promisify\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/;
|
|
11690
|
+
const innerToPattern = new Map;
|
|
11691
|
+
for (const p of patterns) {
|
|
11692
|
+
if (p.languages && !p.languages.includes(language))
|
|
11693
|
+
continue;
|
|
11694
|
+
if (p.class !== undefined)
|
|
11695
|
+
continue;
|
|
11696
|
+
if (!innerToPattern.has(p.method))
|
|
11697
|
+
innerToPattern.set(p.method, p);
|
|
11698
|
+
}
|
|
11699
|
+
for (const p of patterns) {
|
|
11700
|
+
if (p.languages && !p.languages.includes(language))
|
|
11701
|
+
continue;
|
|
11702
|
+
if (innerToPattern.has(p.method))
|
|
11703
|
+
continue;
|
|
11704
|
+
innerToPattern.set(p.method, p);
|
|
11705
|
+
}
|
|
11706
|
+
const aliasPatterns = [];
|
|
11707
|
+
const seenAliases = new Set;
|
|
11708
|
+
for (const line of sourceLines) {
|
|
11709
|
+
const m = PROMISIFY_RE.exec(line);
|
|
11710
|
+
if (!m)
|
|
11711
|
+
continue;
|
|
11712
|
+
const aliasName = m[1];
|
|
11713
|
+
const innerName = m[2];
|
|
11714
|
+
if (!aliasName || !innerName)
|
|
11715
|
+
continue;
|
|
11716
|
+
if (seenAliases.has(aliasName))
|
|
11717
|
+
continue;
|
|
11718
|
+
const template = innerToPattern.get(innerName);
|
|
11719
|
+
if (!template)
|
|
11720
|
+
continue;
|
|
11721
|
+
seenAliases.add(aliasName);
|
|
11722
|
+
aliasPatterns.push({
|
|
11723
|
+
method: aliasName,
|
|
11724
|
+
type: template.type,
|
|
11725
|
+
cwe: template.cwe,
|
|
11726
|
+
severity: template.severity,
|
|
11727
|
+
arg_positions: template.arg_positions,
|
|
11728
|
+
languages: [language],
|
|
11729
|
+
note: `util.promisify alias of ${innerName} — cognium-dev #187`
|
|
11730
|
+
});
|
|
11731
|
+
}
|
|
11732
|
+
return aliasPatterns.length === 0 ? patterns : patterns.concat(aliasPatterns);
|
|
11733
|
+
}
|
|
11670
11734
|
function attachSourceLineCode(sources, sinks, code) {
|
|
11671
11735
|
const lines = code.split(`
|
|
11672
11736
|
`);
|
|
@@ -12047,6 +12111,46 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
12047
12111
|
return false;
|
|
12048
12112
|
return true;
|
|
12049
12113
|
}
|
|
12114
|
+
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
12115
|
+
if (language !== "javascript" && language !== "typescript")
|
|
12116
|
+
return false;
|
|
12117
|
+
if (pattern.type !== "command_injection")
|
|
12118
|
+
return false;
|
|
12119
|
+
const m = call.method_name;
|
|
12120
|
+
if (m !== "spawn" && m !== "spawnSync" && m !== "execFile" && m !== "execFileSync")
|
|
12121
|
+
return false;
|
|
12122
|
+
const programArg = call.arguments.find((a) => a.position === 0);
|
|
12123
|
+
if (!programArg)
|
|
12124
|
+
return false;
|
|
12125
|
+
let program;
|
|
12126
|
+
if (programArg.literal !== null && programArg.literal !== undefined) {
|
|
12127
|
+
program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
|
|
12128
|
+
} else {
|
|
12129
|
+
const expr = (programArg.expression ?? "").trim();
|
|
12130
|
+
if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
|
|
12131
|
+
return false;
|
|
12132
|
+
}
|
|
12133
|
+
const stripped = expr.slice(1, -1);
|
|
12134
|
+
program = stripped.split("/").pop() ?? stripped;
|
|
12135
|
+
}
|
|
12136
|
+
const SHELL_PROGRAMS = new Set([
|
|
12137
|
+
"sh",
|
|
12138
|
+
"bash",
|
|
12139
|
+
"zsh",
|
|
12140
|
+
"dash",
|
|
12141
|
+
"ash",
|
|
12142
|
+
"ksh",
|
|
12143
|
+
"cmd",
|
|
12144
|
+
"cmd.exe",
|
|
12145
|
+
"powershell",
|
|
12146
|
+
"pwsh",
|
|
12147
|
+
"powershell.exe",
|
|
12148
|
+
"pwsh.exe"
|
|
12149
|
+
]);
|
|
12150
|
+
if (SHELL_PROGRAMS.has(program))
|
|
12151
|
+
return false;
|
|
12152
|
+
return true;
|
|
12153
|
+
}
|
|
12050
12154
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
12051
12155
|
if (language !== "rust")
|
|
12052
12156
|
return false;
|
|
@@ -12283,6 +12387,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12283
12387
|
if (isSafeRustCommandCall(call, pattern, language)) {
|
|
12284
12388
|
continue;
|
|
12285
12389
|
}
|
|
12390
|
+
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
12391
|
+
continue;
|
|
12392
|
+
}
|
|
12286
12393
|
if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
12287
12394
|
continue;
|
|
12288
12395
|
}
|
|
@@ -12706,6 +12813,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12706
12813
|
return true;
|
|
12707
12814
|
}
|
|
12708
12815
|
}
|
|
12816
|
+
if (className === "Logger" && /\.getLogger\(.*\)$/.test(receiver)) {
|
|
12817
|
+
return true;
|
|
12818
|
+
}
|
|
12709
12819
|
}
|
|
12710
12820
|
if (receiver.includes("::") && receiver.endsWith(")")) {
|
|
12711
12821
|
const scopedMatch = receiver.match(/^(\w+)::(\w+)\(.*\)$/);
|
|
@@ -23569,6 +23679,23 @@ var JS_XSS_SANITIZERS = [
|
|
|
23569
23679
|
/\btrustAsHtml\s*\(/,
|
|
23570
23680
|
/\bbypassSecurityTrust/
|
|
23571
23681
|
];
|
|
23682
|
+
var JS_LOG_INJECTION_SANITIZERS = [
|
|
23683
|
+
/\bstripCrlf\s*\(/,
|
|
23684
|
+
/\bstripCRLF\s*\(/,
|
|
23685
|
+
/\bremoveNewlines\s*\(/,
|
|
23686
|
+
/\bsanitizeLogValue\s*\(/,
|
|
23687
|
+
/\.replace\(\s*\/\[\s*\\r\s*\\n\s*\]\/[gimsu]*\s*,\s*['"`]['"`]\s*\)/,
|
|
23688
|
+
/\.replace\(\s*\/\\r\\n\/[gimsu]*\s*,\s*['"`]['"`]\s*\)/
|
|
23689
|
+
];
|
|
23690
|
+
var PY_BUILTIN_LDAP_SANITIZERS = ["escape_filter_chars", "filter_format"];
|
|
23691
|
+
var PY_LDAP_METACHARS = ["(", ")", "=", "*", "\\"];
|
|
23692
|
+
var PY_DEF_RE = /^\s*def\s+([A-Za-z_]\w*)\s*\(\s*([A-Za-z_]\w*)\s*\)\s*:\s*$/;
|
|
23693
|
+
var PY_LDAP_STRIP_RETURN_RE = /^\s*return\s+re\.sub\(\s*r?["']\[([^"'\]]+)\]["']\s*,\s*r?["']["']\s*,\s*([A-Za-z_]\w*)\s*\)\s*$/;
|
|
23694
|
+
var PY_XML_PARSER_HARDENED_RE = /\bXMLParser\s*\([^)]*\bresolve_entities\s*=\s*False\b[^)]*\)/;
|
|
23695
|
+
var PY_SQL_EXEC_METHODS = new Set(["execute", "executemany"]);
|
|
23696
|
+
var PY_INLINE_FULLMATCH_RE = /\bre\s*\.\s*fullmatch\s*\(\s*r?"((?:[^"\\]|\\.)*)"/g;
|
|
23697
|
+
var PY_INLINE_MATCH_ANCHORED_RE = /\bre\s*\.\s*match\s*\(\s*r?"\^((?:[^"\\]|\\.)*)\$"/g;
|
|
23698
|
+
var PY_BIND_PLACEHOLDER_RE = /\?|%s|:[A-Za-z_]\w*/;
|
|
23572
23699
|
var DATA_PARSER_TYPES = new Set([
|
|
23573
23700
|
"Parser",
|
|
23574
23701
|
"CommandLine",
|
|
@@ -24358,9 +24485,355 @@ class SinkFilterPass {
|
|
|
24358
24485
|
return true;
|
|
24359
24486
|
});
|
|
24360
24487
|
}
|
|
24488
|
+
if (["javascript", "typescript"].includes(language)) {
|
|
24489
|
+
const sourceLines = ctx.code.split(`
|
|
24490
|
+
`);
|
|
24491
|
+
filtered = filtered.filter((sink) => {
|
|
24492
|
+
if (sink.type !== "log_injection")
|
|
24493
|
+
return true;
|
|
24494
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
24495
|
+
if (JS_LOG_INJECTION_SANITIZERS.some((p) => p.test(sinkLineText)))
|
|
24496
|
+
return false;
|
|
24497
|
+
const varNames = extractSourceIdentifiers(sinkLineText);
|
|
24498
|
+
for (const v of varNames) {
|
|
24499
|
+
if (isAssignedFromSanitizerPattern(sourceLines, sink.line, v, JS_LOG_INJECTION_SANITIZERS)) {
|
|
24500
|
+
return false;
|
|
24501
|
+
}
|
|
24502
|
+
}
|
|
24503
|
+
return true;
|
|
24504
|
+
});
|
|
24505
|
+
}
|
|
24506
|
+
if (language === "python") {
|
|
24507
|
+
const sourceLines = ctx.code.split(`
|
|
24508
|
+
`);
|
|
24509
|
+
const wrappers = findPythonLdapStripWrappers(sourceLines);
|
|
24510
|
+
const allLdapSanitizers = [...PY_BUILTIN_LDAP_SANITIZERS, ...wrappers];
|
|
24511
|
+
if (allLdapSanitizers.length > 0) {
|
|
24512
|
+
const ldapPatterns = allLdapSanitizers.map((name2) => new RegExp(`\\b${escapeRegex(name2)}\\s*\\(`));
|
|
24513
|
+
filtered = filtered.filter((sink) => {
|
|
24514
|
+
if (sink.type !== "ldap_injection")
|
|
24515
|
+
return true;
|
|
24516
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
24517
|
+
if (ldapPatterns.some((p) => p.test(sinkLineText)))
|
|
24518
|
+
return false;
|
|
24519
|
+
const varNames = extractSourceIdentifiers(sinkLineText);
|
|
24520
|
+
for (const v of varNames) {
|
|
24521
|
+
if (isAssignedFromSanitizerPattern(sourceLines, sink.line, v, ldapPatterns)) {
|
|
24522
|
+
return false;
|
|
24523
|
+
}
|
|
24524
|
+
}
|
|
24525
|
+
return true;
|
|
24526
|
+
});
|
|
24527
|
+
}
|
|
24528
|
+
}
|
|
24529
|
+
if (language === "python") {
|
|
24530
|
+
const sourceLines = ctx.code.split(`
|
|
24531
|
+
`);
|
|
24532
|
+
filtered = filtered.filter((sink) => {
|
|
24533
|
+
if (sink.type !== "xxe")
|
|
24534
|
+
return true;
|
|
24535
|
+
const lookback = 30;
|
|
24536
|
+
const lo = Math.max(0, sink.line - 1 - lookback);
|
|
24537
|
+
for (let i2 = sink.line - 2;i2 >= lo; i2--) {
|
|
24538
|
+
const ln = sourceLines[i2] ?? "";
|
|
24539
|
+
if (PY_XML_PARSER_HARDENED_RE.test(ln))
|
|
24540
|
+
return false;
|
|
24541
|
+
if (/^\s*def\s+\w+\s*\(/.test(ln))
|
|
24542
|
+
break;
|
|
24543
|
+
}
|
|
24544
|
+
return true;
|
|
24545
|
+
});
|
|
24546
|
+
}
|
|
24547
|
+
if (language === "python") {
|
|
24548
|
+
const sourceLines = ctx.code.split(`
|
|
24549
|
+
`);
|
|
24550
|
+
filtered = filtered.filter((sink) => {
|
|
24551
|
+
if (sink.type !== "sql_injection")
|
|
24552
|
+
return true;
|
|
24553
|
+
const method = sink.method ?? "";
|
|
24554
|
+
if (!PY_SQL_EXEC_METHODS.has(method))
|
|
24555
|
+
return true;
|
|
24556
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
24557
|
+
const fstrMatch = sinkLineText.match(/\bf"((?:[^"\\]|\\.)*)"|\bf'((?:[^'\\]|\\.)*)'/);
|
|
24558
|
+
if (!fstrMatch)
|
|
24559
|
+
return true;
|
|
24560
|
+
const fstrBody = fstrMatch[1] ?? fstrMatch[2] ?? "";
|
|
24561
|
+
const { literals, interps } = splitPyFstringTokens(fstrBody);
|
|
24562
|
+
if (interps.length === 0)
|
|
24563
|
+
return true;
|
|
24564
|
+
if (!literals.some((lit) => PY_BIND_PLACEHOLDER_RE.test(lit)))
|
|
24565
|
+
return true;
|
|
24566
|
+
let sawHelperWithGuard = false;
|
|
24567
|
+
for (const interp of interps) {
|
|
24568
|
+
const t = interp.trim();
|
|
24569
|
+
if (t === "")
|
|
24570
|
+
return true;
|
|
24571
|
+
if (/^-?\d+$/.test(t) || /^"(?:[^"\\]|\\.)*"$/.test(t) || /^'(?:[^'\\]|\\.)*'$/.test(t)) {
|
|
24572
|
+
continue;
|
|
24573
|
+
}
|
|
24574
|
+
let helperName = null;
|
|
24575
|
+
const directCall = t.match(/^([A-Za-z_]\w*)\s*\(/);
|
|
24576
|
+
if (directCall) {
|
|
24577
|
+
helperName = directCall[1] ?? null;
|
|
24578
|
+
} else if (/^[A-Za-z_]\w*$/.test(t)) {
|
|
24579
|
+
helperName = findPythonAssignedHelperCall(t, sink.line, sourceLines);
|
|
24580
|
+
if (helperName === null)
|
|
24581
|
+
return true;
|
|
24582
|
+
} else {
|
|
24583
|
+
return true;
|
|
24584
|
+
}
|
|
24585
|
+
const body2 = findPythonFunctionBody(helperName, sourceLines);
|
|
24586
|
+
if (body2 && pythonBodyHasInlineRegexAllowlistRaise(body2)) {
|
|
24587
|
+
sawHelperWithGuard = true;
|
|
24588
|
+
}
|
|
24589
|
+
}
|
|
24590
|
+
if (!sawHelperWithGuard)
|
|
24591
|
+
return true;
|
|
24592
|
+
return false;
|
|
24593
|
+
});
|
|
24594
|
+
}
|
|
24361
24595
|
return { sources, sinks: filtered, sanitizers };
|
|
24362
24596
|
}
|
|
24363
24597
|
}
|
|
24598
|
+
function escapeRegex(s) {
|
|
24599
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24600
|
+
}
|
|
24601
|
+
var RESERVED_KEYWORDS = new Set([
|
|
24602
|
+
"if",
|
|
24603
|
+
"else",
|
|
24604
|
+
"return",
|
|
24605
|
+
"const",
|
|
24606
|
+
"let",
|
|
24607
|
+
"var",
|
|
24608
|
+
"function",
|
|
24609
|
+
"class",
|
|
24610
|
+
"new",
|
|
24611
|
+
"this",
|
|
24612
|
+
"for",
|
|
24613
|
+
"while",
|
|
24614
|
+
"do",
|
|
24615
|
+
"break",
|
|
24616
|
+
"continue",
|
|
24617
|
+
"switch",
|
|
24618
|
+
"case",
|
|
24619
|
+
"default",
|
|
24620
|
+
"try",
|
|
24621
|
+
"catch",
|
|
24622
|
+
"finally",
|
|
24623
|
+
"throw",
|
|
24624
|
+
"typeof",
|
|
24625
|
+
"instanceof",
|
|
24626
|
+
"in",
|
|
24627
|
+
"of",
|
|
24628
|
+
"await",
|
|
24629
|
+
"async",
|
|
24630
|
+
"import",
|
|
24631
|
+
"export",
|
|
24632
|
+
"from",
|
|
24633
|
+
"as",
|
|
24634
|
+
"void",
|
|
24635
|
+
"null",
|
|
24636
|
+
"undefined",
|
|
24637
|
+
"true",
|
|
24638
|
+
"false",
|
|
24639
|
+
"super",
|
|
24640
|
+
"extends",
|
|
24641
|
+
"static",
|
|
24642
|
+
"def",
|
|
24643
|
+
"lambda",
|
|
24644
|
+
"yield",
|
|
24645
|
+
"pass",
|
|
24646
|
+
"with",
|
|
24647
|
+
"global",
|
|
24648
|
+
"nonlocal",
|
|
24649
|
+
"and",
|
|
24650
|
+
"or",
|
|
24651
|
+
"not",
|
|
24652
|
+
"is",
|
|
24653
|
+
"None",
|
|
24654
|
+
"True",
|
|
24655
|
+
"False",
|
|
24656
|
+
"raise",
|
|
24657
|
+
"except",
|
|
24658
|
+
"elif",
|
|
24659
|
+
"self",
|
|
24660
|
+
"cls",
|
|
24661
|
+
"print",
|
|
24662
|
+
"console",
|
|
24663
|
+
"req",
|
|
24664
|
+
"res",
|
|
24665
|
+
"request",
|
|
24666
|
+
"response"
|
|
24667
|
+
]);
|
|
24668
|
+
function extractSourceIdentifiers(text) {
|
|
24669
|
+
const out2 = [];
|
|
24670
|
+
const re = /\b([A-Za-z_]\w+)\b/g;
|
|
24671
|
+
let m;
|
|
24672
|
+
while ((m = re.exec(text)) !== null) {
|
|
24673
|
+
const name2 = m[1];
|
|
24674
|
+
if (!RESERVED_KEYWORDS.has(name2))
|
|
24675
|
+
out2.push(name2);
|
|
24676
|
+
}
|
|
24677
|
+
return out2;
|
|
24678
|
+
}
|
|
24679
|
+
function isAssignedFromSanitizerPattern(sourceLines, sinkLine, varName, sanitizerPatterns, lookback = 30) {
|
|
24680
|
+
const escapedVar = escapeRegex(varName);
|
|
24681
|
+
const assignRe = new RegExp(`(?:\\b(?:const|let|var|final|String)\\s+)?\\b${escapedVar}\\s*=\\s*(.+)`);
|
|
24682
|
+
const fnBoundaryRe = /^\s*(?:def\s+\w+\s*\(|function\s+\w+\s*\(|\w+\s*[:=]\s*(?:async\s+)?\(?[^)]*\)?\s*=>)/;
|
|
24683
|
+
const lo = Math.max(0, sinkLine - 1 - lookback);
|
|
24684
|
+
for (let i2 = sinkLine - 2;i2 >= lo; i2--) {
|
|
24685
|
+
const ln = sourceLines[i2] ?? "";
|
|
24686
|
+
const m = ln.match(assignRe);
|
|
24687
|
+
if (m) {
|
|
24688
|
+
const rhs = (m[1] ?? "").trim();
|
|
24689
|
+
return sanitizerPatterns.some((p) => p.test(rhs));
|
|
24690
|
+
}
|
|
24691
|
+
if (fnBoundaryRe.test(ln))
|
|
24692
|
+
break;
|
|
24693
|
+
}
|
|
24694
|
+
return false;
|
|
24695
|
+
}
|
|
24696
|
+
function findPythonLdapStripWrappers(sourceLines) {
|
|
24697
|
+
const out2 = [];
|
|
24698
|
+
for (let i2 = 0;i2 < sourceLines.length; i2++) {
|
|
24699
|
+
const defMatch = sourceLines[i2].match(PY_DEF_RE);
|
|
24700
|
+
if (!defMatch)
|
|
24701
|
+
continue;
|
|
24702
|
+
const fnName = defMatch[1];
|
|
24703
|
+
const param = defMatch[2];
|
|
24704
|
+
for (let j = i2 + 1;j < Math.min(i2 + 4, sourceLines.length); j++) {
|
|
24705
|
+
const body2 = sourceLines[j];
|
|
24706
|
+
if (/^\s*$/.test(body2) || /^\s*#/.test(body2))
|
|
24707
|
+
continue;
|
|
24708
|
+
const ret = body2.match(PY_LDAP_STRIP_RETURN_RE);
|
|
24709
|
+
if (!ret)
|
|
24710
|
+
break;
|
|
24711
|
+
const charClass = ret[1];
|
|
24712
|
+
const argName = ret[2];
|
|
24713
|
+
if (argName !== param)
|
|
24714
|
+
break;
|
|
24715
|
+
const metaCount = PY_LDAP_METACHARS.filter((c) => charClass.includes(c)).length;
|
|
24716
|
+
if (metaCount >= 3)
|
|
24717
|
+
out2.push(fnName);
|
|
24718
|
+
break;
|
|
24719
|
+
}
|
|
24720
|
+
}
|
|
24721
|
+
return out2;
|
|
24722
|
+
}
|
|
24723
|
+
function splitPyFstringTokens(body2) {
|
|
24724
|
+
const literals = [];
|
|
24725
|
+
const interps = [];
|
|
24726
|
+
let i2 = 0;
|
|
24727
|
+
let cur = "";
|
|
24728
|
+
while (i2 < body2.length) {
|
|
24729
|
+
const ch = body2[i2] ?? "";
|
|
24730
|
+
if (ch === "{" && body2[i2 + 1] === "{") {
|
|
24731
|
+
cur += "{";
|
|
24732
|
+
i2 += 2;
|
|
24733
|
+
continue;
|
|
24734
|
+
}
|
|
24735
|
+
if (ch === "}" && body2[i2 + 1] === "}") {
|
|
24736
|
+
cur += "}";
|
|
24737
|
+
i2 += 2;
|
|
24738
|
+
continue;
|
|
24739
|
+
}
|
|
24740
|
+
if (ch === "{") {
|
|
24741
|
+
literals.push(cur);
|
|
24742
|
+
cur = "";
|
|
24743
|
+
i2++;
|
|
24744
|
+
let depth = 1;
|
|
24745
|
+
let expr = "";
|
|
24746
|
+
while (i2 < body2.length && depth > 0) {
|
|
24747
|
+
const ch2 = body2[i2] ?? "";
|
|
24748
|
+
if (ch2 === "{") {
|
|
24749
|
+
depth++;
|
|
24750
|
+
expr += ch2;
|
|
24751
|
+
i2++;
|
|
24752
|
+
continue;
|
|
24753
|
+
}
|
|
24754
|
+
if (ch2 === "}") {
|
|
24755
|
+
depth--;
|
|
24756
|
+
if (depth === 0) {
|
|
24757
|
+
i2++;
|
|
24758
|
+
break;
|
|
24759
|
+
}
|
|
24760
|
+
expr += ch2;
|
|
24761
|
+
i2++;
|
|
24762
|
+
continue;
|
|
24763
|
+
}
|
|
24764
|
+
expr += ch2;
|
|
24765
|
+
i2++;
|
|
24766
|
+
}
|
|
24767
|
+
const colonIdx = expr.indexOf(":");
|
|
24768
|
+
const interp = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
|
24769
|
+
interps.push(interp);
|
|
24770
|
+
continue;
|
|
24771
|
+
}
|
|
24772
|
+
cur += ch;
|
|
24773
|
+
i2++;
|
|
24774
|
+
}
|
|
24775
|
+
literals.push(cur);
|
|
24776
|
+
return { literals, interps };
|
|
24777
|
+
}
|
|
24778
|
+
function findPythonAssignedHelperCall(varName, sinkLine, sourceLines) {
|
|
24779
|
+
if (!/^[A-Za-z_]\w*$/.test(varName))
|
|
24780
|
+
return null;
|
|
24781
|
+
const lo = Math.max(0, sinkLine - 31);
|
|
24782
|
+
const assignRe = new RegExp(`^\\s*${varName}\\s*=\\s*([A-Za-z_]\\w*)\\s*\\(`);
|
|
24783
|
+
for (let i2 = sinkLine - 2;i2 >= lo; i2--) {
|
|
24784
|
+
const ln = sourceLines[i2] ?? "";
|
|
24785
|
+
const m = ln.match(assignRe);
|
|
24786
|
+
if (m)
|
|
24787
|
+
return m[1] ?? null;
|
|
24788
|
+
}
|
|
24789
|
+
return null;
|
|
24790
|
+
}
|
|
24791
|
+
function findPythonFunctionBody(funcName, sourceLines) {
|
|
24792
|
+
if (!/^[A-Za-z_]\w*$/.test(funcName))
|
|
24793
|
+
return null;
|
|
24794
|
+
const sigRe = new RegExp(`^(\\s*)def\\s+${funcName}\\s*\\(`);
|
|
24795
|
+
for (let i2 = 0;i2 < sourceLines.length; i2++) {
|
|
24796
|
+
const ln = sourceLines[i2] ?? "";
|
|
24797
|
+
const m = ln.match(sigRe);
|
|
24798
|
+
if (!m)
|
|
24799
|
+
continue;
|
|
24800
|
+
const baseIndent = (m[1] ?? "").length;
|
|
24801
|
+
const body2 = [];
|
|
24802
|
+
for (let j = i2 + 1;j < sourceLines.length; j++) {
|
|
24803
|
+
const ln2 = sourceLines[j] ?? "";
|
|
24804
|
+
if (ln2.trim() === "") {
|
|
24805
|
+
body2.push(ln2);
|
|
24806
|
+
continue;
|
|
24807
|
+
}
|
|
24808
|
+
const indentMatch = ln2.match(/^(\s*)/);
|
|
24809
|
+
const indent = (indentMatch?.[1] ?? "").length;
|
|
24810
|
+
if (indent <= baseIndent)
|
|
24811
|
+
break;
|
|
24812
|
+
body2.push(ln2);
|
|
24813
|
+
}
|
|
24814
|
+
return body2;
|
|
24815
|
+
}
|
|
24816
|
+
return null;
|
|
24817
|
+
}
|
|
24818
|
+
function pythonBodyHasInlineRegexAllowlistRaise(bodyLines) {
|
|
24819
|
+
const stripped = bodyLines.map((ln) => ln.replace(/#.*$/, ""));
|
|
24820
|
+
const text = stripped.join(`
|
|
24821
|
+
`);
|
|
24822
|
+
if (!/\braise\s+/.test(text))
|
|
24823
|
+
return false;
|
|
24824
|
+
PY_INLINE_FULLMATCH_RE.lastIndex = 0;
|
|
24825
|
+
let m;
|
|
24826
|
+
while ((m = PY_INLINE_FULLMATCH_RE.exec(text)) !== null) {
|
|
24827
|
+
if (isImplicitlyAnchoredAllowlistRegex(m[1] ?? ""))
|
|
24828
|
+
return true;
|
|
24829
|
+
}
|
|
24830
|
+
PY_INLINE_MATCH_ANCHORED_RE.lastIndex = 0;
|
|
24831
|
+
while ((m = PY_INLINE_MATCH_ANCHORED_RE.exec(text)) !== null) {
|
|
24832
|
+
if (isImplicitlyAnchoredAllowlistRegex(m[1] ?? ""))
|
|
24833
|
+
return true;
|
|
24834
|
+
}
|
|
24835
|
+
return false;
|
|
24836
|
+
}
|
|
24364
24837
|
function evalArithmetic(input) {
|
|
24365
24838
|
let pos = 0;
|
|
24366
24839
|
const len = input.length;
|
|
@@ -24776,7 +25249,10 @@ class TaintPropagationPass {
|
|
|
24776
25249
|
const sink = sinkByLine.get(f.sink_line);
|
|
24777
25250
|
if (!sink || !sink.code || !sink.method)
|
|
24778
25251
|
return true;
|
|
24779
|
-
|
|
25252
|
+
const sourceVar = f.path && f.path.length > 0 ? f.path[0].variable : null;
|
|
25253
|
+
const isHttpSource = typeof f.source_type === "string" && f.source_type.startsWith("http_");
|
|
25254
|
+
const sourceVarForFilter = isHttpSource ? sourceVar : null;
|
|
25255
|
+
return !isMongoValueBoundFilter(sink.code, sink.method, sourceVarForFilter);
|
|
24780
25256
|
});
|
|
24781
25257
|
}
|
|
24782
25258
|
}
|
|
@@ -24814,7 +25290,7 @@ class TaintPropagationPass {
|
|
|
24814
25290
|
return { flows: finalFlows };
|
|
24815
25291
|
}
|
|
24816
25292
|
}
|
|
24817
|
-
function isMongoValueBoundFilter(sinkCode, sinkMethod) {
|
|
25293
|
+
function isMongoValueBoundFilter(sinkCode, sinkMethod, sourceVar) {
|
|
24818
25294
|
if (!sinkCode || !sinkMethod)
|
|
24819
25295
|
return false;
|
|
24820
25296
|
const callIdx = sinkCode.indexOf(`${sinkMethod}(`);
|
|
@@ -24882,6 +25358,12 @@ function isMongoValueBoundFilter(sinkCode, sinkMethod) {
|
|
|
24882
25358
|
return false;
|
|
24883
25359
|
if (/(['"])\$[A-Za-z_]\w*\1\s*:/.test(body2))
|
|
24884
25360
|
return false;
|
|
25361
|
+
if (sourceVar && /^[A-Za-z_]\w*$/.test(sourceVar)) {
|
|
25362
|
+
const escaped = sourceVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25363
|
+
const tokenRe = new RegExp(`(?<![\\p{L}\\p{N}_])${escaped}(?![\\p{L}\\p{N}_])`, "u");
|
|
25364
|
+
if (tokenRe.test(stripped))
|
|
25365
|
+
return false;
|
|
25366
|
+
}
|
|
24885
25367
|
return true;
|
|
24886
25368
|
}
|
|
24887
25369
|
function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
|
|
@@ -27007,7 +27489,7 @@ class NullDerefPass {
|
|
|
27007
27489
|
}
|
|
27008
27490
|
|
|
27009
27491
|
// ../circle-ir/dist/analysis/passes/resource-leak-pass.js
|
|
27010
|
-
function
|
|
27492
|
+
function escapeRegex2(s) {
|
|
27011
27493
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27012
27494
|
}
|
|
27013
27495
|
var FACTORY_METHOD_NAME_RE = /^(?:open|create|new|get|make|build)[A-Z]/;
|
|
@@ -27161,7 +27643,7 @@ class ResourceLeakPass {
|
|
|
27161
27643
|
return false;
|
|
27162
27644
|
}
|
|
27163
27645
|
isReturnedToCaller(lines, variable, fromLine, toLine) {
|
|
27164
|
-
const varRe = new RegExp(`\\breturn\\b[^;]*\\b${
|
|
27646
|
+
const varRe = new RegExp(`\\breturn\\b[^;]*\\b${escapeRegex2(variable)}\\b`);
|
|
27165
27647
|
for (let l = fromLine;l <= toLine && l <= lines.length; l++) {
|
|
27166
27648
|
if (varRe.test(lines[l - 1] ?? ""))
|
|
27167
27649
|
return true;
|
|
@@ -27169,7 +27651,7 @@ class ResourceLeakPass {
|
|
|
27169
27651
|
return false;
|
|
27170
27652
|
}
|
|
27171
27653
|
fieldStoredName(lines, variable, openLine, toLine) {
|
|
27172
|
-
const thisAssignRe = new RegExp(`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${
|
|
27654
|
+
const thisAssignRe = new RegExp(`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${escapeRegex2(variable)}\\b`);
|
|
27173
27655
|
for (let l = openLine;l <= toLine && l <= lines.length; l++) {
|
|
27174
27656
|
const m = thisAssignRe.exec(lines[l - 1] ?? "");
|
|
27175
27657
|
if (m)
|
|
@@ -36200,7 +36682,7 @@ var colors = {
|
|
|
36200
36682
|
};
|
|
36201
36683
|
|
|
36202
36684
|
// src/version.ts
|
|
36203
|
-
var version = "3.
|
|
36685
|
+
var version = "3.111.0";
|
|
36204
36686
|
|
|
36205
36687
|
// src/formatters.ts
|
|
36206
36688
|
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": "3.
|
|
3
|
+
"version": "3.111.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",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.111.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|