cognium-dev 3.107.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.
Files changed (2) hide show
  1. package/dist/cli.js +514 -30
  2. 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: "error", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11227
- { method: "debug", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11228
- { method: "critical", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11229
- { method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11230
- { method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11231
- { method: "error", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11232
- { method: "debug", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11233
- { method: "critical", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
11234
- { method: "log", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1] },
11235
- { method: "exception", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
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 sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
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",
@@ -24311,18 +24438,20 @@ class SinkFilterPass {
24311
24438
  if (!callArgs || callArgs.length === 0)
24312
24439
  return true;
24313
24440
  const sqlVar = callArgs[0]?.trim() ?? "";
24314
- if (!/^[A-Za-z_]\w*$/.test(sqlVar))
24315
- return true;
24316
- const lo = Math.max(0, sink.line - 31);
24317
- const assignRe = new RegExp(JAVA_SQL_ASSIGN_RE_TEMPLATE.replace("SQLVAR", sqlVar));
24318
24441
  let rhs = null;
24319
- for (let i2 = sink.line - 2;i2 >= lo; i2--) {
24320
- const ln = sourceLines[i2] ?? "";
24321
- const m = ln.match(assignRe);
24322
- if (m) {
24323
- rhs = m[1] ?? null;
24324
- break;
24442
+ if (/^[A-Za-z_]\w*$/.test(sqlVar)) {
24443
+ const lo = Math.max(0, sink.line - 31);
24444
+ const assignRe = new RegExp(JAVA_SQL_ASSIGN_RE_TEMPLATE.replace("SQLVAR", sqlVar));
24445
+ for (let i2 = sink.line - 2;i2 >= lo; i2--) {
24446
+ const ln = sourceLines[i2] ?? "";
24447
+ const m = ln.match(assignRe);
24448
+ if (m) {
24449
+ rhs = m[1] ?? null;
24450
+ break;
24451
+ }
24325
24452
  }
24453
+ } else if (/"[^"]*"/.test(sqlVar) && sqlVar.includes("+")) {
24454
+ rhs = sqlVar;
24326
24455
  }
24327
24456
  if (!rhs)
24328
24457
  return true;
@@ -24356,9 +24485,355 @@ class SinkFilterPass {
24356
24485
  return true;
24357
24486
  });
24358
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
+ }
24359
24595
  return { sources, sinks: filtered, sanitizers };
24360
24596
  }
24361
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
+ }
24362
24837
  function evalArithmetic(input) {
24363
24838
  let pos = 0;
24364
24839
  const len = input.length;
@@ -24774,7 +25249,10 @@ class TaintPropagationPass {
24774
25249
  const sink = sinkByLine.get(f.sink_line);
24775
25250
  if (!sink || !sink.code || !sink.method)
24776
25251
  return true;
24777
- return !isMongoValueBoundFilter(sink.code, sink.method);
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);
24778
25256
  });
24779
25257
  }
24780
25258
  }
@@ -24812,7 +25290,7 @@ class TaintPropagationPass {
24812
25290
  return { flows: finalFlows };
24813
25291
  }
24814
25292
  }
24815
- function isMongoValueBoundFilter(sinkCode, sinkMethod) {
25293
+ function isMongoValueBoundFilter(sinkCode, sinkMethod, sourceVar) {
24816
25294
  if (!sinkCode || !sinkMethod)
24817
25295
  return false;
24818
25296
  const callIdx = sinkCode.indexOf(`${sinkMethod}(`);
@@ -24880,6 +25358,12 @@ function isMongoValueBoundFilter(sinkCode, sinkMethod) {
24880
25358
  return false;
24881
25359
  if (/(['"])\$[A-Za-z_]\w*\1\s*:/.test(body2))
24882
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
+ }
24883
25367
  return true;
24884
25368
  }
24885
25369
  function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
@@ -27005,7 +27489,7 @@ class NullDerefPass {
27005
27489
  }
27006
27490
 
27007
27491
  // ../circle-ir/dist/analysis/passes/resource-leak-pass.js
27008
- function escapeRegex(s) {
27492
+ function escapeRegex2(s) {
27009
27493
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27010
27494
  }
27011
27495
  var FACTORY_METHOD_NAME_RE = /^(?:open|create|new|get|make|build)[A-Z]/;
@@ -27159,7 +27643,7 @@ class ResourceLeakPass {
27159
27643
  return false;
27160
27644
  }
27161
27645
  isReturnedToCaller(lines, variable, fromLine, toLine) {
27162
- const varRe = new RegExp(`\\breturn\\b[^;]*\\b${escapeRegex(variable)}\\b`);
27646
+ const varRe = new RegExp(`\\breturn\\b[^;]*\\b${escapeRegex2(variable)}\\b`);
27163
27647
  for (let l = fromLine;l <= toLine && l <= lines.length; l++) {
27164
27648
  if (varRe.test(lines[l - 1] ?? ""))
27165
27649
  return true;
@@ -27167,7 +27651,7 @@ class ResourceLeakPass {
27167
27651
  return false;
27168
27652
  }
27169
27653
  fieldStoredName(lines, variable, openLine, toLine) {
27170
- const thisAssignRe = new RegExp(`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${escapeRegex(variable)}\\b`);
27654
+ const thisAssignRe = new RegExp(`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${escapeRegex2(variable)}\\b`);
27171
27655
  for (let l = openLine;l <= toLine && l <= lines.length; l++) {
27172
27656
  const m = thisAssignRe.exec(lines[l - 1] ?? "");
27173
27657
  if (m)
@@ -36198,7 +36682,7 @@ var colors = {
36198
36682
  };
36199
36683
 
36200
36684
  // src/version.ts
36201
- var version = "3.107.0";
36685
+ var version = "3.111.0";
36202
36686
 
36203
36687
  // src/formatters.ts
36204
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.107.0",
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.107.0"
69
+ "circle-ir": "^3.111.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",