cognium-dev 3.81.0 → 3.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +319 -4
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -11478,9 +11478,15 @@ var DEFAULT_SANITIZERS = [
|
|
|
11478
11478
|
{ method: "sanitize", class: "DOMPurify", removes: ["xss"] },
|
|
11479
11479
|
{ method: "escape", class: "validator", removes: ["xss"] },
|
|
11480
11480
|
{ method: "parse", class: "JSON", removes: ["xss", "code_injection"] },
|
|
11481
|
-
{ method: "parseInt", removes: ["sql_injection", "nosql_injection", "command_injection", "xss"] },
|
|
11482
|
-
{ method: "parseFloat", removes: ["sql_injection", "nosql_injection", "command_injection"] },
|
|
11483
|
-
{ method: "Number", removes: ["sql_injection", "nosql_injection", "command_injection"] },
|
|
11481
|
+
{ method: "parseInt", removes: ["sql_injection", "nosql_injection", "command_injection", "xss", "external_taint_escape", "path_traversal", "code_injection"] },
|
|
11482
|
+
{ method: "parseFloat", removes: ["sql_injection", "nosql_injection", "command_injection", "external_taint_escape", "path_traversal", "code_injection"] },
|
|
11483
|
+
{ method: "Number", removes: ["sql_injection", "nosql_injection", "command_injection", "external_taint_escape", "path_traversal", "code_injection"] },
|
|
11484
|
+
{ method: "min", class: "Math", removes: ["external_taint_escape"] },
|
|
11485
|
+
{ method: "max", class: "Math", removes: ["external_taint_escape"] },
|
|
11486
|
+
{ method: "includes", removes: ["external_taint_escape"] },
|
|
11487
|
+
{ method: "has", removes: ["external_taint_escape"] },
|
|
11488
|
+
{ method: "contains", removes: ["external_taint_escape"] },
|
|
11489
|
+
{ method: "indexOf", removes: ["external_taint_escape"] },
|
|
11484
11490
|
{ method: "basename", class: "path", removes: ["path_traversal"] },
|
|
11485
11491
|
{ method: "normalize", class: "path", removes: ["path_traversal"] },
|
|
11486
11492
|
{ method: "resolve", class: "path", removes: ["path_traversal"] },
|
|
@@ -30522,6 +30528,311 @@ class WeakPasswordEncodingPass {
|
|
|
30522
30528
|
}
|
|
30523
30529
|
}
|
|
30524
30530
|
|
|
30531
|
+
// ../circle-ir/dist/analysis/passes/info-disclosure-stacktrace-pass.js
|
|
30532
|
+
var RESPONSE_RECEIVER_RE = /^(res|response|w|writer|ctx|c)$/i;
|
|
30533
|
+
var LOGGER_RECEIVER_RE = /^(log|logger|slog|console|pino|winston|sentry)$/i;
|
|
30534
|
+
var RESPONSE_SEND_METHODS = new Set([
|
|
30535
|
+
"send",
|
|
30536
|
+
"json",
|
|
30537
|
+
"write",
|
|
30538
|
+
"writeHead",
|
|
30539
|
+
"end",
|
|
30540
|
+
"sendFile",
|
|
30541
|
+
"println",
|
|
30542
|
+
"print",
|
|
30543
|
+
"getWriter",
|
|
30544
|
+
"Fprintln",
|
|
30545
|
+
"Fprintf",
|
|
30546
|
+
"Fprint"
|
|
30547
|
+
]);
|
|
30548
|
+
function isExceptionExpression(expr) {
|
|
30549
|
+
if (!expr)
|
|
30550
|
+
return false;
|
|
30551
|
+
const e = expr.trim();
|
|
30552
|
+
return /\b(err|error|exc|exception|e|t|throwable)\.(stack|message|toString\(|getMessage\(|getStackTrace\(|getLocalizedMessage\(|getCause\()/i.test(e) || /\btraceback\.(format_exc|format_exception|print_exc)\b/i.test(e) || /\bdebug\.Stack\(\)/.test(e) || /\bstr\(\s*(err|error|exc|exception|e)\s*\)/i.test(e) || /\bString\(\s*(err|error|exc|exception|e)\s*\)/i.test(e);
|
|
30553
|
+
}
|
|
30554
|
+
function argIsException(arg) {
|
|
30555
|
+
if (!arg)
|
|
30556
|
+
return false;
|
|
30557
|
+
if (arg.variable && /^(err|error|exc|exception|e|t|throwable)$/i.test(arg.variable)) {
|
|
30558
|
+
return true;
|
|
30559
|
+
}
|
|
30560
|
+
return isExceptionExpression(arg.expression);
|
|
30561
|
+
}
|
|
30562
|
+
function detectJavaPrintStackTrace(call) {
|
|
30563
|
+
if (call.method_name !== "printStackTrace")
|
|
30564
|
+
return null;
|
|
30565
|
+
const rec = call.receiver ?? "";
|
|
30566
|
+
if (!/^(e|ex|exc|exception|err|error|t|throwable)$/i.test(rec))
|
|
30567
|
+
return null;
|
|
30568
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
30569
|
+
if (!arg0)
|
|
30570
|
+
return null;
|
|
30571
|
+
const expr = (arg0.expression ?? arg0.variable ?? "").trim();
|
|
30572
|
+
if (/\bresponse\.getWriter\(\)/.test(expr) || /\bresp\.getWriter\(\)/.test(expr) || /\bout\b/.test(expr) || /\bgetWriter\(\)/.test(expr)) {
|
|
30573
|
+
return "e.printStackTrace(response.getWriter())";
|
|
30574
|
+
}
|
|
30575
|
+
return null;
|
|
30576
|
+
}
|
|
30577
|
+
function detectResponseLeakCall(call) {
|
|
30578
|
+
const method = call.method_name ?? "";
|
|
30579
|
+
const receiver = call.receiver ?? "";
|
|
30580
|
+
if (!RESPONSE_SEND_METHODS.has(method))
|
|
30581
|
+
return null;
|
|
30582
|
+
if (LOGGER_RECEIVER_RE.test(receiver))
|
|
30583
|
+
return null;
|
|
30584
|
+
const recTail = receiver.split(".").pop() ?? receiver;
|
|
30585
|
+
const recHead = receiver.split(".")[0] ?? receiver;
|
|
30586
|
+
if (!RESPONSE_RECEIVER_RE.test(recTail) && !RESPONSE_RECEIVER_RE.test(recHead)) {
|
|
30587
|
+
if (!/(?:^|[.\s])(res|response)\.(?:status|set|header|cookie)\b/i.test(receiver)) {
|
|
30588
|
+
return null;
|
|
30589
|
+
}
|
|
30590
|
+
}
|
|
30591
|
+
for (const a of call.arguments) {
|
|
30592
|
+
if (argIsException(a)) {
|
|
30593
|
+
return `${receiver || ""}${receiver ? "." : ""}${method}(${(a.expression ?? a.variable ?? "").trim()})`;
|
|
30594
|
+
}
|
|
30595
|
+
}
|
|
30596
|
+
return null;
|
|
30597
|
+
}
|
|
30598
|
+
function detectPythonTracebackReturn(ctx) {
|
|
30599
|
+
const out2 = [];
|
|
30600
|
+
const lines = ctx.code.split(`
|
|
30601
|
+
`);
|
|
30602
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
30603
|
+
const ln = lines[i2] ?? "";
|
|
30604
|
+
if (/\breturn\s+traceback\.format_exc\s*\(\s*\)/.test(ln) || /\breturn\s+\{[^}]*traceback\.format_exc\s*\(\s*\)[^}]*\}/.test(ln) || /\bjsonify\s*\([^)]*traceback\.format_exc\s*\(\s*\)/.test(ln)) {
|
|
30605
|
+
out2.push({ line: i2 + 1, api: "return traceback.format_exc()" });
|
|
30606
|
+
continue;
|
|
30607
|
+
}
|
|
30608
|
+
if (/\breturn\s+(?:str|repr)\s*\(\s*(?:e|err|error|exc|exception)\s*\)/.test(ln)) {
|
|
30609
|
+
const start2 = Math.max(0, i2 - 8);
|
|
30610
|
+
const end = Math.min(lines.length, i2 + 2);
|
|
30611
|
+
const window2 = lines.slice(start2, end).join(`
|
|
30612
|
+
`);
|
|
30613
|
+
if (/@(?:app|router|blueprint)\.(?:route|get|post|put|delete|patch)\b/.test(window2)) {
|
|
30614
|
+
out2.push({ line: i2 + 1, api: "return str(e) in handler" });
|
|
30615
|
+
}
|
|
30616
|
+
}
|
|
30617
|
+
}
|
|
30618
|
+
return out2;
|
|
30619
|
+
}
|
|
30620
|
+
|
|
30621
|
+
class InfoDisclosureStacktracePass {
|
|
30622
|
+
name = "info-disclosure-stacktrace";
|
|
30623
|
+
category = "security";
|
|
30624
|
+
run(ctx) {
|
|
30625
|
+
const { graph, language } = ctx;
|
|
30626
|
+
const file = graph.ir.meta.file;
|
|
30627
|
+
const findings = [];
|
|
30628
|
+
if (language === "python") {
|
|
30629
|
+
for (const f of detectPythonTracebackReturn(ctx)) {
|
|
30630
|
+
findings.push({ line: f.line, api: f.api, language });
|
|
30631
|
+
ctx.addFinding(this.makeFinding(file, f.line, f.api));
|
|
30632
|
+
}
|
|
30633
|
+
}
|
|
30634
|
+
for (const call of graph.ir.calls) {
|
|
30635
|
+
let api = null;
|
|
30636
|
+
if (language === "java") {
|
|
30637
|
+
api = detectJavaPrintStackTrace(call);
|
|
30638
|
+
if (!api)
|
|
30639
|
+
api = detectResponseLeakCall(call);
|
|
30640
|
+
} else if (language === "javascript" || language === "typescript") {
|
|
30641
|
+
api = detectResponseLeakCall(call);
|
|
30642
|
+
} else if (language === "go") {
|
|
30643
|
+
const method = call.method_name ?? "";
|
|
30644
|
+
const rec = call.receiver ?? "";
|
|
30645
|
+
if (rec === "http" && method === "Error") {
|
|
30646
|
+
const arg1 = call.arguments.find((a) => a.position === 1);
|
|
30647
|
+
if (argIsException(arg1))
|
|
30648
|
+
api = "http.Error(w, err.Error())";
|
|
30649
|
+
} else if (rec === "fmt" && (method === "Fprintln" || method === "Fprintf" || method === "Fprint")) {
|
|
30650
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
30651
|
+
if (arg0 && /^(w|writer|resp|response)$/i.test((arg0.variable ?? arg0.expression ?? "").trim())) {
|
|
30652
|
+
for (const a of call.arguments) {
|
|
30653
|
+
if (a.position === 0)
|
|
30654
|
+
continue;
|
|
30655
|
+
if (argIsException(a)) {
|
|
30656
|
+
api = `fmt.${method}(w, err)`;
|
|
30657
|
+
break;
|
|
30658
|
+
}
|
|
30659
|
+
}
|
|
30660
|
+
}
|
|
30661
|
+
} else {
|
|
30662
|
+
api = detectResponseLeakCall(call);
|
|
30663
|
+
}
|
|
30664
|
+
} else if (language === "python") {
|
|
30665
|
+
api = detectResponseLeakCall(call);
|
|
30666
|
+
}
|
|
30667
|
+
if (!api)
|
|
30668
|
+
continue;
|
|
30669
|
+
const line = call.location.line;
|
|
30670
|
+
findings.push({ line, api, language });
|
|
30671
|
+
ctx.addFinding(this.makeFinding(file, line, api));
|
|
30672
|
+
}
|
|
30673
|
+
return { findings };
|
|
30674
|
+
}
|
|
30675
|
+
makeFinding(file, line, api) {
|
|
30676
|
+
return {
|
|
30677
|
+
id: `${this.name}-${file}-${line}`,
|
|
30678
|
+
pass: this.name,
|
|
30679
|
+
category: this.category,
|
|
30680
|
+
rule_id: this.name,
|
|
30681
|
+
cwe: "CWE-209",
|
|
30682
|
+
severity: "medium",
|
|
30683
|
+
level: "warning",
|
|
30684
|
+
message: `Exception detail returned to client via \`${api}\`. ` + "Leaking stack traces / exception messages reveals framework internals, " + "file paths, and class names — useful reconnaissance for an attacker.",
|
|
30685
|
+
file,
|
|
30686
|
+
line,
|
|
30687
|
+
fix: "Return a generic error response to the client (e.g. status 500 + a " + "request id) and log the full exception server-side via your logger " + '(e.g. `logger.error("…", e)` or `console.error(err)`).',
|
|
30688
|
+
evidence: { api }
|
|
30689
|
+
};
|
|
30690
|
+
}
|
|
30691
|
+
}
|
|
30692
|
+
|
|
30693
|
+
// ../circle-ir/dist/analysis/passes/unrestricted-file-upload-pass.js
|
|
30694
|
+
var UPLOAD_NAME_RE = /(?:getOriginalFilename|getSubmittedFileName|originalname|originalName|\.filename|\.Filename|FileHeader\.Filename|UploadFile)/;
|
|
30695
|
+
var FILE_SAFE_CALL_RE = /(?:secure_filename|FilenameUtils\.getExtension|\.lastIndexOf\(['"]\.['"]\)|ALLOWED_EXT|ALLOWED_EXTENSIONS|allowedExtensions|\bfileFilter\b|filepath\.Ext|path\.extname)/;
|
|
30696
|
+
function lineWindow(code, startLine, endLine) {
|
|
30697
|
+
const lines = code.split(`
|
|
30698
|
+
`);
|
|
30699
|
+
const s = Math.max(0, startLine - 1);
|
|
30700
|
+
const e = Math.min(lines.length, endLine);
|
|
30701
|
+
return lines.slice(s, e).join(`
|
|
30702
|
+
`);
|
|
30703
|
+
}
|
|
30704
|
+
function callHasUploadName(call) {
|
|
30705
|
+
for (const a of call.arguments) {
|
|
30706
|
+
const expr = (a.expression ?? a.variable ?? "").trim();
|
|
30707
|
+
if (UPLOAD_NAME_RE.test(expr))
|
|
30708
|
+
return true;
|
|
30709
|
+
}
|
|
30710
|
+
if (UPLOAD_NAME_RE.test(call.receiver ?? ""))
|
|
30711
|
+
return true;
|
|
30712
|
+
return false;
|
|
30713
|
+
}
|
|
30714
|
+
|
|
30715
|
+
class UnrestrictedFileUploadPass {
|
|
30716
|
+
name = "unrestricted-file-upload";
|
|
30717
|
+
category = "security";
|
|
30718
|
+
run(ctx) {
|
|
30719
|
+
const { graph, language, code } = ctx;
|
|
30720
|
+
const file = graph.ir.meta.file;
|
|
30721
|
+
const findings = [];
|
|
30722
|
+
const safeFunctionRanges = [];
|
|
30723
|
+
for (const t of graph.ir.types) {
|
|
30724
|
+
for (const m of t.methods) {
|
|
30725
|
+
const body2 = lineWindow(code, m.start_line, m.end_line);
|
|
30726
|
+
if (FILE_SAFE_CALL_RE.test(body2)) {
|
|
30727
|
+
safeFunctionRanges.push({ start: m.start_line, end: m.end_line });
|
|
30728
|
+
}
|
|
30729
|
+
}
|
|
30730
|
+
}
|
|
30731
|
+
const inSafeRange = (line) => {
|
|
30732
|
+
for (const r of safeFunctionRanges) {
|
|
30733
|
+
if (line >= r.start && line <= r.end)
|
|
30734
|
+
return true;
|
|
30735
|
+
}
|
|
30736
|
+
const win = lineWindow(code, Math.max(1, line - 20), line + 5);
|
|
30737
|
+
return FILE_SAFE_CALL_RE.test(win);
|
|
30738
|
+
};
|
|
30739
|
+
if (language === "java") {
|
|
30740
|
+
for (const call of graph.ir.calls) {
|
|
30741
|
+
const m = call.method_name ?? "";
|
|
30742
|
+
if (m === "transferTo" && callHasUploadName(call)) {
|
|
30743
|
+
if (inSafeRange(call.location.line))
|
|
30744
|
+
continue;
|
|
30745
|
+
this.emit(ctx, findings, file, call.location.line, language, "MultipartFile.transferTo(<original filename>)");
|
|
30746
|
+
continue;
|
|
30747
|
+
}
|
|
30748
|
+
if (m === "copy" && (call.receiver === "Files" || (call.receiver ?? "").endsWith(".Files"))) {
|
|
30749
|
+
if (callHasUploadName(call)) {
|
|
30750
|
+
if (inSafeRange(call.location.line))
|
|
30751
|
+
continue;
|
|
30752
|
+
this.emit(ctx, findings, file, call.location.line, language, "Files.copy(input, Path.of(dir, <original filename>))");
|
|
30753
|
+
}
|
|
30754
|
+
}
|
|
30755
|
+
}
|
|
30756
|
+
}
|
|
30757
|
+
if (language === "javascript" || language === "typescript") {
|
|
30758
|
+
for (const call of graph.ir.calls) {
|
|
30759
|
+
const m = call.method_name ?? "";
|
|
30760
|
+
const rec = call.receiver ?? "";
|
|
30761
|
+
if (m === "multer" || rec === "" && m === "multer") {
|
|
30762
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
30763
|
+
const expr = (arg0?.expression ?? "").trim();
|
|
30764
|
+
if (/\bdest\s*:/.test(expr) && !/\bfileFilter\s*:/.test(expr)) {
|
|
30765
|
+
if (inSafeRange(call.location.line))
|
|
30766
|
+
continue;
|
|
30767
|
+
this.emit(ctx, findings, file, call.location.line, language, "multer({ dest }) without fileFilter");
|
|
30768
|
+
continue;
|
|
30769
|
+
}
|
|
30770
|
+
}
|
|
30771
|
+
if (rec === "fs" && (m === "writeFile" || m === "writeFileSync" || m === "appendFile")) {
|
|
30772
|
+
if (callHasUploadName(call) || call.arguments.some((a) => /\breq\.file(?:s)?\b/.test(a.expression ?? a.variable ?? ""))) {
|
|
30773
|
+
if (inSafeRange(call.location.line))
|
|
30774
|
+
continue;
|
|
30775
|
+
this.emit(ctx, findings, file, call.location.line, language, `fs.${m}(<path>, req.file.buffer)`);
|
|
30776
|
+
}
|
|
30777
|
+
}
|
|
30778
|
+
}
|
|
30779
|
+
}
|
|
30780
|
+
if (language === "python") {
|
|
30781
|
+
for (const call of graph.ir.calls) {
|
|
30782
|
+
const m = call.method_name ?? "";
|
|
30783
|
+
if (m === "save") {
|
|
30784
|
+
const rec = call.receiver ?? "";
|
|
30785
|
+
if (!/^(f|file|upload|attachment)$/i.test(rec) && rec !== "")
|
|
30786
|
+
continue;
|
|
30787
|
+
if (!callHasUploadName(call))
|
|
30788
|
+
continue;
|
|
30789
|
+
if (inSafeRange(call.location.line))
|
|
30790
|
+
continue;
|
|
30791
|
+
this.emit(ctx, findings, file, call.location.line, language, "f.save(<dir>, f.filename) without secure_filename");
|
|
30792
|
+
}
|
|
30793
|
+
}
|
|
30794
|
+
}
|
|
30795
|
+
if (language === "go") {
|
|
30796
|
+
for (const call of graph.ir.calls) {
|
|
30797
|
+
const m = call.method_name ?? "";
|
|
30798
|
+
const rec = call.receiver ?? "";
|
|
30799
|
+
if (rec === "os" && (m === "Create" || m === "OpenFile")) {
|
|
30800
|
+
if (callHasUploadName(call)) {
|
|
30801
|
+
if (inSafeRange(call.location.line))
|
|
30802
|
+
continue;
|
|
30803
|
+
this.emit(ctx, findings, file, call.location.line, language, `os.${m}(<uploaded filename>)`);
|
|
30804
|
+
}
|
|
30805
|
+
}
|
|
30806
|
+
if ((rec === "os" || rec === "ioutil") && m === "WriteFile") {
|
|
30807
|
+
if (callHasUploadName(call)) {
|
|
30808
|
+
if (inSafeRange(call.location.line))
|
|
30809
|
+
continue;
|
|
30810
|
+
this.emit(ctx, findings, file, call.location.line, language, `${rec}.WriteFile(<uploaded filename>, …)`);
|
|
30811
|
+
}
|
|
30812
|
+
}
|
|
30813
|
+
}
|
|
30814
|
+
}
|
|
30815
|
+
return { findings };
|
|
30816
|
+
}
|
|
30817
|
+
emit(ctx, findings, file, line, language, api) {
|
|
30818
|
+
findings.push({ line, api, language });
|
|
30819
|
+
ctx.addFinding({
|
|
30820
|
+
id: `${this.name}-${file}-${line}`,
|
|
30821
|
+
pass: this.name,
|
|
30822
|
+
category: this.category,
|
|
30823
|
+
rule_id: this.name,
|
|
30824
|
+
cwe: "CWE-434",
|
|
30825
|
+
severity: "high",
|
|
30826
|
+
level: "error",
|
|
30827
|
+
message: `File upload saved using untrusted name (${api}) — no extension allow-list or ` + "filename canonicalization detected. An attacker can upload a `.jsp`/`.php`/`.html` " + "file and request it back, achieving RCE or stored XSS.",
|
|
30828
|
+
file,
|
|
30829
|
+
line,
|
|
30830
|
+
fix: "Validate the uploaded extension against an allow-list (e.g. " + '`Set.of("png","jpg")`), then save with a sanitized filename. In Python use ' + "`werkzeug.utils.secure_filename`. In multer pass a `fileFilter`. Never " + "concatenate the upload's original filename into a save path without " + "validation.",
|
|
30831
|
+
evidence: { api, language }
|
|
30832
|
+
});
|
|
30833
|
+
}
|
|
30834
|
+
}
|
|
30835
|
+
|
|
30525
30836
|
// ../circle-ir/dist/analysis/passes/plaintext-password-storage-pass.js
|
|
30526
30837
|
function isWriteStorageCall(call, language) {
|
|
30527
30838
|
const method = call.method_name ?? "";
|
|
@@ -33084,6 +33395,10 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
33084
33395
|
pipeline.add(new XmlEntityExpansionPass);
|
|
33085
33396
|
if (!disabledPasses.has("mass-assignment"))
|
|
33086
33397
|
pipeline.add(new MassAssignmentPass);
|
|
33398
|
+
if (!disabledPasses.has("info-disclosure-stacktrace"))
|
|
33399
|
+
pipeline.add(new InfoDisclosureStacktracePass);
|
|
33400
|
+
if (!disabledPasses.has("unrestricted-file-upload"))
|
|
33401
|
+
pipeline.add(new UnrestrictedFileUploadPass);
|
|
33087
33402
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
33088
33403
|
const sinkFilter = results.get("sink-filter");
|
|
33089
33404
|
const interProc = results.get("interprocedural");
|
|
@@ -33277,7 +33592,7 @@ var colors = {
|
|
|
33277
33592
|
};
|
|
33278
33593
|
|
|
33279
33594
|
// src/version.ts
|
|
33280
|
-
var version = "3.
|
|
33595
|
+
var version = "3.82.0";
|
|
33281
33596
|
|
|
33282
33597
|
// src/formatters.ts
|
|
33283
33598
|
var SINK_SEVERITY = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.82.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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"circle-ir": "^3.
|
|
68
|
+
"circle-ir": "^3.82.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|