cognium-dev 3.132.0 → 3.133.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 +479 -1
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -22610,6 +22610,9 @@ class LanguageSourcesPass {
22610
22610
  for (const finding of findGoLocationHeaderOpenRedirectFindings(code, graph.ir.meta.file)) {
22611
22611
  ctx.addFinding(finding);
22612
22612
  }
22613
+ for (const finding of findGoPluginOpenCodeInjectionFindings(code, graph.ir.meta.file)) {
22614
+ ctx.addFinding(finding);
22615
+ }
22613
22616
  }
22614
22617
  if (language === "python") {
22615
22618
  additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
@@ -22631,6 +22634,9 @@ class LanguageSourcesPass {
22631
22634
  for (const finding of findPythonHeadersSubscriptOpenRedirectFindings(code, graph.ir.meta.file)) {
22632
22635
  ctx.addFinding(finding);
22633
22636
  }
22637
+ for (const finding of findPythonInteractiveInterpreterCodeInjectionFindings(code, graph.ir.meta.file)) {
22638
+ ctx.addFinding(finding);
22639
+ }
22634
22640
  }
22635
22641
  if (language === "rust") {
22636
22642
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
@@ -22655,6 +22661,9 @@ class LanguageSourcesPass {
22655
22661
  for (const finding of findRustAppendHeaderTupleOpenRedirectFindings(code, graph.ir.meta.file)) {
22656
22662
  ctx.addFinding(finding);
22657
22663
  }
22664
+ for (const finding of findRustEvalCrateCodeInjectionFindings(code, graph.ir.meta.file)) {
22665
+ ctx.addFinding(finding);
22666
+ }
22658
22667
  }
22659
22668
  if (language === "javascript" || language === "typescript" || language === "htmljs") {
22660
22669
  additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
@@ -22676,6 +22685,9 @@ class LanguageSourcesPass {
22676
22685
  for (const finding of findJsDomOpenRedirectFindings(code, graph.ir.meta.file)) {
22677
22686
  ctx.addFinding(finding);
22678
22687
  }
22688
+ for (const finding of findJsIndirectEvalCodeInjectionFindings(code, graph.ir.meta.file)) {
22689
+ ctx.addFinding(finding);
22690
+ }
22679
22691
  }
22680
22692
  if (language === "java") {
22681
22693
  additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
@@ -26202,6 +26214,472 @@ function findJsDomOpenRedirectFindings(code, file) {
26202
26214
  }
26203
26215
  return out2;
26204
26216
  }
26217
+ function findGoPluginOpenCodeInjectionFindings(code, file) {
26218
+ const findings = [];
26219
+ if (typeof code !== "string" || code.length === 0)
26220
+ return findings;
26221
+ if (!/\bplugin\s*\.\s*(?:Open|Lookup)\s*\(/.test(code))
26222
+ return findings;
26223
+ const lines = code.split(`
26224
+ `);
26225
+ const reqExtractRe = /\b\w+\s*\.\s*(?:FormValue|PostFormValue|URL\.Query\(\)\.Get|Header\.Get|Cookie)\s*\(/;
26226
+ const httpReqParamRe = /\*\s*http\.Request\b/;
26227
+ const callRe = /\bplugin\s*\.\s*(?:Open|Lookup)\s*\(\s*([^)]*)\s*\)/;
26228
+ const sinkLabel = (op) => op === "Lookup" ? "Go plugin.Lookup" : "Go plugin.Open";
26229
+ const funcs = [];
26230
+ let cur = null;
26231
+ for (let i2 = 0;i2 < lines.length; i2++) {
26232
+ const t = lines[i2].trim();
26233
+ if (/^func\b/.test(t)) {
26234
+ if (cur) {
26235
+ cur.end = i2 - 1;
26236
+ funcs.push(cur);
26237
+ }
26238
+ cur = { start: i2, end: lines.length - 1 };
26239
+ }
26240
+ }
26241
+ if (cur)
26242
+ funcs.push(cur);
26243
+ for (const fn of funcs) {
26244
+ const header = lines[fn.start];
26245
+ if (!httpReqParamRe.test(header))
26246
+ continue;
26247
+ const taintedVars = new Set;
26248
+ for (let pass = 0;pass < 3; pass++) {
26249
+ const before = taintedVars.size;
26250
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
26251
+ const line = lines[i2];
26252
+ const trimmed = line.trim();
26253
+ const assignMatch = trimmed.match(/^(\w+)\s*(?::=|=)\s*(.+?)(?:\s*\/\/.*)?$/);
26254
+ if (!assignMatch)
26255
+ continue;
26256
+ const lhs = assignMatch[1];
26257
+ const rhs = assignMatch[2];
26258
+ if (taintedVars.has(lhs))
26259
+ continue;
26260
+ if (reqExtractRe.test(rhs)) {
26261
+ taintedVars.add(lhs);
26262
+ continue;
26263
+ }
26264
+ for (const v of taintedVars) {
26265
+ const re = new RegExp(`\\b${v}\\b`);
26266
+ if (re.test(rhs)) {
26267
+ taintedVars.add(lhs);
26268
+ break;
26269
+ }
26270
+ }
26271
+ }
26272
+ if (taintedVars.size === before)
26273
+ break;
26274
+ }
26275
+ if (taintedVars.size === 0)
26276
+ continue;
26277
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
26278
+ const line = lines[i2];
26279
+ const m = line.match(callRe);
26280
+ if (!m)
26281
+ continue;
26282
+ const arg = m[1].trim();
26283
+ if (arg.length === 0)
26284
+ continue;
26285
+ if (/^"[^"]*"$/.test(arg))
26286
+ continue;
26287
+ let tainted = false;
26288
+ if (reqExtractRe.test(arg))
26289
+ tainted = true;
26290
+ else {
26291
+ for (const v of taintedVars) {
26292
+ const re = new RegExp(`\\b${v}\\b`);
26293
+ if (re.test(arg)) {
26294
+ tainted = true;
26295
+ break;
26296
+ }
26297
+ }
26298
+ }
26299
+ if (!tainted)
26300
+ continue;
26301
+ const op = /\bplugin\s*\.\s*Lookup\b/.test(line) ? "Lookup" : "Open";
26302
+ findings.push({
26303
+ id: `code_injection-${file}-${i2 + 1}-go-plugin-${op.toLowerCase()}`,
26304
+ pass: "language-sources",
26305
+ category: "security",
26306
+ rule_id: "code_injection",
26307
+ cwe: "CWE-94",
26308
+ severity: "critical",
26309
+ level: "error",
26310
+ message: `Code injection: ${sinkLabel(op)} called with a path/symbol derived ` + "from an *http.Request without an allow-list. Loading a plugin " + "runs its init() and exposes arbitrary exported symbols. Restrict " + "the path to a trusted directory or use a fixed allow-list.",
26311
+ file,
26312
+ line: i2 + 1,
26313
+ snippet: line.trim()
26314
+ });
26315
+ }
26316
+ }
26317
+ return findings;
26318
+ }
26319
+ function findJsIndirectEvalCodeInjectionFindings(code, file) {
26320
+ const findings = [];
26321
+ if (typeof code !== "string" || code.length === 0)
26322
+ return findings;
26323
+ if (!/\beval\b/.test(code))
26324
+ return findings;
26325
+ const lines = code.split(`
26326
+ `);
26327
+ const reqExtractRe = /\breq(?:uest)?\s*\.\s*(?:body|query|params|headers|cookies)\b/;
26328
+ const aliasRe = /^\s*(?:const|let|var)\s+(\w+)\s*=\s*(?:globalThis\s*\.\s*eval|global\s*\.\s*eval|window\s*\.\s*eval|self\s*\.\s*eval|eval)\s*;?\s*$/;
26329
+ const aliases = new Set;
26330
+ for (const line of lines) {
26331
+ const m = line.match(aliasRe);
26332
+ if (m)
26333
+ aliases.add(m[1]);
26334
+ }
26335
+ const taintedVars = new Set;
26336
+ const assignRe = /^\s*(?:const|let|var)\s+(\w+)\s*=\s*(.+?);?\s*$/;
26337
+ const reassignRe = /^\s*(\w+)\s*=\s*(.+?);?\s*$/;
26338
+ for (let pass = 0;pass < 3; pass++) {
26339
+ const before = taintedVars.size;
26340
+ for (const line of lines) {
26341
+ const m = line.match(assignRe) || line.match(reassignRe);
26342
+ if (!m)
26343
+ continue;
26344
+ const lhs = m[1];
26345
+ const rhs = m[2];
26346
+ if (taintedVars.has(lhs))
26347
+ continue;
26348
+ if (lhs === "const" || lhs === "let" || lhs === "var")
26349
+ continue;
26350
+ if (reqExtractRe.test(rhs)) {
26351
+ taintedVars.add(lhs);
26352
+ continue;
26353
+ }
26354
+ for (const v of taintedVars) {
26355
+ const re = new RegExp(`\\b${v}\\b`);
26356
+ if (re.test(rhs)) {
26357
+ taintedVars.add(lhs);
26358
+ break;
26359
+ }
26360
+ }
26361
+ }
26362
+ if (taintedVars.size === before)
26363
+ break;
26364
+ }
26365
+ const indirectCommaRe = /\(\s*0\s*,\s*eval\s*\)\s*\(\s*([^)]*)\s*\)/;
26366
+ const indirectMemberRe = /\b(?:globalThis|global|window|self)\s*\.\s*eval\s*\(\s*([^)]*)\s*\)/;
26367
+ for (let i2 = 0;i2 < lines.length; i2++) {
26368
+ const line = lines[i2];
26369
+ const trimmed = line.trim();
26370
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
26371
+ continue;
26372
+ if (aliasRe.test(line))
26373
+ continue;
26374
+ let arg = null;
26375
+ let formLabel = "";
26376
+ let m = trimmed.match(indirectCommaRe);
26377
+ if (m) {
26378
+ arg = m[1].trim();
26379
+ formLabel = "(0, eval)(...) indirect eval";
26380
+ }
26381
+ if (!arg) {
26382
+ m = trimmed.match(indirectMemberRe);
26383
+ if (m) {
26384
+ arg = m[1].trim();
26385
+ formLabel = "globalThis.eval / window.eval / self.eval indirect eval";
26386
+ }
26387
+ }
26388
+ if (!arg && aliases.size > 0) {
26389
+ for (const a of aliases) {
26390
+ const aliasCallRe = new RegExp(`\\b${a}\\s*\\(\\s*([^)]*)\\s*\\)`);
26391
+ const mm = trimmed.match(aliasCallRe);
26392
+ if (mm) {
26393
+ arg = mm[1].trim();
26394
+ formLabel = `aliased eval reference \`${a}(...)\``;
26395
+ break;
26396
+ }
26397
+ }
26398
+ }
26399
+ if (arg === null)
26400
+ continue;
26401
+ if (arg.length === 0)
26402
+ continue;
26403
+ if (/^['"`][^'"`]*['"`]$/.test(arg))
26404
+ continue;
26405
+ let tainted = false;
26406
+ if (reqExtractRe.test(arg))
26407
+ tainted = true;
26408
+ else {
26409
+ for (const v of taintedVars) {
26410
+ const re = new RegExp(`\\b${v}\\b`);
26411
+ if (re.test(arg)) {
26412
+ tainted = true;
26413
+ break;
26414
+ }
26415
+ }
26416
+ }
26417
+ if (!tainted)
26418
+ continue;
26419
+ findings.push({
26420
+ id: `code_injection-${file}-${i2 + 1}-js-indirect-eval`,
26421
+ pass: "language-sources",
26422
+ category: "security",
26423
+ rule_id: "code_injection",
26424
+ cwe: "CWE-94",
26425
+ severity: "critical",
26426
+ level: "error",
26427
+ message: `Code injection: ${formLabel} called with a value derived from ` + "an HTTP request body/query/headers. Indirect eval forms still " + "execute arbitrary code in the global scope. Remove the eval and " + "parse the input with a safe data parser instead.",
26428
+ file,
26429
+ line: i2 + 1,
26430
+ snippet: trimmed
26431
+ });
26432
+ }
26433
+ return findings;
26434
+ }
26435
+ function findPythonInteractiveInterpreterCodeInjectionFindings(code, file) {
26436
+ const findings = [];
26437
+ if (typeof code !== "string" || code.length === 0)
26438
+ return findings;
26439
+ if (!/^\s*import\s+code\b/m.test(code))
26440
+ return findings;
26441
+ if (!/\bcode\s*\.\s*(?:InteractiveInterpreter|InteractiveConsole|compile_command)\b/.test(code)) {
26442
+ return findings;
26443
+ }
26444
+ const lines = code.split(`
26445
+ `);
26446
+ const reqExtractRe = /\brequest\s*\.\s*(?:args|form|values|files|json|cookies|headers|get_data|get_json)\b/;
26447
+ const taintedVars = new Set;
26448
+ const assignRe = /^\s*(\w+)\s*=\s*(.+?)\s*(?:#.*)?$/;
26449
+ for (let pass = 0;pass < 3; pass++) {
26450
+ const before = taintedVars.size;
26451
+ for (const line of lines) {
26452
+ const m = line.match(assignRe);
26453
+ if (!m)
26454
+ continue;
26455
+ const lhs = m[1];
26456
+ const rhs = m[2];
26457
+ if (taintedVars.has(lhs))
26458
+ continue;
26459
+ if (reqExtractRe.test(rhs)) {
26460
+ taintedVars.add(lhs);
26461
+ continue;
26462
+ }
26463
+ for (const v of taintedVars) {
26464
+ const re = new RegExp(`\\b${v}\\b`);
26465
+ if (re.test(rhs)) {
26466
+ taintedVars.add(lhs);
26467
+ break;
26468
+ }
26469
+ }
26470
+ }
26471
+ if (taintedVars.size === before)
26472
+ break;
26473
+ }
26474
+ const callRe = /\bcode\s*\.\s*(?:InteractiveInterpreter|InteractiveConsole)\s*\([^)]*\)\s*\.\s*(runsource|runcode|push|interact)\s*\(\s*([^),]+)/;
26475
+ const compileRe = /\bcode\s*\.\s*compile_command\s*\(\s*([^),]+)/;
26476
+ for (let i2 = 0;i2 < lines.length; i2++) {
26477
+ const line = lines[i2];
26478
+ const trimmed = line.trim();
26479
+ let arg = null;
26480
+ let formLabel = "";
26481
+ const m1 = trimmed.match(callRe);
26482
+ if (m1) {
26483
+ arg = m1[2].trim();
26484
+ formLabel = `code.${/Interpreter/.test(trimmed) ? "InteractiveInterpreter" : "InteractiveConsole"}().${m1[1]}`;
26485
+ }
26486
+ if (!arg) {
26487
+ const m2 = trimmed.match(compileRe);
26488
+ if (m2) {
26489
+ arg = m2[1].trim();
26490
+ formLabel = "code.compile_command";
26491
+ }
26492
+ }
26493
+ if (arg === null)
26494
+ continue;
26495
+ if (arg.length === 0)
26496
+ continue;
26497
+ if (/^['"][^'"]*['"]$/.test(arg))
26498
+ continue;
26499
+ let tainted = false;
26500
+ if (reqExtractRe.test(arg))
26501
+ tainted = true;
26502
+ else {
26503
+ for (const v of taintedVars) {
26504
+ const re = new RegExp(`\\b${v}\\b`);
26505
+ if (re.test(arg)) {
26506
+ tainted = true;
26507
+ break;
26508
+ }
26509
+ }
26510
+ }
26511
+ if (!tainted)
26512
+ continue;
26513
+ findings.push({
26514
+ id: `code_injection-${file}-${i2 + 1}-py-interactive-interpreter`,
26515
+ pass: "language-sources",
26516
+ category: "security",
26517
+ rule_id: "code_injection",
26518
+ cwe: "CWE-94",
26519
+ severity: "critical",
26520
+ level: "error",
26521
+ message: `Code injection: ${formLabel}(...) called with a value derived from ` + "a Flask request extractor. The Python `code` module compiles and " + "executes arbitrary source. Remove the call and validate input " + "against a fixed allow-list instead.",
26522
+ file,
26523
+ line: i2 + 1,
26524
+ snippet: trimmed
26525
+ });
26526
+ }
26527
+ return findings;
26528
+ }
26529
+ function findRustEvalCrateCodeInjectionFindings(code, file) {
26530
+ const findings = [];
26531
+ if (typeof code !== "string" || code.length === 0)
26532
+ return findings;
26533
+ if (!/\b(?:evalexpr\s*::\s*eval|libloading\s*::\s*Library\s*::\s*new|\.\s*load\s*\([^)]*\)\s*\.\s*(?:exec|eval|call))/.test(code)) {
26534
+ return findings;
26535
+ }
26536
+ const lines = code.split(`
26537
+ `);
26538
+ const extractorTypeRe = /:\s*(?:String|Bytes|bytes::Bytes|axum::body::Bytes|web::Query\b|web::Path\b|web::Form\b|web::Json\b|HttpRequest\b|actix_web::HttpRequest\b)/;
26539
+ const fns = [];
26540
+ let cur = null;
26541
+ for (let i2 = 0;i2 < lines.length; i2++) {
26542
+ const t = lines[i2];
26543
+ if (/^\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\(/.test(t)) {
26544
+ if (cur) {
26545
+ cur.end = i2 - 1;
26546
+ fns.push(cur);
26547
+ }
26548
+ cur = { start: i2, end: lines.length - 1, tainted: new Set };
26549
+ const headerJoined = (() => {
26550
+ let j = i2;
26551
+ let s = "";
26552
+ while (j < lines.length && !/\{\s*$/.test(s)) {
26553
+ s += lines[j];
26554
+ if (/\{\s*$/.test(lines[j]))
26555
+ break;
26556
+ j++;
26557
+ if (j - i2 > 12)
26558
+ break;
26559
+ }
26560
+ return s;
26561
+ })();
26562
+ const open = headerJoined.indexOf("(");
26563
+ const close = headerJoined.lastIndexOf(")");
26564
+ if (open !== -1 && close > open) {
26565
+ const params = headerJoined.substring(open + 1, close);
26566
+ let depth = 0;
26567
+ let buf = "";
26568
+ const parts2 = [];
26569
+ for (const ch of params) {
26570
+ if (ch === "<" || ch === "(")
26571
+ depth++;
26572
+ else if (ch === ">" || ch === ")")
26573
+ depth--;
26574
+ if (ch === "," && depth === 0) {
26575
+ parts2.push(buf);
26576
+ buf = "";
26577
+ continue;
26578
+ }
26579
+ buf += ch;
26580
+ }
26581
+ if (buf.trim().length > 0)
26582
+ parts2.push(buf);
26583
+ for (const p of parts2) {
26584
+ const pm = p.match(/(?:mut\s+)?(\w+)\s*:/);
26585
+ if (!pm)
26586
+ continue;
26587
+ if (extractorTypeRe.test(p))
26588
+ cur.tainted.add(pm[1]);
26589
+ }
26590
+ }
26591
+ }
26592
+ }
26593
+ if (cur)
26594
+ fns.push(cur);
26595
+ for (const fn of fns) {
26596
+ for (let pass = 0;pass < 3; pass++) {
26597
+ const before = fn.tainted.size;
26598
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
26599
+ const t = lines[i2].trim();
26600
+ const m = t.match(/^let\s+(?:mut\s+)?(\w+)\s*(?::\s*[^=]+)?=\s*(.+?);?$/);
26601
+ if (!m)
26602
+ continue;
26603
+ const lhs = m[1];
26604
+ const rhs = m[2];
26605
+ if (fn.tainted.has(lhs))
26606
+ continue;
26607
+ for (const v of fn.tainted) {
26608
+ const re = new RegExp(`\\b${v}\\b`);
26609
+ if (re.test(rhs)) {
26610
+ fn.tainted.add(lhs);
26611
+ break;
26612
+ }
26613
+ }
26614
+ }
26615
+ if (fn.tainted.size === before)
26616
+ break;
26617
+ }
26618
+ }
26619
+ const evalExprRe = /\bevalexpr\s*::\s*eval(?:_with_context|_boolean|_int|_float|_string|_tuple|_empty)?\s*\(\s*([^)]+)\s*\)/;
26620
+ const libloadingRe = /\blibloading\s*::\s*Library\s*::\s*new\s*\(\s*([^)]+)\s*\)/;
26621
+ const luaLoadRe = /\.\s*load\s*\(\s*([^)]+)\s*\)\s*\.\s*(?:exec|eval|call)\b/;
26622
+ for (const fn of fns) {
26623
+ if (fn.tainted.size === 0)
26624
+ continue;
26625
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
26626
+ const line = lines[i2];
26627
+ const trimmed = line.trim();
26628
+ let arg = null;
26629
+ let formLabel = "";
26630
+ let m = trimmed.match(evalExprRe);
26631
+ if (m) {
26632
+ arg = m[1].trim();
26633
+ formLabel = "evalexpr::eval";
26634
+ }
26635
+ if (!arg) {
26636
+ m = trimmed.match(libloadingRe);
26637
+ if (m) {
26638
+ arg = m[1].trim();
26639
+ formLabel = "libloading::Library::new";
26640
+ }
26641
+ }
26642
+ if (!arg) {
26643
+ m = trimmed.match(luaLoadRe);
26644
+ if (m) {
26645
+ arg = m[1].trim();
26646
+ formLabel = "mlua/rlua Lua::load().{exec|eval|call}";
26647
+ }
26648
+ }
26649
+ if (arg === null)
26650
+ continue;
26651
+ if (arg.length === 0)
26652
+ continue;
26653
+ let unwrapped = arg.replace(/^&\s*/, "").trim();
26654
+ if (/^"[^"]*"$/.test(unwrapped))
26655
+ continue;
26656
+ let tainted = false;
26657
+ for (const v of fn.tainted) {
26658
+ const re = new RegExp(`\\b${v}\\b`);
26659
+ if (re.test(unwrapped)) {
26660
+ tainted = true;
26661
+ break;
26662
+ }
26663
+ }
26664
+ if (!tainted)
26665
+ continue;
26666
+ findings.push({
26667
+ id: `code_injection-${file}-${i2 + 1}-rust-eval-crate`,
26668
+ pass: "language-sources",
26669
+ category: "security",
26670
+ rule_id: "code_injection",
26671
+ cwe: "CWE-94",
26672
+ severity: "critical",
26673
+ level: "error",
26674
+ message: `Code injection: ${formLabel}(...) called with a value derived ` + "from an HTTP request extractor (body / Query / Path / Form / " + "Json / HttpRequest). The expression / library / Lua chunk is " + "executed as code. Remove the dynamic-eval path or restrict " + "input to a fixed allow-list.",
26675
+ file,
26676
+ line: i2 + 1,
26677
+ snippet: trimmed
26678
+ });
26679
+ }
26680
+ }
26681
+ return findings;
26682
+ }
26205
26683
 
26206
26684
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
26207
26685
  var JS_XSS_SANITIZERS = [
@@ -39329,7 +39807,7 @@ var colors = {
39329
39807
  };
39330
39808
 
39331
39809
  // src/version.ts
39332
- var version = "3.132.0";
39810
+ var version = "3.133.0";
39333
39811
 
39334
39812
  // src/formatters.ts
39335
39813
  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.132.0",
3
+ "version": "3.133.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.132.0"
69
+ "circle-ir": "^3.133.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",