circle-ir 3.110.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.
@@ -10485,10 +10485,20 @@ var DEFAULT_SINKS = [
10485
10485
  { method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10486
10486
  { method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10487
10487
  { method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10488
- { method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10488
+ // cognium-dev #187 Sprint 54: arg_positions [0, 1] so the JS shell-mode shape
10489
+ // `spawn('sh', ['-c', tainted])` / `execFile('/bin/sh', ['-c', tainted])`
10490
+ // surfaces taint at the argv-array position (arg[1]).
10491
+ { method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10492
+ { method: "spawnSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10493
+ { method: "execFile", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10494
+ { method: "execFileSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10489
10495
  { method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10490
10496
  { method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10491
10497
  { method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10498
+ // execa (npm) — parses tainted shell-style strings into program+argv;
10499
+ // arg[0] is shell-injectable. cognium-dev #187 Sprint 54.
10500
+ { method: "command", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10501
+ { method: "commandSync", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10492
10502
  // Apache Commons Exec
10493
10503
  // Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
10494
10504
  { method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -11499,7 +11509,9 @@ var DEFAULT_SINKS = [
11499
11509
  { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
11500
11510
  // pymongo dynamic attribute-access pattern: `db.users.find({...})` — receiver
11501
11511
  // class isn't statically known. Method-only entries restricted to Python.
11502
- // cognium-dev#104 Sprint 22.
11512
+ // cognium-dev#104 Sprint 22, #194 Sprint 54 added `find`/`aggregate`.
11513
+ { method: "find", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11514
+ { method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11503
11515
  { method: "find_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11504
11516
  { method: "update_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
11505
11517
  { method: "update_many", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
@@ -12079,10 +12091,50 @@ var PYTHON_TAINTED_PATTERNS = [
12079
12091
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
12080
12092
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
12081
12093
  const sources = findSources(calls, types, config.sources, sourceLines, language);
12082
- const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
12094
+ const sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
12095
+ const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
12083
12096
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
12084
12097
  return { sources, sinks, sanitizers };
12085
12098
  }
12099
+ function expandPromisifyAliases(patterns, sourceLines, language) {
12100
+ if (!sourceLines || sourceLines.length === 0) return patterns;
12101
+ if (language !== "javascript" && language !== "typescript") return patterns;
12102
+ 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*\)/;
12103
+ const innerToPattern = /* @__PURE__ */ new Map();
12104
+ for (const p of patterns) {
12105
+ if (p.languages && !p.languages.includes(language)) continue;
12106
+ if (p.class !== void 0) continue;
12107
+ if (!innerToPattern.has(p.method)) innerToPattern.set(p.method, p);
12108
+ }
12109
+ for (const p of patterns) {
12110
+ if (p.languages && !p.languages.includes(language)) continue;
12111
+ if (innerToPattern.has(p.method)) continue;
12112
+ innerToPattern.set(p.method, p);
12113
+ }
12114
+ const aliasPatterns = [];
12115
+ const seenAliases = /* @__PURE__ */ new Set();
12116
+ for (const line of sourceLines) {
12117
+ const m = PROMISIFY_RE.exec(line);
12118
+ if (!m) continue;
12119
+ const aliasName = m[1];
12120
+ const innerName = m[2];
12121
+ if (!aliasName || !innerName) continue;
12122
+ if (seenAliases.has(aliasName)) continue;
12123
+ const template = innerToPattern.get(innerName);
12124
+ if (!template) continue;
12125
+ seenAliases.add(aliasName);
12126
+ aliasPatterns.push({
12127
+ method: aliasName,
12128
+ type: template.type,
12129
+ cwe: template.cwe,
12130
+ severity: template.severity,
12131
+ arg_positions: template.arg_positions,
12132
+ languages: [language],
12133
+ note: `util.promisify alias of ${innerName} \u2014 cognium-dev #187`
12134
+ });
12135
+ }
12136
+ return aliasPatterns.length === 0 ? patterns : patterns.concat(aliasPatterns);
12137
+ }
12086
12138
  function attachSourceLineCode(sources, sinks, code) {
12087
12139
  const lines = code.split("\n");
12088
12140
  for (const s of sources) {
@@ -12448,6 +12500,41 @@ function isSafeGoExecCommandCall(call, pattern, language) {
12448
12500
  if (SHELL_PROGRAMS.has(program)) return false;
12449
12501
  return true;
12450
12502
  }
12503
+ function isSafeJSChildProcessCall(call, pattern, language) {
12504
+ if (language !== "javascript" && language !== "typescript") return false;
12505
+ if (pattern.type !== "command_injection") return false;
12506
+ const m = call.method_name;
12507
+ if (m !== "spawn" && m !== "spawnSync" && m !== "execFile" && m !== "execFileSync") return false;
12508
+ const programArg = call.arguments.find((a) => a.position === 0);
12509
+ if (!programArg) return false;
12510
+ let program;
12511
+ if (programArg.literal !== null && programArg.literal !== void 0) {
12512
+ program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
12513
+ } else {
12514
+ const expr = (programArg.expression ?? "").trim();
12515
+ if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
12516
+ return false;
12517
+ }
12518
+ const stripped = expr.slice(1, -1);
12519
+ program = stripped.split("/").pop() ?? stripped;
12520
+ }
12521
+ const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
12522
+ "sh",
12523
+ "bash",
12524
+ "zsh",
12525
+ "dash",
12526
+ "ash",
12527
+ "ksh",
12528
+ "cmd",
12529
+ "cmd.exe",
12530
+ "powershell",
12531
+ "pwsh",
12532
+ "powershell.exe",
12533
+ "pwsh.exe"
12534
+ ]);
12535
+ if (SHELL_PROGRAMS.has(program)) return false;
12536
+ return true;
12537
+ }
12451
12538
  function isSafeRustCommandCall(call, pattern, language) {
12452
12539
  if (language !== "rust") return false;
12453
12540
  if (pattern.type !== "command_injection") return false;
@@ -12662,6 +12749,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12662
12749
  if (isSafeRustCommandCall(call, pattern, language)) {
12663
12750
  continue;
12664
12751
  }
12752
+ if (isSafeJSChildProcessCall(call, pattern, language)) {
12753
+ continue;
12754
+ }
12665
12755
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
12666
12756
  continue;
12667
12757
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.110.0",
3
+ "version": "3.111.0",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",