From cae01c6e0f28cb7bc3836e63df0b173b68ff988c Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:52:04 +0200 Subject: [PATCH 1/5] Fix flood of Angular warning messages on policies page (#16618) Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --- .../policies/policies.component.html | 23 ++++------ .../policies/policies.component.ts | 46 +++++++++++++------ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.html b/apps/web/src/app/admin-console/organizations/policies/policies.component.html index ea14986749f..8df73a50e14 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.html @@ -1,7 +1,6 @@ - @let organization = organization$ | async; @if (loading) { - @for (p of policies; track p.name) { - @if (p.display$(organization, configService) | async) { - - - {{ p.name | i18n }} - @if (policiesEnabledMap.get(p.type)) { - {{ "on" | i18n }} - } - {{ p.description | i18n }} - - - } + @for (p of policies$ | async; track p.type) { + + + {{ p.name | i18n }} + @if (policiesEnabledMap.get(p.type)) { + {{ "on" | i18n }} + } + {{ p.description | i18n }} + + } diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 45383133687..e2c51b77d45 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -2,8 +2,17 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, lastValueFrom, Observable } from "rxjs"; -import { first, map } from "rxjs/operators"; +import { + combineLatest, + firstValueFrom, + lastValueFrom, + Observable, + of, + switchMap, + first, + map, + withLatestFrom, +} from "rxjs"; import { getOrganizationById, @@ -11,7 +20,6 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -39,8 +47,7 @@ import { POLICY_EDIT_REGISTER } from "./policy-register-token"; export class PoliciesComponent implements OnInit { loading = true; organizationId: string; - policies: readonly BasePolicyEditDefinition[]; - protected organization$: Observable; + policies$: Observable; private orgPolicies: PolicyResponse[]; protected policiesEnabledMap: Map = new Map(); @@ -63,28 +70,41 @@ export class PoliciesComponent implements OnInit { this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - this.organization$ = this.organizationService + const organization$ = this.organizationService .organizations$(userId) .pipe(getOrganizationById(this.organizationId)); - this.policies = this.policyListService.getPolicies(); + this.policies$ = organization$.pipe( + withLatestFrom(of(this.policyListService.getPolicies())), + switchMap(([organization, policies]) => { + return combineLatest( + policies.map((policy) => + policy + .display$(organization, this.configService) + .pipe(map((shouldDisplay) => ({ policy, shouldDisplay }))), + ), + ); + }), + map((results) => + results.filter((result) => result.shouldDisplay).map((result) => result.policy), + ), + ); await this.load(); // Handle policies component launch from Event message - this.route.queryParams - .pipe(first()) + combineLatest([this.route.queryParams.pipe(first()), this.policies$]) /* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ - .subscribe(async (qParams) => { + .subscribe(async ([qParams, policies]) => { if (qParams.policyId != null) { const policyIdFromEvents: string = qParams.policyId; for (const orgPolicy of this.orgPolicies) { if (orgPolicy.id === policyIdFromEvents) { - for (let i = 0; i < this.policies.length; i++) { - if (this.policies[i].type === orgPolicy.type) { + for (let i = 0; i < policies.length; i++) { + if (policies[i].type === orgPolicy.type) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.edit(this.policies[i]); + this.edit(policies[i]); break; } } From 2ccd841f5807e7086b3b141a6435b3fed232d72e Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Tue, 30 Sep 2025 07:53:10 -0400 Subject: [PATCH 2/5] feat(Utils.fromBufferToB64): [Platform/PM-26186] Add type safety and ArrayBufferView support + tests (#16609) * PM-26186 - Utils.ts - fromBufferToB64 - (1) Add type safety (2) Add ArrayBufferView support (3) Add tests * PM-26186 - Utils.ts - add overloads so that we can specify callers who pass defined buffers will always get a string back so I don't have to modify all call sites to add a null assertion or "as string" --- libs/common/src/platform/misc/utils.spec.ts | 77 ++++++++++++++++++++- libs/common/src/platform/misc/utils.ts | 67 +++++++++++++++++- 2 files changed, 140 insertions(+), 4 deletions(-) diff --git a/libs/common/src/platform/misc/utils.spec.ts b/libs/common/src/platform/misc/utils.spec.ts index 818138863fb..9f01db61fa6 100644 --- a/libs/common/src/platform/misc/utils.spec.ts +++ b/libs/common/src/platform/misc/utils.spec.ts @@ -302,7 +302,7 @@ describe("Utils Service", () => { expect(b64String).toBe(b64HelloWorldString); }); - runInBothEnvironments("should return an empty string for an empty ArrayBuffer", () => { + runInBothEnvironments("should return empty string for an empty ArrayBuffer", () => { const buffer = new Uint8Array([]).buffer; const b64String = Utils.fromBufferToB64(buffer); expect(b64String).toBe(""); @@ -312,6 +312,81 @@ describe("Utils Service", () => { const b64String = Utils.fromBufferToB64(null); expect(b64String).toBeNull(); }); + + runInBothEnvironments("returns null for undefined input", () => { + const b64 = Utils.fromBufferToB64(undefined as unknown as ArrayBuffer); + expect(b64).toBeNull(); + }); + + runInBothEnvironments("returns empty string for empty input", () => { + const b64 = Utils.fromBufferToB64(new ArrayBuffer(0)); + expect(b64).toBe(""); + }); + + runInBothEnvironments("accepts Uint8Array directly", () => { + const u8 = new Uint8Array(asciiHelloWorldArray); + const b64 = Utils.fromBufferToB64(u8); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("respects byteOffset/byteLength (view window)", () => { + // [xx, 'hello world', yy] — view should only encode the middle slice + const prefix = [1, 2, 3]; + const suffix = [4, 5]; + const all = new Uint8Array([...prefix, ...asciiHelloWorldArray, ...suffix]); + const view = new Uint8Array(all.buffer, prefix.length, asciiHelloWorldArray.length); + const b64 = Utils.fromBufferToB64(view); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("handles DataView (ArrayBufferView other than Uint8Array)", () => { + const u8 = new Uint8Array(asciiHelloWorldArray); + const dv = new DataView(u8.buffer, 0, u8.byteLength); + const b64 = Utils.fromBufferToB64(dv); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("handles DataView with offset/length window", () => { + // Buffer: [xx, 'hello world', yy] + const prefix = [9, 9, 9]; + const suffix = [8, 8]; + const all = new Uint8Array([...prefix, ...asciiHelloWorldArray, ...suffix]); + + // DataView over just the "hello world" window + const dv = new DataView(all.buffer, prefix.length, asciiHelloWorldArray.length); + + const b64 = Utils.fromBufferToB64(dv); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments( + "encodes empty view (offset-length window of zero) as empty string", + () => { + const backing = new Uint8Array([1, 2, 3, 4]); + const emptyView = new Uint8Array(backing.buffer, 2, 0); + const b64 = Utils.fromBufferToB64(emptyView); + expect(b64).toBe(""); + }, + ); + + runInBothEnvironments("does not mutate the input", () => { + const original = new Uint8Array(asciiHelloWorldArray); + const copyBefore = new Uint8Array(original); // snapshot + Utils.fromBufferToB64(original); + expect(original).toEqual(copyBefore); // unchanged + }); + + it("produces the same Base64 in Node vs non-Node mode", () => { + const bytes = new Uint8Array(asciiHelloWorldArray); + + Utils.isNode = true; + const nodeB64 = Utils.fromBufferToB64(bytes); + + Utils.isNode = false; + const browserB64 = Utils.fromBufferToB64(bytes); + + expect(browserB64).toBe(nodeB64); + }); }); describe("fromB64ToArray(...)", () => { diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index c103e346a85..43a9e43d92b 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -128,15 +128,52 @@ export class Utils { return arr; } - static fromBufferToB64(buffer: ArrayBuffer): string { + /** + * Convert binary data into a Base64 string. + * + * Overloads are provided for two categories of input: + * + * 1. ArrayBuffer + * - A raw, fixed-length chunk of memory (no element semantics). + * - Example: `const buf = new ArrayBuffer(16);` + * + * 2. ArrayBufferView + * - A *view* onto an existing buffer that gives the bytes meaning. + * - Examples: Uint8Array, Int32Array, DataView, etc. + * - Views can expose only a *window* of the underlying buffer via + * `byteOffset` and `byteLength`. + * Example: + * ```ts + * const buf = new ArrayBuffer(8); + * const full = new Uint8Array(buf); // sees all 8 bytes + * const half = new Uint8Array(buf, 4, 4); // sees only last 4 bytes + * ``` + * + * Returns: + * - Base64 string for non-empty inputs, + * - null if `buffer` is `null` or `undefined` + * - empty string if `buffer` is empty (0 bytes) + */ + static fromBufferToB64(buffer: null | undefined): null; + static fromBufferToB64(buffer: ArrayBuffer): string; + static fromBufferToB64(buffer: ArrayBufferView): string; + static fromBufferToB64(buffer: ArrayBuffer | ArrayBufferView | null | undefined): string | null { + // Handle null / undefined input if (buffer == null) { return null; } + + const bytes: Uint8Array = Utils.normalizeToUint8Array(buffer); + + // Handle empty input + if (bytes.length === 0) { + return ""; + } + if (Utils.isNode) { - return Buffer.from(buffer).toString("base64"); + return Buffer.from(bytes).toString("base64"); } else { let binary = ""; - const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } @@ -144,6 +181,30 @@ export class Utils { } } + /** + * Normalizes input into a Uint8Array so we always have a uniform, + * byte-level view of the data. This avoids dealing with differences + * between ArrayBuffer (raw memory with no indexing) and other typed + * views (which may have element sizes, offsets, and lengths). + * @param buffer ArrayBuffer or ArrayBufferView (e.g. Uint8Array, DataView, etc.) + */ + private static normalizeToUint8Array(buffer: ArrayBuffer | ArrayBufferView): Uint8Array { + /** + * 1) Uint8Array: already bytes → use directly. + * 2) ArrayBuffer: wrap whole buffer. + * 3) Other ArrayBufferView (e.g., DataView, Int32Array): + * wrap the view’s window (byteOffset..byteOffset+byteLength). + */ + if (buffer instanceof Uint8Array) { + return buffer; + } else if (buffer instanceof ArrayBuffer) { + return new Uint8Array(buffer); + } else { + const view = buffer as ArrayBufferView; + return new Uint8Array(view.buffer, view.byteOffset, view.byteLength); + } + } + static fromBufferToUrlB64(buffer: ArrayBuffer): string { return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer)); } From 54a53a1c348808c0dfeb917626887070d8b00c64 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Tue, 30 Sep 2025 06:33:32 -0600 Subject: [PATCH 3/5] Use tracing in ssh_agent (#16455) * [BEEEP][PM-255518] Use tracing for improved observability * feedback dani-garcia: use DefaultVisitor * set default log level * convert printlns in objc crate * convert printlns in autotype crate * convert printlns in autostart crate * convert printlns in core/password crate * convert printlns in core/biometric crate * convert printlns in napi crate * convert log usage in macos provider crate * convert existing log macros to tracing * fix the cargo.toml sort lint errors * Revert "fix the cargo.toml sort lint errors" This reverts commit fd149ab697d37ea8fc9c22db8f96684fc99bf2d8. * fix the sort lint using correct cargo sort version * feedback coltonhurst: more comments/clarity on behavior * revert changes to ssh_agent * Use tracing in ssh_agent --- .../desktop_native/core/src/ssh_agent/mod.rs | 23 ++++++++-------- .../ssh_agent/named_pipe_listener_stream.rs | 27 +++++++++---------- .../desktop_native/core/src/ssh_agent/unix.rs | 17 ++++++------ 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs index d038ba2277f..3440a0114ae 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -11,6 +11,7 @@ use bitwarden_russh::{ session_bind::SessionBindResult, ssh_agent::{self, SshKey}, }; +use tracing::{error, info}; #[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "unix.rs")] @@ -86,7 +87,7 @@ impl ssh_agent::Agent info: &peerinfo::models::PeerInfo, ) -> bool { if !self.is_running() { - println!("[BitwardenDesktopAgent] Agent is not running, but tried to call confirm"); + error!("Agent is not running, but tried to call confirm"); return false; } @@ -94,7 +95,7 @@ impl ssh_agent::Agent let request_data = match request_parser::parse_request(data) { Ok(data) => data, Err(e) => { - println!("[SSH Agent] Error while parsing request: {e}"); + error!(error = %e, "Error while parsing request"); return false; } }; @@ -105,12 +106,12 @@ impl ssh_agent::Agent _ => None, }; - println!( - "[SSH Agent] Confirming request from application: {}, is_forwarding: {}, namespace: {}, host_key: {}", + info!( + is_forwarding = %info.is_forwarding(), + namespace = ?namespace.as_ref(), + host_key = %STANDARD.encode(info.host_key()), + "Confirming request from application: {}", info.process_name(), - info.is_forwarding(), - namespace.clone().unwrap_or_default(), - STANDARD.encode(info.host_key()) ); let mut rx_channel = self.get_ui_response_rx.lock().await.resubscribe(); @@ -172,7 +173,7 @@ impl ssh_agent::Agent connection_info.set_host_key(session_bind_info.host_key.clone()); } SessionBindResult::SignatureFailure => { - println!("[BitwardenDesktopAgent] Session bind failure: Signature failure"); + error!("Session bind failure: Signature failure"); } } } @@ -181,7 +182,7 @@ impl ssh_agent::Agent impl BitwardenDesktopAgent { pub fn stop(&self) { if !self.is_running() { - println!("[BitwardenDesktopAgent] Tried to stop agent while it is not running"); + error!("Tried to stop agent while it is not running"); return; } @@ -227,7 +228,7 @@ impl BitwardenDesktopAgent { ); } Err(e) => { - eprintln!("[SSH Agent Native Module] Error while parsing key: {e}"); + error!(error=%e, "Error while parsing key"); } } } @@ -265,7 +266,7 @@ impl BitwardenDesktopAgent { fn get_request_id(&self) -> u32 { if !self.is_running() { - println!("[BitwardenDesktopAgent] Agent is not running, but tried to get request id"); + error!("Agent is not running, but tried to get request id"); return 0; } diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs index fccd7ca5ed6..cb10e873a33 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs @@ -14,6 +14,7 @@ use tokio::{ select, }; use tokio_util::sync::CancellationToken; +use tracing::{error, info}; use windows::Win32::{Foundation::HANDLE, System::Pipes::GetNamedPipeClientProcessId}; use crate::ssh_agent::peerinfo::{self, models::PeerInfo}; @@ -31,42 +32,38 @@ impl NamedPipeServerStream { pub fn new(cancellation_token: CancellationToken, is_running: Arc) -> Self { let (tx, rx) = tokio::sync::mpsc::channel(16); tokio::spawn(async move { - println!( - "[SSH Agent Native Module] Creating named pipe server on {}", - PIPE_NAME - ); + info!("Creating named pipe server on {}", PIPE_NAME); let mut listener = match ServerOptions::new().create(PIPE_NAME) { Ok(pipe) => pipe, - Err(err) => { - println!("[SSH Agent Native Module] Encountered an error creating the first pipe. The system's openssh service must likely be disabled"); - println!("[SSH Agent Natvie Module] error: {}", err); + Err(e) => { + error!(error = %e, "Encountered an error creating the first pipe. The system's openssh service must likely be disabled"); cancellation_token.cancel(); is_running.store(false, Ordering::Relaxed); return; } }; loop { - println!("[SSH Agent Native Module] Waiting for connection"); + info!("Waiting for connection"); select! { _ = cancellation_token.cancelled() => { - println!("[SSH Agent Native Module] Cancellation token triggered, stopping named pipe server"); + info!("[SSH Agent Native Module] Cancellation token triggered, stopping named pipe server"); break; } _ = listener.connect() => { - println!("[SSH Agent Native Module] Incoming connection"); + info!("[SSH Agent Native Module] Incoming connection"); let handle = HANDLE(listener.as_raw_handle()); let mut pid = 0; unsafe { if let Err(e) = GetNamedPipeClientProcessId(handle, &mut pid) { - println!("Error getting named pipe client process id {}", e); + error!(error = %e, pid, "Faile to get named pipe client process id"); continue } }; let peer_info = peerinfo::gather::get_peer_info(pid); let peer_info = match peer_info { - Err(err) => { - println!("Failed getting process info for pid {} {}", pid, err); + Err(e) => { + error!(error = %e, pid = %pid, "Failed getting process info"); continue }, Ok(info) => info, @@ -76,8 +73,8 @@ impl NamedPipeServerStream { listener = match ServerOptions::new().create(PIPE_NAME) { Ok(pipe) => pipe, - Err(err) => { - println!("[SSH Agent Native Module] Encountered an error creating a new pipe {}", err); + Err(e) => { + error!(error = %e, "Encountered an error creating a new pipe"); cancellation_token.cancel(); is_running.store(false, Ordering::Relaxed); return; diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs index 53142d4c476..813ebd61cc1 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs @@ -12,6 +12,7 @@ use bitwarden_russh::ssh_agent; use homedir::my_home; use tokio::{net::UnixListener, sync::Mutex}; use tokio_util::sync::CancellationToken; +use tracing::{error, info}; use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; @@ -36,14 +37,12 @@ impl BitwardenDesktopAgent { let ssh_path = match std::env::var("BITWARDEN_SSH_AUTH_SOCK") { Ok(path) => path, Err(_) => { - println!("[SSH Agent Native Module] BITWARDEN_SSH_AUTH_SOCK not set, using default path"); + info!("BITWARDEN_SSH_AUTH_SOCK not set, using default path"); let ssh_agent_directory = match my_home() { Ok(Some(home)) => home, _ => { - println!( - "[SSH Agent Native Module] Could not determine home directory" - ); + info!("Could not determine home directory"); return; } }; @@ -65,10 +64,10 @@ impl BitwardenDesktopAgent { } }; - println!("[SSH Agent Native Module] Starting SSH Agent server on {ssh_path:?}"); + info!(socket = %ssh_path, "Starting SSH Agent server"); let sockname = std::path::Path::new(&ssh_path); if let Err(e) = std::fs::remove_file(sockname) { - println!("[SSH Agent Native Module] Could not remove existing socket file: {e}"); + error!(error = %e, socket = %ssh_path, "Could not remove existing socket file"); if e.kind() != std::io::ErrorKind::NotFound { return; } @@ -79,7 +78,7 @@ impl BitwardenDesktopAgent { // Only the current user should be able to access the socket if let Err(e) = fs::set_permissions(sockname, fs::Permissions::from_mode(0o600)) { - println!("[SSH Agent Native Module] Could not set socket permissions: {e}"); + error!(error = %e, socket = ?sockname, "Could not set socket permissions"); return; } @@ -100,10 +99,10 @@ impl BitwardenDesktopAgent { cloned_agent_state .is_running .store(false, std::sync::atomic::Ordering::Relaxed); - println!("[SSH Agent Native Module] SSH Agent server exited"); + info!("SSH Agent server exited"); } Err(e) => { - eprintln!("[SSH Agent Native Module] Error while starting agent server: {e}"); + error!(error = %e, socket = %ssh_path, "Unable to start start agent server"); } } }); From 25020ced5d18f30965e46ddb87df4c77c2d176d2 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:37:00 +0200 Subject: [PATCH 4/5] [PM-23251] Remove low-kdf banner (#16511) * Remove low-kdf banner * update tests --- .../services/vault-banners.service.spec.ts | 71 +------------------ .../services/vault-banners.service.ts | 33 +-------- .../vault-banners.component.html | 12 ---- .../vault-banners.component.spec.ts | 7 -- .../vault-banners/vault-banners.component.ts | 2 - apps/web/src/locales/en/messages.json | 12 ---- 6 files changed, 2 insertions(+), 135 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 89a3757e939..6b46cd89956 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -1,14 +1,8 @@ import { TestBed } from "@angular/core/testing"; import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs"; -import { - AuthRequestServiceAbstraction, - UserDecryptionOptions, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; -import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { DeviceType } from "@bitwarden/common/enums"; @@ -18,7 +12,6 @@ import { StateProvider } from "@bitwarden/common/platform/state"; import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { KdfConfigService, KdfType } from "@bitwarden/key-management"; import { PREMIUM_BANNER_REPROMPT_KEY, @@ -34,14 +27,9 @@ describe("VaultBannersService", () => { const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const getEmailVerified = jest.fn().mockResolvedValue(true); const lastSync$ = new BehaviorSubject(null); - const userDecryptionOptions$ = new BehaviorSubject({ - hasMasterPassword: true, - }); - const kdfConfig$ = new BehaviorSubject({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 }); const accounts$ = new BehaviorSubject>({ [userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo, }); - const devices$ = new BehaviorSubject([]); const pendingAuthRequests$ = new BehaviorSubject>([]); beforeEach(() => { @@ -64,32 +52,14 @@ describe("VaultBannersService", () => { provide: StateProvider, useValue: fakeStateProvider, }, - { - provide: PlatformUtilsService, - useValue: { isSelfHost }, - }, { provide: AccountService, useValue: { accounts$ }, }, - { - provide: KdfConfigService, - useValue: { getKdfConfig$: () => kdfConfig$ }, - }, { provide: SyncService, useValue: { lastSync$: () => lastSync$ }, }, - { - provide: UserDecryptionOptionsServiceAbstraction, - useValue: { - userDecryptionOptionsById$: () => userDecryptionOptions$, - }, - }, - { - provide: DevicesServiceAbstraction, - useValue: { getDevices$: () => devices$ }, - }, { provide: AuthRequestServiceAbstraction, useValue: { getPendingAuthRequests$: () => pendingAuthRequests$ }, @@ -197,45 +167,6 @@ describe("VaultBannersService", () => { }); }); - describe("KDFSettings", () => { - beforeEach(async () => { - userDecryptionOptions$.next({ hasMasterPassword: true }); - kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 599999 }); - }); - - it("shows low KDF iteration banner", async () => { - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); - }); - - it("does not show low KDF iteration banner if KDF type is not PBKDF2_SHA256", async () => { - kdfConfig$.next({ kdfType: KdfType.Argon2id, iterations: 600001 }); - - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); - }); - - it("does not show low KDF for iterations about 600,000", async () => { - kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600001 }); - - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); - }); - - it("dismisses low KDF iteration banner", async () => { - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); - - await service.dismissBanner(userId, VisibleVaultBanner.KDFSettings); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); - }); - }); - describe("OutdatedBrowser", () => { beforeEach(async () => { // Hardcode `MSIE` in userAgent string diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index c4396940998..1c53274d9d7 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -1,10 +1,7 @@ import { Injectable } from "@angular/core"; import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs"; -import { - AuthRequestServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -18,10 +15,8 @@ import { import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; -import { PBKDF2KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"; export const VisibleVaultBanner = { - KDFSettings: "kdf-settings", OutdatedBrowser: "outdated-browser", Premium: "premium", VerifyEmail: "verify-email", @@ -64,9 +59,7 @@ export class VaultBannersService { private stateProvider: StateProvider, private billingAccountProfileStateService: BillingAccountProfileStateService, private platformUtilsService: PlatformUtilsService, - private kdfConfigService: KdfConfigService, private syncService: SyncService, - private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private authRequestService: AuthRequestServiceAbstraction, ) {} @@ -133,21 +126,6 @@ export class VaultBannersService { return needsVerification && !alreadyDismissed; } - /** Returns true when the low KDF iteration banner should be shown */ - async shouldShowLowKDFBanner(userId: UserId): Promise { - const hasLowKDF = ( - await firstValueFrom(this.userDecryptionOptionsService.userDecryptionOptionsById$(userId)) - )?.hasMasterPassword - ? await this.isLowKdfIteration(userId) - : false; - - const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( - VisibleVaultBanner.KDFSettings, - ); - - return hasLowKDF && !alreadyDismissed; - } - /** Dismiss the given banner and perform any respective side effects */ async dismissBanner(userId: UserId, banner: SessionBanners): Promise { if (banner === VisibleVaultBanner.Premium) { @@ -221,13 +199,4 @@ export class VaultBannersService { }; }); } - - private async isLowKdfIteration(userId: UserId) { - const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); - return ( - kdfConfig != null && - kdfConfig.kdfType === KdfType.PBKDF2_SHA256 && - kdfConfig.iterations < PBKDF2KdfConfig.ITERATIONS.defaultValue - ); - } } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html index d52ea9f61e6..44b2975ee19 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html @@ -25,18 +25,6 @@ - - {{ "lowKDFIterationsBanner" | i18n }} - - {{ "changeKDFSettings" | i18n }} - - - { shouldShowPremiumBanner$: jest.fn((userId: UserId) => premiumBanner$), shouldShowUpdateBrowserBanner: jest.fn(), shouldShowVerifyEmailBanner: jest.fn(), - shouldShowLowKDFBanner: jest.fn(), shouldShowPendingAuthRequestBanner: jest.fn((userId: UserId) => Promise.resolve(pendingAuthRequest$.value), ), @@ -48,7 +47,6 @@ describe("VaultBannersComponent", () => { messageSubject = new Subject<{ command: string }>(); bannerService.shouldShowUpdateBrowserBanner.mockResolvedValue(false); bannerService.shouldShowVerifyEmailBanner.mockResolvedValue(false); - bannerService.shouldShowLowKDFBanner.mockResolvedValue(false); pendingAuthRequest$.next(false); premiumBanner$.next(false); @@ -137,11 +135,6 @@ describe("VaultBannersComponent", () => { method: bannerService.shouldShowVerifyEmailBanner, banner: VisibleVaultBanner.VerifyEmail, }, - { - name: "LowKDF", - method: bannerService.shouldShowLowKDFBanner, - banner: VisibleVaultBanner.KDFSettings, - }, ].forEach(({ name, method, banner }) => { describe(name, () => { beforeEach(async () => { diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index a16374f19b3..011e7a3bce6 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -100,14 +100,12 @@ export class VaultBannersComponent implements OnInit { const showBrowserOutdated = await this.vaultBannerService.shouldShowUpdateBrowserBanner(activeUserId); const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(activeUserId); - const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(activeUserId); const showPendingAuthRequest = await this.vaultBannerService.shouldShowPendingAuthRequestBanner(activeUserId); this.visibleBanners = [ showBrowserOutdated ? VisibleVaultBanner.OutdatedBrowser : null, showVerifyEmail ? VisibleVaultBanner.VerifyEmail : null, - showLowKdf ? VisibleVaultBanner.KDFSettings : null, showPendingAuthRequest ? VisibleVaultBanner.PendingAuthRequest : null, ].filter((banner) => banner !== null); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1b374c97478..e2bb463c939 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8408,12 +8408,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -10031,12 +10025,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, From c93586a0aab2b1654a196475ac25d2686d84d035 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:56:29 +0200 Subject: [PATCH 5/5] [deps] Tools: Update jsdom to v27 (#16634) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 242 +++++++++++++++++++++++++++++------------- package.json | 2 +- 3 files changed, 169 insertions(+), 77 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 659a68d13a5..4ed72a9c21b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -73,7 +73,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", diff --git a/package-lock.json b/package-lock.json index 1b126255e63..5c325844a94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -208,7 +208,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -2586,23 +2586,54 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.5.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz", + "integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.1" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" }, "node_modules/@babel/code-frame": { "version": "7.27.1", @@ -5378,9 +5409,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "funding": [ { "type": "github", @@ -5420,9 +5451,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "funding": [ { "type": "github", @@ -5435,7 +5466,7 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.0.2", + "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "engines": { @@ -5468,6 +5499,28 @@ "@csstools/css-tokenizer": "^3.0.4" } }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", @@ -16919,6 +16972,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -19109,6 +19171,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -19150,16 +19225,17 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz", - "integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", + "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/csstype": { @@ -19183,16 +19259,16 @@ } }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", "license": "MIT", "dependencies": { "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-url": "^15.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/data-view-buffer": { @@ -26910,34 +26986,34 @@ } }, "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", "license": "MIT", "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", + "@asamuzakjp/dom-selector": "^6.5.4", + "cssstyle": "^5.3.0", + "data-urls": "^6.0.0", "decimal.js": "^10.5.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", + "parse5": "^7.3.0", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", + "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", + "webidl-conversions": "^8.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", + "whatwg-url": "^15.0.0", + "ws": "^8.18.2", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "canvas": "^3.0.0" @@ -26949,35 +27025,38 @@ } }, "node_modules/jsdom/node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", + "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "tldts-core": "^7.0.16" }, "bin": { "tldts": "bin/cli.js" } }, - "node_modules/jsdom/node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "license": "MIT" - }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "license": "BSD-3-Clause", "dependencies": { - "tldts": "^6.1.32" + "tldts": "^7.0.5" }, "engines": { "node": ">=16" } }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -28937,6 +29016,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -30268,7 +30353,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -31424,6 +31508,7 @@ "version": "2.2.20", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, "license": "MIT" }, "node_modules/nx": { @@ -33413,7 +33498,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -34711,7 +34795,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -36071,7 +36154,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -37597,9 +37679,9 @@ } }, "node_modules/tldts-core": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.9.tgz", - "integrity": "sha512-/FGY1+CryHsxF9SFiPZlMOcwQsfABkAvOJO5VEKE8TNifVEqgMF7+UVXHGhm1z4gPUfvVS/EYcwhiRU3vUa1ag==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", + "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", "license": "MIT" }, "node_modules/tmp": { @@ -37673,15 +37755,15 @@ } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/tree-dump": { @@ -39867,6 +39949,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -40807,16 +40890,25 @@ } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" } }, "node_modules/which": { diff --git a/package.json b/package.json index e94d0e98522..f5894a04da9 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1",