circle-ir 3.110.0 → 3.112.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.
@@ -10551,10 +10551,20 @@ var DEFAULT_SINKS = [
10551
10551
  { method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10552
10552
  { method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10553
10553
  { method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10554
- { method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10554
+ // cognium-dev #187 Sprint 54: arg_positions [0, 1] so the JS shell-mode shape
10555
+ // `spawn('sh', ['-c', tainted])` / `execFile('/bin/sh', ['-c', tainted])`
10556
+ // surfaces taint at the argv-array position (arg[1]).
10557
+ { method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10558
+ { method: "spawnSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10559
+ { method: "execFile", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10560
+ { method: "execFileSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
10555
10561
  { method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10556
10562
  { method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10557
10563
  { method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10564
+ // execa (npm) — parses tainted shell-style strings into program+argv;
10565
+ // arg[0] is shell-injectable. cognium-dev #187 Sprint 54.
10566
+ { method: "command", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10567
+ { method: "commandSync", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10558
10568
  // Apache Commons Exec
10559
10569
  // Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
10560
10570
  { method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -11320,8 +11330,27 @@ var DEFAULT_SINKS = [
11320
11330
  // SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
11321
11331
  // Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
11322
11332
  { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11333
+ // sqlite3 (npm) — Database/Statement methods. The JS plugin resolves
11334
+ // `const db = new sqlite3.Database(...); db.all(sql)` to the resolution
11335
+ // target `Connection.all`, so class-scoped patterns matching `Connection`
11336
+ // hit (see #186 Sprint 55 matcher extension consulting call.resolution.target).
11337
+ // `exec`/`run`/`all`/`get`/`each` follow the same shape.
11338
+ { method: "all", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11339
+ { method: "run", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11340
+ { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11341
+ { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11342
+ { method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11323
11343
  // Browser DOM XSS sinks
11324
11344
  { method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
11345
+ // Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
11346
+ // These methods explicitly bypass Angular's built-in sanitizer; passing
11347
+ // tainted strings re-introduces DOM-injection risk. Distinctive method
11348
+ // names — classless + language-scoped is safe.
11349
+ { method: "bypassSecurityTrustHtml", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11350
+ { method: "bypassSecurityTrustScript", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11351
+ { method: "bypassSecurityTrustStyle", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11352
+ { method: "bypassSecurityTrustUrl", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11353
+ { method: "bypassSecurityTrustResourceUrl", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11325
11354
  // Express.js XSS (response methods)
11326
11355
  { method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
11327
11356
  { method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
@@ -11334,6 +11363,19 @@ var DEFAULT_SINKS = [
11334
11363
  { method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11335
11364
  { method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11336
11365
  { method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11366
+ // `new vm.Script(taint)` — Node core `vm` module compiles strings. The
11367
+ // JS plugin emits method_name = 'vm.Script' for the constructor call;
11368
+ // the matcher's dotted-simple-name fallback (taint-matcher.ts:1664) lets
11369
+ // pattern.method = 'Script' hit on method_name = 'vm.Script'. The
11370
+ // `class: 'constructor'` short-circuit accepts the no-receiver shape.
11371
+ // (#188 Sprint 55)
11372
+ { method: "Script", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11373
+ // `setImmediate(taintedString)` — like setTimeout/setInterval, Node will
11374
+ // evaluate the first argument when it is a string. The callback-shape
11375
+ // suppression in taint-matcher.ts:1342 already covers setTimeout/
11376
+ // setInterval; the Sprint 55 fix extends that gate to setImmediate too.
11377
+ // (#188 Sprint 55)
11378
+ { method: "setImmediate", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11337
11379
  // protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
11338
11380
  // tainted schema → code execution (CVE-2026-41242). Issue #94.
11339
11381
  { method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
@@ -11396,6 +11438,14 @@ var DEFAULT_SINKS = [
11396
11438
  // got library
11397
11439
  { method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
11398
11440
  { method: "post", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
11441
+ // got/request npm packages default-export a callable function:
11442
+ // const got = require('got'); got(req.query.url)
11443
+ // const request = require('request'); request(req.query.url, cb)
11444
+ // The classless method names are distinctive enough (`got`, `request`) that
11445
+ // the FP risk is acceptable; both are scoped to JS/TS so they don't leak
11446
+ // into other plugins. (#185 Sprint 55)
11447
+ { method: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11448
+ { method: "request", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11399
11449
  // superagent
11400
11450
  { method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
11401
11451
  { method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -11565,7 +11615,9 @@ var DEFAULT_SINKS = [
11565
11615
  { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
11566
11616
  // pymongo dynamic attribute-access pattern: `db.users.find({...})` — receiver
11567
11617
  // class isn't statically known. Method-only entries restricted to Python.
11568
- // cognium-dev#104 Sprint 22.
11618
+ // cognium-dev#104 Sprint 22, #194 Sprint 54 added `find`/`aggregate`.
11619
+ { method: "find", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11620
+ { method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11569
11621
  { method: "find_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11570
11622
  { method: "update_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
11571
11623
  { method: "update_many", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
@@ -12145,10 +12197,50 @@ var PYTHON_TAINTED_PATTERNS = [
12145
12197
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
12146
12198
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
12147
12199
  const sources = findSources(calls, types, config.sources, sourceLines, language);
12148
- const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
12200
+ const sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
12201
+ const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
12149
12202
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
12150
12203
  return { sources, sinks, sanitizers };
12151
12204
  }
12205
+ function expandPromisifyAliases(patterns, sourceLines, language) {
12206
+ if (!sourceLines || sourceLines.length === 0) return patterns;
12207
+ if (language !== "javascript" && language !== "typescript") return patterns;
12208
+ 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*\)/;
12209
+ const innerToPattern = /* @__PURE__ */ new Map();
12210
+ for (const p of patterns) {
12211
+ if (p.languages && !p.languages.includes(language)) continue;
12212
+ if (p.class !== void 0) continue;
12213
+ if (!innerToPattern.has(p.method)) innerToPattern.set(p.method, p);
12214
+ }
12215
+ for (const p of patterns) {
12216
+ if (p.languages && !p.languages.includes(language)) continue;
12217
+ if (innerToPattern.has(p.method)) continue;
12218
+ innerToPattern.set(p.method, p);
12219
+ }
12220
+ const aliasPatterns = [];
12221
+ const seenAliases = /* @__PURE__ */ new Set();
12222
+ for (const line of sourceLines) {
12223
+ const m = PROMISIFY_RE.exec(line);
12224
+ if (!m) continue;
12225
+ const aliasName = m[1];
12226
+ const innerName = m[2];
12227
+ if (!aliasName || !innerName) continue;
12228
+ if (seenAliases.has(aliasName)) continue;
12229
+ const template = innerToPattern.get(innerName);
12230
+ if (!template) continue;
12231
+ seenAliases.add(aliasName);
12232
+ aliasPatterns.push({
12233
+ method: aliasName,
12234
+ type: template.type,
12235
+ cwe: template.cwe,
12236
+ severity: template.severity,
12237
+ arg_positions: template.arg_positions,
12238
+ languages: [language],
12239
+ note: `util.promisify alias of ${innerName} \u2014 cognium-dev #187`
12240
+ });
12241
+ }
12242
+ return aliasPatterns.length === 0 ? patterns : patterns.concat(aliasPatterns);
12243
+ }
12152
12244
  function attachSourceLineCode(sources, sinks, code) {
12153
12245
  const lines = code.split("\n");
12154
12246
  for (const s of sources) {
@@ -12514,6 +12606,41 @@ function isSafeGoExecCommandCall(call, pattern, language) {
12514
12606
  if (SHELL_PROGRAMS.has(program)) return false;
12515
12607
  return true;
12516
12608
  }
12609
+ function isSafeJSChildProcessCall(call, pattern, language) {
12610
+ if (language !== "javascript" && language !== "typescript") return false;
12611
+ if (pattern.type !== "command_injection") return false;
12612
+ const m = call.method_name;
12613
+ if (m !== "spawn" && m !== "spawnSync" && m !== "execFile" && m !== "execFileSync") return false;
12614
+ const programArg = call.arguments.find((a) => a.position === 0);
12615
+ if (!programArg) return false;
12616
+ let program;
12617
+ if (programArg.literal !== null && programArg.literal !== void 0) {
12618
+ program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
12619
+ } else {
12620
+ const expr = (programArg.expression ?? "").trim();
12621
+ if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
12622
+ return false;
12623
+ }
12624
+ const stripped = expr.slice(1, -1);
12625
+ program = stripped.split("/").pop() ?? stripped;
12626
+ }
12627
+ const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
12628
+ "sh",
12629
+ "bash",
12630
+ "zsh",
12631
+ "dash",
12632
+ "ash",
12633
+ "ksh",
12634
+ "cmd",
12635
+ "cmd.exe",
12636
+ "powershell",
12637
+ "pwsh",
12638
+ "powershell.exe",
12639
+ "pwsh.exe"
12640
+ ]);
12641
+ if (SHELL_PROGRAMS.has(program)) return false;
12642
+ return true;
12643
+ }
12517
12644
  function isSafeRustCommandCall(call, pattern, language) {
12518
12645
  if (language !== "rust") return false;
12519
12646
  if (pattern.type !== "command_injection") return false;
@@ -12728,6 +12855,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12728
12855
  if (isSafeRustCommandCall(call, pattern, language)) {
12729
12856
  continue;
12730
12857
  }
12858
+ if (isSafeJSChildProcessCall(call, pattern, language)) {
12859
+ continue;
12860
+ }
12731
12861
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
12732
12862
  continue;
12733
12863
  }
@@ -12743,7 +12873,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12743
12873
  }
12744
12874
  }
12745
12875
  }
12746
- if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
12876
+ if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
12747
12877
  const firstArg = call.arguments.find((a) => a.position === 0);
12748
12878
  if (firstArg && isFunctionCallbackArgument(firstArg)) {
12749
12879
  continue;
@@ -13060,6 +13190,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
13060
13190
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
13061
13191
  return true;
13062
13192
  }
13193
+ const resolvedTarget = call.resolution?.target;
13194
+ const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
13195
+ if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
13196
+ return true;
13197
+ }
13063
13198
  if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
13064
13199
  return true;
13065
13200
  }
@@ -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] },
@@ -11254,8 +11264,27 @@ var DEFAULT_SINKS = [
11254
11264
  // SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
11255
11265
  // Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
11256
11266
  { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11267
+ // sqlite3 (npm) — Database/Statement methods. The JS plugin resolves
11268
+ // `const db = new sqlite3.Database(...); db.all(sql)` to the resolution
11269
+ // target `Connection.all`, so class-scoped patterns matching `Connection`
11270
+ // hit (see #186 Sprint 55 matcher extension consulting call.resolution.target).
11271
+ // `exec`/`run`/`all`/`get`/`each` follow the same shape.
11272
+ { method: "all", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11273
+ { method: "run", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11274
+ { method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11275
+ { method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11276
+ { method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
11257
11277
  // Browser DOM XSS sinks
11258
11278
  { method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
11279
+ // Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
11280
+ // These methods explicitly bypass Angular's built-in sanitizer; passing
11281
+ // tainted strings re-introduces DOM-injection risk. Distinctive method
11282
+ // names — classless + language-scoped is safe.
11283
+ { method: "bypassSecurityTrustHtml", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11284
+ { method: "bypassSecurityTrustScript", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11285
+ { method: "bypassSecurityTrustStyle", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11286
+ { method: "bypassSecurityTrustUrl", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11287
+ { method: "bypassSecurityTrustResourceUrl", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11259
11288
  // Express.js XSS (response methods)
11260
11289
  { method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
11261
11290
  { method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
@@ -11268,6 +11297,19 @@ var DEFAULT_SINKS = [
11268
11297
  { method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11269
11298
  { method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11270
11299
  { method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11300
+ // `new vm.Script(taint)` — Node core `vm` module compiles strings. The
11301
+ // JS plugin emits method_name = 'vm.Script' for the constructor call;
11302
+ // the matcher's dotted-simple-name fallback (taint-matcher.ts:1664) lets
11303
+ // pattern.method = 'Script' hit on method_name = 'vm.Script'. The
11304
+ // `class: 'constructor'` short-circuit accepts the no-receiver shape.
11305
+ // (#188 Sprint 55)
11306
+ { method: "Script", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11307
+ // `setImmediate(taintedString)` — like setTimeout/setInterval, Node will
11308
+ // evaluate the first argument when it is a string. The callback-shape
11309
+ // suppression in taint-matcher.ts:1342 already covers setTimeout/
11310
+ // setInterval; the Sprint 55 fix extends that gate to setImmediate too.
11311
+ // (#188 Sprint 55)
11312
+ { method: "setImmediate", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11271
11313
  // protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
11272
11314
  // tainted schema → code execution (CVE-2026-41242). Issue #94.
11273
11315
  { method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
@@ -11330,6 +11372,14 @@ var DEFAULT_SINKS = [
11330
11372
  // got library
11331
11373
  { method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
11332
11374
  { method: "post", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
11375
+ // got/request npm packages default-export a callable function:
11376
+ // const got = require('got'); got(req.query.url)
11377
+ // const request = require('request'); request(req.query.url, cb)
11378
+ // The classless method names are distinctive enough (`got`, `request`) that
11379
+ // the FP risk is acceptable; both are scoped to JS/TS so they don't leak
11380
+ // into other plugins. (#185 Sprint 55)
11381
+ { method: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11382
+ { method: "request", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
11333
11383
  // superagent
11334
11384
  { method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
11335
11385
  { method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -11499,7 +11549,9 @@ var DEFAULT_SINKS = [
11499
11549
  { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
11500
11550
  // pymongo dynamic attribute-access pattern: `db.users.find({...})` — receiver
11501
11551
  // class isn't statically known. Method-only entries restricted to Python.
11502
- // cognium-dev#104 Sprint 22.
11552
+ // cognium-dev#104 Sprint 22, #194 Sprint 54 added `find`/`aggregate`.
11553
+ { method: "find", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11554
+ { method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11503
11555
  { method: "find_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
11504
11556
  { method: "update_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
11505
11557
  { method: "update_many", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
@@ -12079,10 +12131,50 @@ var PYTHON_TAINTED_PATTERNS = [
12079
12131
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
12080
12132
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
12081
12133
  const sources = findSources(calls, types, config.sources, sourceLines, language);
12082
- const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
12134
+ const sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
12135
+ const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
12083
12136
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
12084
12137
  return { sources, sinks, sanitizers };
12085
12138
  }
12139
+ function expandPromisifyAliases(patterns, sourceLines, language) {
12140
+ if (!sourceLines || sourceLines.length === 0) return patterns;
12141
+ if (language !== "javascript" && language !== "typescript") return patterns;
12142
+ 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*\)/;
12143
+ const innerToPattern = /* @__PURE__ */ new Map();
12144
+ for (const p of patterns) {
12145
+ if (p.languages && !p.languages.includes(language)) continue;
12146
+ if (p.class !== void 0) continue;
12147
+ if (!innerToPattern.has(p.method)) innerToPattern.set(p.method, p);
12148
+ }
12149
+ for (const p of patterns) {
12150
+ if (p.languages && !p.languages.includes(language)) continue;
12151
+ if (innerToPattern.has(p.method)) continue;
12152
+ innerToPattern.set(p.method, p);
12153
+ }
12154
+ const aliasPatterns = [];
12155
+ const seenAliases = /* @__PURE__ */ new Set();
12156
+ for (const line of sourceLines) {
12157
+ const m = PROMISIFY_RE.exec(line);
12158
+ if (!m) continue;
12159
+ const aliasName = m[1];
12160
+ const innerName = m[2];
12161
+ if (!aliasName || !innerName) continue;
12162
+ if (seenAliases.has(aliasName)) continue;
12163
+ const template = innerToPattern.get(innerName);
12164
+ if (!template) continue;
12165
+ seenAliases.add(aliasName);
12166
+ aliasPatterns.push({
12167
+ method: aliasName,
12168
+ type: template.type,
12169
+ cwe: template.cwe,
12170
+ severity: template.severity,
12171
+ arg_positions: template.arg_positions,
12172
+ languages: [language],
12173
+ note: `util.promisify alias of ${innerName} \u2014 cognium-dev #187`
12174
+ });
12175
+ }
12176
+ return aliasPatterns.length === 0 ? patterns : patterns.concat(aliasPatterns);
12177
+ }
12086
12178
  function attachSourceLineCode(sources, sinks, code) {
12087
12179
  const lines = code.split("\n");
12088
12180
  for (const s of sources) {
@@ -12448,6 +12540,41 @@ function isSafeGoExecCommandCall(call, pattern, language) {
12448
12540
  if (SHELL_PROGRAMS.has(program)) return false;
12449
12541
  return true;
12450
12542
  }
12543
+ function isSafeJSChildProcessCall(call, pattern, language) {
12544
+ if (language !== "javascript" && language !== "typescript") return false;
12545
+ if (pattern.type !== "command_injection") return false;
12546
+ const m = call.method_name;
12547
+ if (m !== "spawn" && m !== "spawnSync" && m !== "execFile" && m !== "execFileSync") return false;
12548
+ const programArg = call.arguments.find((a) => a.position === 0);
12549
+ if (!programArg) return false;
12550
+ let program;
12551
+ if (programArg.literal !== null && programArg.literal !== void 0) {
12552
+ program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
12553
+ } else {
12554
+ const expr = (programArg.expression ?? "").trim();
12555
+ if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
12556
+ return false;
12557
+ }
12558
+ const stripped = expr.slice(1, -1);
12559
+ program = stripped.split("/").pop() ?? stripped;
12560
+ }
12561
+ const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
12562
+ "sh",
12563
+ "bash",
12564
+ "zsh",
12565
+ "dash",
12566
+ "ash",
12567
+ "ksh",
12568
+ "cmd",
12569
+ "cmd.exe",
12570
+ "powershell",
12571
+ "pwsh",
12572
+ "powershell.exe",
12573
+ "pwsh.exe"
12574
+ ]);
12575
+ if (SHELL_PROGRAMS.has(program)) return false;
12576
+ return true;
12577
+ }
12451
12578
  function isSafeRustCommandCall(call, pattern, language) {
12452
12579
  if (language !== "rust") return false;
12453
12580
  if (pattern.type !== "command_injection") return false;
@@ -12662,6 +12789,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12662
12789
  if (isSafeRustCommandCall(call, pattern, language)) {
12663
12790
  continue;
12664
12791
  }
12792
+ if (isSafeJSChildProcessCall(call, pattern, language)) {
12793
+ continue;
12794
+ }
12665
12795
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
12666
12796
  continue;
12667
12797
  }
@@ -12677,7 +12807,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12677
12807
  }
12678
12808
  }
12679
12809
  }
12680
- if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
12810
+ if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
12681
12811
  const firstArg = call.arguments.find((a) => a.position === 0);
12682
12812
  if (firstArg && isFunctionCallbackArgument(firstArg)) {
12683
12813
  continue;
@@ -12994,6 +13124,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12994
13124
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
12995
13125
  return true;
12996
13126
  }
13127
+ const resolvedTarget = call.resolution?.target;
13128
+ const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
13129
+ if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
13130
+ return true;
13131
+ }
12997
13132
  if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
12998
13133
  return true;
12999
13134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.110.0",
3
+ "version": "3.112.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",