From 35af69c11e169b8cfed2ce3d2f1181b2154efb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 22 Apr 2026 16:12:37 +0200 Subject: [PATCH 01/12] set baseline security headers --- Cargo.toml | 2 +- crates/defguard_core/src/headers.rs | 50 ++++++++++++++++++++++++++--- crates/defguard_core/src/lib.rs | 17 ++++------ 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e59620bd3..a28892a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,7 +130,7 @@ tonic-prost = "0.14" tonic-prost-build = "0.14" totp-lite = { version = "2.0" } tower = "0.5" -tower-http = { version = "0.6", features = ["fs", "trace", "set-header"] } +tower-http = { version = "0.6", features = ["fs", "trace"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } trait-variant = "0.1" diff --git a/crates/defguard_core/src/headers.rs b/crates/defguard_core/src/headers.rs index a577cd3b2..180e8dec4 100644 --- a/crates/defguard_core/src/headers.rs +++ b/crates/defguard_core/src/headers.rs @@ -1,6 +1,6 @@ use std::{borrow::Borrow, sync::LazyLock}; -use axum::http::{HeaderName, HeaderValue}; +use axum::http::{HeaderName, HeaderValue, Response, header}; use defguard_common::db::{ Id, models::{DeviceLoginEvent, User}, @@ -9,10 +9,50 @@ use defguard_mail::templates::{SessionContext, TemplateError, new_device_login_m use sqlx::PgPool; use uaparser::{Client, Parser, UserAgentParser}; -pub(crate) const CONTENT_SECURITY_POLICY_HEADER_NAME: HeaderName = - HeaderName::from_static("content-security-policy"); -pub(crate) const CONTENT_SECURITY_POLICY_HEADER_VALUE: HeaderValue = - HeaderValue::from_static("frame-ancestors 'none';"); +// Header name constants not yet present in the `http` crate v1.x standard set. +const PERMISSIONS_POLICY: HeaderName = HeaderName::from_static("permissions-policy"); +const CROSS_ORIGIN_OPENER_POLICY: HeaderName = + HeaderName::from_static("cross-origin-opener-policy"); +const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName = + HeaderName::from_static("cross-origin-resource-policy"); + +/// Injects baseline security response headers on every response. +pub(crate) async fn security_headers_middleware(mut response: Response) -> Response { + let headers = response.headers_mut(); + // `X-Content-Type-Options: nosniff` - prevents MIME-type sniffing/confusion attacks + headers.insert( + header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff"), + ); + // `Referrer-Policy: strict-origin-when-cross-origin` - avoids leaking internal URLs via Referer to external sites + headers.insert( + header::REFERRER_POLICY, + HeaderValue::from_static("strict-origin-when-cross-origin"), + ); + // `Permissions-Policy: geolocation=(), camera=(), microphone=()` - disables unused browser APIs + headers.insert( + PERMISSIONS_POLICY, + HeaderValue::from_static("geolocation=(), camera=(), microphone=()"), + ); + // `Cross-Origin-Opener-Policy: same-origin` - severs window.opener references, preventing reverse tabnapping + headers.insert( + CROSS_ORIGIN_OPENER_POLICY, + HeaderValue::from_static("same-origin"), + ); + // `Cross-Origin-Resource-Policy: same-origin` - blocks cross-origin embedding of application resources + headers.insert( + CROSS_ORIGIN_RESOURCE_POLICY, + HeaderValue::from_static("same-origin"), + ); + // `X-Frame-Options: DENY` - clickjacking defense for browsers without CSP frame-ancestors support + headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY")); + // `Content-Security-Policy: frame-ancestors 'none'` - prevents framing/clickjacking + // Use entry/or_insert so individual handlers can override CSP (e.g. per-request nonces) + headers + .entry(header::CONTENT_SECURITY_POLICY) + .or_insert(HeaderValue::from_static("frame-ancestors 'none';")); + response +} pub static USER_AGENT_PARSER: LazyLock = LazyLock::new(|| { let regexes = include_bytes!("../user_agent_header_regexes.yaml"); diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 204dc7a96..fe1ccd266 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -9,6 +9,7 @@ use anyhow::anyhow; use axum::{ Extension, Json, Router, http::{Request, StatusCode}, + middleware, routing::{delete, get, post, put}, }; use axum_extra::extract::cookie::Key; @@ -63,10 +64,7 @@ use tokio::sync::{ broadcast::Sender, mpsc::{UnboundedReceiver, UnboundedSender}, }; -use tower_http::{ - set_header::SetResponseHeaderLayer, - trace::{DefaultOnResponse, TraceLayer}, -}; +use tower_http::trace::{DefaultOnResponse, TraceLayer}; use tracing::Level; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; @@ -612,12 +610,11 @@ pub fn build_webapp( .layer(Extension(worker_state)), ); - let webapp = webapp.layer(DefguardVersionLayer::new(version)).layer( - SetResponseHeaderLayer::if_not_present( - headers::CONTENT_SECURITY_POLICY_HEADER_NAME, - headers::CONTENT_SECURITY_POLICY_HEADER_VALUE, - ), - ); + let webapp = webapp + .layer(DefguardVersionLayer::new(version)) + .layer(middleware::map_response( + headers::security_headers_middleware, + )); let swagger = SwaggerUi::new("/api-docs").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()); From dd4bf98d50d46fa7330d71d3b863c5590a0faf75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 22 Apr 2026 16:59:05 +0200 Subject: [PATCH 02/12] only set HSTS header in HTTPS mode --- crates/defguard_core/src/appstate.rs | 6 +- crates/defguard_core/src/headers.rs | 38 ++++++++++++- crates/defguard_core/src/lib.rs | 55 ++++++++++++------- .../tests/integration/api/common/mod.rs | 3 +- .../tests/integration/api/proxy_certs.rs | 3 +- crates/defguard_setup/src/migration.rs | 3 +- 6 files changed, 80 insertions(+), 28 deletions(-) diff --git a/crates/defguard_core/src/appstate.rs b/crates/defguard_core/src/appstate.rs index 7243cf6a4..499775c14 100644 --- a/crates/defguard_core/src/appstate.rs +++ b/crates/defguard_core/src/appstate.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex, RwLock, atomic::AtomicBool}; use axum::extract::FromRef; use axum_extra::extract::cookie::Key; @@ -36,6 +36,8 @@ pub struct AppState { pub event_tx: UnboundedSender, pub incompatible_components: Arc>, pub proxy_control_tx: tokio::sync::mpsc::Sender, + /// Reflects whether the HTTP server is currently running with TLS + pub tls_active: Arc, } impl AppState { @@ -123,6 +125,7 @@ impl AppState { event_tx: UnboundedSender, incompatible_components: Arc>, proxy_control_tx: tokio::sync::mpsc::Sender, + tls_active: Arc, ) -> Self { spawn(Self::handle_triggers(pool.clone(), rx)); @@ -136,6 +139,7 @@ impl AppState { event_tx, incompatible_components, proxy_control_tx, + tls_active, } } } diff --git a/crates/defguard_core/src/headers.rs b/crates/defguard_core/src/headers.rs index 180e8dec4..af7611b6e 100644 --- a/crates/defguard_core/src/headers.rs +++ b/crates/defguard_core/src/headers.rs @@ -1,6 +1,17 @@ -use std::{borrow::Borrow, sync::LazyLock}; +use std::{ + borrow::Borrow, + sync::{LazyLock, atomic::Ordering}, +}; + +use axum::{ + body::Body, + extract::State, + http::{HeaderName, HeaderValue, Request, header}, + middleware::Next, + response::Response, +}; -use axum::http::{HeaderName, HeaderValue, Response, header}; +use crate::appstate::AppState; use defguard_common::db::{ Id, models::{DeviceLoginEvent, User}, @@ -17,40 +28,61 @@ const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName = HeaderName::from_static("cross-origin-resource-policy"); /// Injects baseline security response headers on every response. -pub(crate) async fn security_headers_middleware(mut response: Response) -> Response { +pub(crate) async fn security_headers_middleware( + State(state): State, + request: Request, + next: Next, +) -> Response { + let mut response = next.run(request).await; let headers = response.headers_mut(); + // `X-Content-Type-Options: nosniff` - prevents MIME-type sniffing/confusion attacks headers.insert( header::X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static("nosniff"), ); + // `Referrer-Policy: strict-origin-when-cross-origin` - avoids leaking internal URLs via Referer to external sites headers.insert( header::REFERRER_POLICY, HeaderValue::from_static("strict-origin-when-cross-origin"), ); + // `Permissions-Policy: geolocation=(), camera=(), microphone=()` - disables unused browser APIs headers.insert( PERMISSIONS_POLICY, HeaderValue::from_static("geolocation=(), camera=(), microphone=()"), ); + // `Cross-Origin-Opener-Policy: same-origin` - severs window.opener references, preventing reverse tabnapping headers.insert( CROSS_ORIGIN_OPENER_POLICY, HeaderValue::from_static("same-origin"), ); + // `Cross-Origin-Resource-Policy: same-origin` - blocks cross-origin embedding of application resources headers.insert( CROSS_ORIGIN_RESOURCE_POLICY, HeaderValue::from_static("same-origin"), ); + // `X-Frame-Options: DENY` - clickjacking defense for browsers without CSP frame-ancestors support headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY")); + // `Content-Security-Policy: frame-ancestors 'none'` - prevents framing/clickjacking // Use entry/or_insert so individual handlers can override CSP (e.g. per-request nonces) headers .entry(header::CONTENT_SECURITY_POLICY) .or_insert(HeaderValue::from_static("frame-ancestors 'none';")); + + // `Strict-Transport-Security` - only sent over TLS; ignored and potentially harmful over plain HTTP (RFC 6797 §7.2) + let tls = state.tls_active.load(Ordering::Relaxed); + if tls { + headers.insert( + header::STRICT_TRANSPORT_SECURITY, + HeaderValue::from_static("max-age=31536000; includeSubDomains"), + ); + } response } diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index fe1ccd266..9132daf3e 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -1,7 +1,10 @@ #![allow(clippy::too_many_arguments)] use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::{Arc, LazyLock, Mutex, RwLock}, + sync::{ + Arc, LazyLock, Mutex, RwLock, + atomic::{AtomicBool, Ordering}, + }, time::Duration, }; @@ -233,6 +236,7 @@ pub fn build_webapp( version: Version, incompatible_components: Arc>, proxy_control_tx: tokio::sync::mpsc::Sender, + tls_active: Arc, ) -> Router { let webapp: Router = Router::new() .route("/", get(index)) @@ -610,28 +614,33 @@ pub fn build_webapp( .layer(Extension(worker_state)), ); - let webapp = webapp - .layer(DefguardVersionLayer::new(version)) - .layer(middleware::map_response( - headers::security_headers_middleware, - )); + let app_state = AppState::new( + pool.clone(), + webhook_tx, + webhook_rx, + wireguard_tx, + web_reload_tx, + key, + failed_logins, + event_tx, + incompatible_components, + proxy_control_tx.clone(), + tls_active, + ); + + let webapp = + webapp + .layer(DefguardVersionLayer::new(version)) + .layer(middleware::from_fn_with_state( + app_state.clone(), + headers::security_headers_middleware, + )); let swagger = SwaggerUi::new("/api-docs").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()); webapp - .with_state(AppState::new( - pool.clone(), - webhook_tx, - webhook_rx, - wireguard_tx, - web_reload_tx, - key, - failed_logins, - event_tx, - incompatible_components, - proxy_control_tx.clone(), - )) + .with_state(app_state) .layer(Extension(pool)) .layer(Extension(proxy_control_tx)) .layer( @@ -665,6 +674,8 @@ pub async fn run_web_server( let settings = Settings::get_current_settings(); let key = Key::from(settings.secret_key_required()?.as_bytes()); + let tls_active = Arc::new(AtomicBool::new(false)); + let webapp = build_webapp( webhook_tx, webhook_rx, @@ -678,6 +689,7 @@ pub async fn run_web_server( Version::parse(VERSION)?, incompatible_components, proxy_control_tx, + Arc::clone(&tls_active), ); info!("Started web services"); let server_config = server_config(); @@ -693,13 +705,14 @@ pub async fn run_web_server( loop { let handle = axum_server::Handle::new(); let handle_clone = handle.clone(); - let app = webapp - .clone() - .into_make_service_with_connect_info::(); let current_tls_cert_pair = Certificates::get_or_default(&pool).await.map_or(None, |c| { c.core_http_cert_pair() .map(|(cert, key)| (cert.to_owned(), key.to_owned())) }); + tls_active.store(current_tls_cert_pair.is_some(), Ordering::Relaxed); + let app = webapp + .clone() + .into_make_service_with_connect_info::(); let mut server_task = tokio::spawn(async move { if let Some((cert_pem, key_pem)) = current_tls_cert_pair { diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index f514e313b..169cf7b0c 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod client; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, str::FromStr, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, atomic::AtomicBool}, }; use axum_extra::extract::cookie::Key; @@ -152,6 +152,7 @@ pub(crate) async fn make_base_client( Version::parse(VERSION).unwrap(), Arc::default(), proxy_control_tx, + Arc::new(AtomicBool::new(false)), ); ( diff --git a/crates/defguard_core/tests/integration/api/proxy_certs.rs b/crates/defguard_core/tests/integration/api/proxy_certs.rs index 44a2dd39d..911047651 100644 --- a/crates/defguard_core/tests/integration/api/proxy_certs.rs +++ b/crates/defguard_core/tests/integration/api/proxy_certs.rs @@ -8,7 +8,7 @@ /// was sent after a successful cert operation without needing a real proxy process. use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, atomic::AtomicBool}, time::Duration, }; @@ -152,6 +152,7 @@ async fn make_test_client_with_proxy_rx( Version::parse(VERSION).unwrap(), Arc::default(), proxy_control_tx, + Arc::new(AtomicBool::new(false)), ); let client = TestClient::new(webapp, listener, api_event_rx); diff --git a/crates/defguard_setup/src/migration.rs b/crates/defguard_setup/src/migration.rs index 738321c92..ee1980bf4 100644 --- a/crates/defguard_setup/src/migration.rs +++ b/crates/defguard_setup/src/migration.rs @@ -1,6 +1,6 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex, RwLock, atomic::AtomicBool}, }; use anyhow::anyhow; @@ -93,6 +93,7 @@ pub fn build_migration_webapp( event_tx, incompatible_components, proxy_control_tx.clone(), + Arc::new(AtomicBool::new(false)), ); let router = Router::new() From c0b48e1f31f5de3d30b0adf586fc1e6fcd559132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 22 Apr 2026 18:48:19 +0200 Subject: [PATCH 03/12] set request body size limits --- crates/defguard_core/src/lib.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 9132daf3e..5a896174f 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -11,6 +11,7 @@ use std::{ use anyhow::anyhow; use axum::{ Extension, Json, Router, + extract::DefaultBodyLimit, http::{Request, StatusCode}, middleware, routing::{delete, get, post, put}, @@ -203,6 +204,13 @@ extern crate tracing; #[macro_use] extern crate serde; +/// Default request body size limit applied globally to every route. +const REQUEST_BODY_LIMIT: usize = 256 * 1024; // 256 KB + +/// Raised body size limit for the WireGuard config import endpoint, which may +/// carry configs with hundreds of peers. +const NETWORK_IMPORT_BODY_LIMIT: usize = 4 * 1024 * 1024; // 4 MB + static PHONE_NUMBER_REGEX: LazyLock = LazyLock::new(|| { Regex::new(r"^(\+?\d{1,3}\s?)?(\(\d{1,3}\)|\d{1,3})[-\s]?\d{1,4}[-\s]?\d{1,4}?$") .expect("Failed to parse phone number regex") @@ -559,7 +567,10 @@ pub fn build_webapp( .route("/network", post(create_network).get(list_networks)) .route("/network/count", get(count_networks)) .route("/network/display", get(get_locations_display)) - .route("/network/import", post(import_network)) + .route( + "/network/import", + post(import_network).layer(DefaultBodyLimit::max(NETWORK_IMPORT_BODY_LIMIT)), + ) .route("/network/stats", get(locations_overview_stats)) .route("/network/gateways", get(all_gateways_status)) .route( @@ -654,6 +665,9 @@ pub fn build_webapp( }) .on_response(DefaultOnResponse::new().level(Level::INFO)), ) + // Global request body size limit. Per-route layers (e.g. /network/import) can + // override this by applying a larger DefaultBodyLimit closer to the handler. + .layer(DefaultBodyLimit::max(REQUEST_BODY_LIMIT)) .merge(swagger) } From e1175a9d01091ec26dc3598fe1105958351fc3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 22 Apr 2026 20:26:04 +0200 Subject: [PATCH 04/12] add request timeout --- Cargo.toml | 2 +- crates/defguard_core/src/lib.rs | 56 +++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a28892a5d..2c2d5ee1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,7 +130,7 @@ tonic-prost = "0.14" tonic-prost-build = "0.14" totp-lite = { version = "2.0" } tower = "0.5" -tower-http = { version = "0.6", features = ["fs", "trace"] } +tower-http = { version = "0.6", features = ["fs", "trace", "timeout"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } trait-variant = "0.1" diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 5a896174f..3bc403e71 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -68,7 +68,10 @@ use tokio::sync::{ broadcast::Sender, mpsc::{UnboundedReceiver, UnboundedSender}, }; -use tower_http::trace::{DefaultOnResponse, TraceLayer}; +use tower_http::{ + timeout::TimeoutLayer, + trace::{DefaultOnResponse, TraceLayer}, +}; use tracing::Level; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; @@ -211,6 +214,10 @@ const REQUEST_BODY_LIMIT: usize = 256 * 1024; // 256 KB /// carry configs with hundreds of peers. const NETWORK_IMPORT_BODY_LIMIT: usize = 4 * 1024 * 1024; // 4 MB +/// Maximum time a single request may take before the server returns 408. +/// Applies to all routes except long-lived SSE streams. +const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); + static PHONE_NUMBER_REGEX: LazyLock = LazyLock::new(|| { Regex::new(r"^(\+?\d{1,3}\s?)?(\(\d{1,3}\)|\d{1,3})[-\s]?\d{1,4}[-\s]?\d{1,4}?$") .expect("Failed to parse phone number regex") @@ -410,10 +417,7 @@ pub fn build_webapp( get(gateway_details) .put(update_gateway) .delete(delete_gateway), - ) - // Proxy setup with SSE - .route("/proxy/setup/stream", get(setup_proxy_tls_stream)) - .route("/proxy/acme/stream", get(stream_proxy_acme)), + ), ); // Enterprise features @@ -579,11 +583,6 @@ pub fn build_webapp( .delete(delete_network) .get(network_details), ) - // Gateway adding (uses SSE) - .route( - "/network/{network_id}/gateways/setup", - get(setup_gateway_tls_stream), - ) .route("/network/{network_id}/gateways", get(gateway_status)) .route("/network/{network_id}/devices", post(add_user_devices)) .route( @@ -625,6 +624,21 @@ pub fn build_webapp( .layer(Extension(worker_state)), ); + // SSE routes are long-lived connections; they must not be wrapped by the + // request timeout. They are merged in after TimeoutLayer is applied to the + // main router so that they bypass the timeout while still receiving all + // other middleware (security headers, tracing, body limit, etc.). + let sse_routes: Router = Router::new().nest( + "/api/v1", + Router::new() + .route("/proxy/setup/stream", get(setup_proxy_tls_stream)) + .route("/proxy/acme/stream", get(stream_proxy_acme)) + .route( + "/network/{network_id}/gateways/setup", + get(setup_gateway_tls_stream), + ), + ); + let app_state = AppState::new( pool.clone(), webhook_tx, @@ -639,13 +653,21 @@ pub fn build_webapp( tls_active, ); - let webapp = - webapp - .layer(DefguardVersionLayer::new(version)) - .layer(middleware::from_fn_with_state( - app_state.clone(), - headers::security_headers_middleware, - )); + let webapp = webapp + // Apply timeout to the main router BEFORE merging SSE routes so + // that long-lived streams bypass it. + .layer(TimeoutLayer::with_status_code( + StatusCode::REQUEST_TIMEOUT, + REQUEST_TIMEOUT, + )) + .merge(sse_routes) + // Version and security headers are applied after the merge so that + // they cover all routes, including SSE. + .layer(DefguardVersionLayer::new(version)) + .layer(middleware::from_fn_with_state( + app_state.clone(), + headers::security_headers_middleware, + )); let swagger = SwaggerUi::new("/api-docs").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()); From f8f66c0b4f44ad6ef373432b4619d8508ee0dc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 22 Apr 2026 22:47:58 +0200 Subject: [PATCH 05/12] duplicate proxy rate limit approach --- Cargo.lock | 128 +++++++++++++++++++++++++++ Cargo.toml | 1 + crates/defguard_common/src/config.rs | 12 +++ crates/defguard_core/Cargo.toml | 1 + crates/defguard_core/src/lib.rs | 51 ++++++++++- 5 files changed, 189 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 430660d5c..4646530d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1293,6 +1293,20 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.10.0" @@ -1455,6 +1469,7 @@ dependencies = [ "totp-lite", "tower", "tower-http", + "tower_governor", "tracing", "tracing-subscriber", "trait-variant", @@ -2429,6 +2444,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.32" @@ -2563,6 +2584,29 @@ dependencies = [ "walkdir", ] +[[package]] +name = "governor" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8" +dependencies = [ + "cfg-if", + "dashmap", + "futures-sink", + "futures-timer", + "futures-util", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand 0.9.4", + "smallvec", + "spinning_top", + "web-time", +] + [[package]] name = "group" version = "0.13.0" @@ -3749,6 +3793,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "noop_proc_macro" version = "0.3.0" @@ -4663,6 +4713,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.5" @@ -4870,6 +4926,21 @@ dependencies = [ "image", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -5073,6 +5144,15 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "rayon" version = "1.12.0" @@ -5963,6 +6043,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -6834,6 +6923,23 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tower_governor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44de9b94d849d3c46e06a883d72d408c2de6403367b39df2b1c9d9e7b6736fe6" +dependencies = [ + "axum", + "forwarded-header-value", + "governor", + "http", + "pin-project", + "thiserror 2.0.18", + "tonic", + "tower", + "tracing", +] + [[package]] name = "tracing" version = "0.1.44" @@ -7502,6 +7608,22 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -7511,6 +7633,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index 2c2d5ee1a..509850e55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ tonic-prost = "0.14" tonic-prost-build = "0.14" totp-lite = { version = "2.0" } tower = "0.5" +tower_governor = "0.8" tower-http = { version = "0.6", features = ["fs", "trace", "timeout"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index ef1a96234..02563837a 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -184,6 +184,16 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_ADOPT_EDGE")] pub adopt_edge: Option, + + /// Maximum number of requests per second per client IP before rate limiting kicks in. + /// Set to 0 to disable rate limiting. + #[arg(long, env = "DEFGUARD_RATELIMIT_PERSECOND", default_value_t = 10)] + pub rate_limit_per_second: u64, + + /// Maximum burst size for the rate limiter (token bucket capacity per client IP). + /// Set to 0 to disable rate limiting. + #[arg(long, env = "DEFGUARD_RATELIMIT_BURST", default_value_t = 100)] + pub rate_limit_burst: u32, } #[derive(Clone, Debug, Subcommand)] @@ -283,6 +293,8 @@ impl DefGuardConfig { grpc_bind_address: None, adopt_gateway: None, adopt_edge: None, + rate_limit_per_second: 10, + rate_limit_burst: 100, }; config diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index fb24a8776..71005e69b 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -71,6 +71,7 @@ tonic = { workspace = true } tonic-health = { workspace = true } totp-lite = { workspace = true } tower-http = { workspace = true } +tower_governor = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } trait-variant = { workspace = true } diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 3bc403e71..c653c62c4 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -64,9 +64,16 @@ use regex::Regex; use secrecy::ExposeSecret; use semver::Version; use sqlx::PgPool; -use tokio::sync::{ - broadcast::Sender, - mpsc::{UnboundedReceiver, UnboundedSender}, +use tokio::{ + spawn, + sync::{ + broadcast::Sender, + mpsc::{UnboundedReceiver, UnboundedSender}, + }, + time::sleep, +}; +use tower_governor::{ + GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, }; use tower_http::{ timeout::TimeoutLayer, @@ -218,6 +225,9 @@ const NETWORK_IMPORT_BODY_LIMIT: usize = 4 * 1024 * 1024; // 4 MB /// Applies to all routes except long-lived SSE streams. const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); +/// How often the rate limiter evicts stale per-IP entries from its in-memory store. +const RATE_LIMITER_CLEANUP_PERIOD: Duration = Duration::from_secs(60); + static PHONE_NUMBER_REGEX: LazyLock = LazyLock::new(|| { Regex::new(r"^(\+?\d{1,3}\s?)?(\(\d{1,3}\)|\d{1,3})[-\s]?\d{1,4}[-\s]?\d{1,4}?$") .expect("Failed to parse phone number regex") @@ -712,7 +722,7 @@ pub async fn run_web_server( let tls_active = Arc::new(AtomicBool::new(false)); - let webapp = build_webapp( + let mut webapp = build_webapp( webhook_tx, webhook_rx, wireguard_tx, @@ -729,6 +739,39 @@ pub async fn run_web_server( ); info!("Started web services"); let server_config = server_config(); + + // Setup rate limiter. Both fields default to non-zero so limiting is on by default; + // operators can set either env var to 0 to disable. + debug!( + "Configuring rate limiter, per_second: {}, burst: {}", + server_config.rate_limit_per_second, server_config.rate_limit_burst + ); + let governor_conf = GovernorConfigBuilder::default() + .key_extractor(SmartIpKeyExtractor) + .per_second(server_config.rate_limit_per_second) + .burst_size(server_config.rate_limit_burst) + .finish(); + if let Some(conf) = governor_conf { + let governor_limiter = conf.limiter().clone(); + spawn(async move { + loop { + sleep(RATE_LIMITER_CLEANUP_PERIOD).await; + debug!( + "Cleaning-up rate limiter storage, current size: {}", + governor_limiter.len() + ); + governor_limiter.retain_recent(); + } + }); + info!( + "Rate limiter configured: {} req/s per IP, burst {}", + server_config.rate_limit_per_second, server_config.rate_limit_burst + ); + webapp = webapp.layer(GovernorLayer::new(conf)); + } else { + info!("Rate limiting disabled (per_second or burst is 0)"); + } + let addr = SocketAddr::new( server_config .http_bind_address From 8f4831d79412dedd49525692d8a658e21799eaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 07:35:41 +0200 Subject: [PATCH 06/12] set cache control cookie for API routes --- crates/defguard_core/src/headers.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/defguard_core/src/headers.rs b/crates/defguard_core/src/headers.rs index af7611b6e..9609f52bb 100644 --- a/crates/defguard_core/src/headers.rs +++ b/crates/defguard_core/src/headers.rs @@ -33,6 +33,7 @@ pub(crate) async fn security_headers_middleware( request: Request, next: Next, ) -> Response { + let is_api = request.uri().path().starts_with("/api/"); let mut response = next.run(request).await; let headers = response.headers_mut(); @@ -83,6 +84,12 @@ pub(crate) async fn security_headers_middleware( HeaderValue::from_static("max-age=31536000; includeSubDomains"), ); } + + // `Cache-Control: no-store` - prevents browsers and caches from storing sensitive API responses + if is_api { + headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); + } + response } From c90ccabd73f7e615280994074bd83f76b825ccc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 08:13:26 +0200 Subject: [PATCH 07/12] review fixes --- crates/defguard_core/src/headers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/defguard_core/src/headers.rs b/crates/defguard_core/src/headers.rs index 9609f52bb..336dd4e73 100644 --- a/crates/defguard_core/src/headers.rs +++ b/crates/defguard_core/src/headers.rs @@ -81,7 +81,7 @@ pub(crate) async fn security_headers_middleware( if tls { headers.insert( header::STRICT_TRANSPORT_SECURITY, - HeaderValue::from_static("max-age=31536000; includeSubDomains"), + HeaderValue::from_static("max-age=31536000"), ); } From 3c5f78fad658961d3d975817c2433a845a142d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 11:09:22 +0200 Subject: [PATCH 08/12] reorder layers --- crates/defguard_core/src/headers.rs | 12 ++++----- crates/defguard_core/src/lib.rs | 25 +++++++++++-------- .../tests/integration/api/common/mod.rs | 3 --- .../tests/integration/api/proxy_certs.rs | 3 --- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/crates/defguard_core/src/headers.rs b/crates/defguard_core/src/headers.rs index 336dd4e73..24b975771 100644 --- a/crates/defguard_core/src/headers.rs +++ b/crates/defguard_core/src/headers.rs @@ -1,17 +1,17 @@ use std::{ borrow::Borrow, - sync::{LazyLock, atomic::Ordering}, + sync::{ + Arc, LazyLock, + atomic::{AtomicBool, Ordering}, + }, }; use axum::{ body::Body, - extract::State, http::{HeaderName, HeaderValue, Request, header}, middleware::Next, response::Response, }; - -use crate::appstate::AppState; use defguard_common::db::{ Id, models::{DeviceLoginEvent, User}, @@ -29,7 +29,7 @@ const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName = /// Injects baseline security response headers on every response. pub(crate) async fn security_headers_middleware( - State(state): State, + tls_active: Arc, request: Request, next: Next, ) -> Response { @@ -77,7 +77,7 @@ pub(crate) async fn security_headers_middleware( .or_insert(HeaderValue::from_static("frame-ancestors 'none';")); // `Strict-Transport-Security` - only sent over TLS; ignored and potentially harmful over plain HTTP (RFC 6797 §7.2) - let tls = state.tls_active.load(Ordering::Relaxed); + let tls = tls_active.load(Ordering::Relaxed); if tls { headers.insert( header::STRICT_TRANSPORT_SECURITY, diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index c653c62c4..77f44237e 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -258,7 +258,6 @@ pub fn build_webapp( key: Key, failed_logins: Arc>, event_tx: UnboundedSender, - version: Version, incompatible_components: Arc>, proxy_control_tx: tokio::sync::mpsc::Sender, tls_active: Arc, @@ -670,14 +669,7 @@ pub fn build_webapp( StatusCode::REQUEST_TIMEOUT, REQUEST_TIMEOUT, )) - .merge(sse_routes) - // Version and security headers are applied after the merge so that - // they cover all routes, including SSE. - .layer(DefguardVersionLayer::new(version)) - .layer(middleware::from_fn_with_state( - app_state.clone(), - headers::security_headers_middleware, - )); + .merge(sse_routes); let swagger = SwaggerUi::new("/api-docs").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()); @@ -686,6 +678,9 @@ pub fn build_webapp( .with_state(app_state) .layer(Extension(pool)) .layer(Extension(proxy_control_tx)) + // swagger is merged before TraceLayer and DefaultBodyLimit so that those + // middleware layers cover swagger routes too. + .merge(swagger) .layer( TraceLayer::new_for_http() .make_span_with(|request: &Request<_>| { @@ -700,7 +695,6 @@ pub fn build_webapp( // Global request body size limit. Per-route layers (e.g. /network/import) can // override this by applying a larger DefaultBodyLimit closer to the handler. .layer(DefaultBodyLimit::max(REQUEST_BODY_LIMIT)) - .merge(swagger) } /// Runs core web server exposing REST API. @@ -732,7 +726,6 @@ pub async fn run_web_server( key, failed_logins, event_tx, - Version::parse(VERSION)?, incompatible_components, proxy_control_tx, Arc::clone(&tls_active), @@ -772,6 +765,16 @@ pub async fn run_web_server( info!("Rate limiting disabled (per_second or burst is 0)"); } + // Version and security headers are the outermost layers so that ALL short-circuit + // responses (408 timeout, 413 body-too-large, 429 rate-limited) and swagger routes + // also carry the baseline security headers and the server version header. + let tls_for_headers = Arc::clone(&tls_active); + webapp = webapp + .layer(DefguardVersionLayer::new(Version::parse(VERSION)?)) + .layer(middleware::from_fn(move |req, next| { + headers::security_headers_middleware(Arc::clone(&tls_for_headers), req, next) + })); + let addr = SocketAddr::new( server_config .http_bind_address diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 169cf7b0c..9b763cdc5 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -13,7 +13,6 @@ use defguard_certs::{ }; pub use defguard_common::db::setup_pool; use defguard_common::{ - VERSION, config::DefGuardConfig, db::{ Id, @@ -31,7 +30,6 @@ use defguard_core::{ handlers::{Auth, user::UserDetails}, }; use reqwest::{StatusCode, header::HeaderName}; -use semver::Version; use serde_json::json; use sqlx::PgPool; use tokio::{ @@ -149,7 +147,6 @@ pub(crate) async fn make_base_client( key, failed_logins, api_event_tx, - Version::parse(VERSION).unwrap(), Arc::default(), proxy_control_tx, Arc::new(AtomicBool::new(false)), diff --git a/crates/defguard_core/tests/integration/api/proxy_certs.rs b/crates/defguard_core/tests/integration/api/proxy_certs.rs index 911047651..60eb7fa8a 100644 --- a/crates/defguard_core/tests/integration/api/proxy_certs.rs +++ b/crates/defguard_core/tests/integration/api/proxy_certs.rs @@ -15,7 +15,6 @@ use std::{ use axum_extra::extract::cookie::Key; use defguard_certs::CertificateAuthority; use defguard_common::{ - VERSION, db::{ models::{ Certificates, ProxyCertSource, Settings, @@ -35,7 +34,6 @@ use defguard_core::{ handlers::Auth, }; use reqwest::StatusCode; -use semver::Version; use serde_json::json; use sqlx::{ PgPool, @@ -149,7 +147,6 @@ async fn make_test_client_with_proxy_rx( key, failed_logins, api_event_tx, - Version::parse(VERSION).unwrap(), Arc::default(), proxy_control_tx, Arc::new(AtomicBool::new(false)), From 3f3170675a2a25b0a1581019bb15ba6318b446e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 11:26:43 +0200 Subject: [PATCH 09/12] update deps to fix trivy --- Cargo.lock | 4 +- flake.lock | 6 +- web/package.json | 18 +- web/pnpm-lock.yaml | 451 ++++++++++++++++++++++----------------------- 4 files changed, 237 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afb20299f..10ab78686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5455,9 +5455,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" dependencies = [ "aws-lc-rs", "log", diff --git a/flake.lock b/flake.lock index 2ecf91fc0..081d78eb9 100644 --- a/flake.lock +++ b/flake.lock @@ -74,11 +74,11 @@ ] }, "locked": { - "lastModified": 1776827647, - "narHash": "sha256-sYixYhp5V8jCajO8TRorE4fzs7IkL4MZdfLTKgkPQBk=", + "lastModified": 1776914043, + "narHash": "sha256-qug5r56yW1qOsjSI99l3Jm15JNT9CvS2otkXNRNtrPI=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "40e6ccc06e1245a4837cbbd6bdda64e21cc67379", + "rev": "2d35c4358d7de3a0e606a6e8b27925d981c01cc3", "type": "github" }, "original": { diff --git a/web/package.json b/web/package.json index bb5f6e8d9..bf293a1a2 100644 --- a/web/package.json +++ b/web/package.json @@ -23,13 +23,13 @@ "@shortercode/webzip": "1.1.1-0", "@stablelib/base64": "^2.0.1", "@stablelib/x25519": "^2.0.1", - "@tanstack/react-form": "^1.29.0", + "@tanstack/react-form": "^1.29.1", "@tanstack/react-query": "^5.99.2", "@tanstack/react-router": "^1.168.23", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.24", "@uidotdev/usehooks": "^2.4.1", - "axios": "^1.15.1", + "axios": "^1.15.2", "byte-size": "^9.0.1", "clsx": "^2.1.1", "dayjs": "^1.11.20", @@ -53,6 +53,12 @@ "zod": "^4.3.6", "zustand": "^5.0.12" }, + "pnpm": { + "overrides": { + "//uuid": "Force uuid >= 14.0.0 to fix GHSA-w5hq-g745-h8pq (missing bounds check in v3/v5/v6). Both @inlang/sdk and @lix-js/sdk pull in older versions. Remove once upstream packages declare uuid@14 themselves.", + "uuid": "^14.0.0" + } + }, "devDependencies": { "@biomejs/biome": "2.4.12", "@inlang/paraglide-js": "2.16.0", @@ -69,18 +75,18 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", - "@vitest/ui": "^4.1.4", + "@vitest/ui": "^4.1.5", "autoprefixer": "^10.5.0", "globals": "^17.5.0", "prettier": "^3.8.3", "sass": "^1.99.0", "sharp": "^0.34.5", - "stylelint": "^17.8.0", + "stylelint": "^17.9.0", "stylelint-config-standard-scss": "^17.0.0", "stylelint-scss": "^7.0.0", "typescript": "~5.9.3", - "vite": "^8.0.9", + "vite": "^8.0.10", "vite-plugin-image-optimizer": "^2.0.3", - "vitest": "^4.1.4" + "vitest": "^4.1.5" } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index a9af77222..ff98e9a72 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + uuid: ^14.0.0 + importers: .: @@ -30,8 +33,8 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tanstack/react-form': - specifier: ^1.29.0 - version: 1.29.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + specifier: ^1.29.1 + version: 1.29.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-query': specifier: ^5.99.2 version: 5.99.2(react@19.2.5) @@ -48,8 +51,8 @@ importers: specifier: ^2.4.1 version: 2.4.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) axios: - specifier: ^1.15.1 - version: 1.15.1 + specifier: ^1.15.2 + version: 1.15.2 byte-size: specifier: ^9.0.1 version: 9.0.1 @@ -122,7 +125,7 @@ importers: version: 2.4.12 '@tanstack/devtools-vite': specifier: ^0.6.0 - version: 0.6.0(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + version: 0.6.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) '@tanstack/react-devtools': specifier: ^0.10.2 version: 0.10.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(solid-js@1.9.9) @@ -134,7 +137,7 @@ importers: version: 1.166.13(@tanstack/react-router@1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/router-plugin': specifier: ^1.167.22 - version: 1.167.22(@tanstack/react-router@1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + version: 1.167.22(@tanstack/react-router@1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) '@types/byte-size': specifier: ^8.1.2 version: 8.1.2 @@ -158,10 +161,10 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) '@vitest/ui': - specifier: ^4.1.4 - version: 4.1.4(vitest@4.1.4) + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) autoprefixer: specifier: ^10.5.0 version: 10.5.0(postcss@8.5.10) @@ -178,26 +181,26 @@ importers: specifier: ^0.34.5 version: 0.34.5 stylelint: - specifier: ^17.8.0 - version: 17.8.0(typescript@5.9.3) + specifier: ^17.9.0 + version: 17.9.0(typescript@5.9.3) stylelint-config-standard-scss: specifier: ^17.0.0 - version: 17.0.0(postcss@8.5.10)(stylelint@17.8.0(typescript@5.9.3)) + version: 17.0.0(postcss@8.5.10)(stylelint@17.9.0(typescript@5.9.3)) stylelint-scss: specifier: ^7.0.0 - version: 7.0.0(stylelint@17.8.0(typescript@5.9.3)) + version: 7.0.0(stylelint@17.9.0(typescript@5.9.3)) typescript: specifier: ~5.9.3 version: 5.9.3 vite: - specifier: ^8.0.9 - version: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + specifier: ^8.0.10 + version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) vite-plugin-image-optimizer: specifier: ^2.0.3 - version: 2.0.3(sharp@0.34.5)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + version: 2.0.3(sharp@0.34.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) vitest: - specifier: ^4.1.4 - version: 4.1.4(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + specifier: ^4.1.5 + version: 4.1.5(@types/node@25.6.0)(@vitest/ui@4.1.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) packages: @@ -397,15 +400,12 @@ packages: peerDependencies: postcss-selector-parser: ^7.1.1 - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -800,8 +800,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.126.0': - resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} @@ -920,103 +920,103 @@ packages: react-redux: optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.16': - resolution: {integrity: sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.16': - resolution: {integrity: sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': - resolution: {integrity: sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': - resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': - resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': - resolution: {integrity: sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': - resolution: {integrity: sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': - resolution: {integrity: sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.16': - resolution: {integrity: sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} @@ -1128,8 +1128,8 @@ packages: peerDependencies: solid-js: '>=1.9.7' - '@tanstack/form-core@1.29.0': - resolution: {integrity: sha512-uyeKEdJBfbj0bkBSwvSYVRtWLOaXvfNX3CeVw1HqGOXVLxpBBGAqWdYLc+UoX/9xcoFwFXrjR9QqMPzvwm2yyQ==} + '@tanstack/form-core@1.29.1': + resolution: {integrity: sha512-NIYPO36eEu7nSWvMpbFDQaBWyVtnH/C8fsZ3/XpJUT4uOWgmxsiUvHGbTbDNIQTXAKIkhwEl0sUrqBNn2SfUnw==} '@tanstack/history@1.161.6': resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} @@ -1154,8 +1154,8 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-form@1.29.0': - resolution: {integrity: sha512-jj425NNX0QKqbUzqSNiYI3HCPHSk2df47acXCJyXczWOTmG81ECZGkgofgqamFsSU9kMiH6Di5RLUnftrlhWSw==} + '@tanstack/react-form@1.29.1': + resolution: {integrity: sha512-hVHk4g0phd0HxRsv2ry6Xt8BqmalT55Q3cokhJBCC1St0hcGZhgwJJbohm9atao45BPG9e55DGvtbwExqZe35g==} peerDependencies: '@tanstack/react-start': '*' react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1384,11 +1384,11 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/expect@4.1.4': - resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} - '@vitest/mocker@4.1.4': - resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1398,25 +1398,25 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} - '@vitest/runner@4.1.4': - resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} - '@vitest/snapshot@4.1.4': - resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} - '@vitest/spy@4.1.4': - resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - '@vitest/ui@4.1.4': - resolution: {integrity: sha512-EgFR7nlj5iTDYZYCvavjFokNYwr3c3ry0sFiCg+N7B233Nwp+NNx7eoF/XvMWDCKY71xXAG3kFkt97ZHBJVL8A==} + '@vitest/ui@4.1.5': + resolution: {integrity: sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==} peerDependencies: - vitest: 4.1.4 + vitest: 4.1.5 - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} @@ -1474,8 +1474,8 @@ packages: peerDependencies: postcss: ^8.1.0 - axios@1.15.1: - resolution: {integrity: sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==} + axios@1.15.2: + resolution: {integrity: sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==} babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} @@ -1483,8 +1483,8 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - baseline-browser-mapping@2.10.20: - resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==} + baseline-browser-mapping@2.10.21: + resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1525,8 +1525,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001788: - resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1719,8 +1719,8 @@ packages: easy-file-picker@1.2.0: resolution: {integrity: sha512-GJxOW5s+g/pBr8Ha86a768yx0UZ6fYw+iAOrxK5HOzQ8q9hZxEJF0C8ztdAsH0mcze58FSpzv/d9flRCAuUKHg==} - electron-to-chromium@1.5.340: - resolution: {integrity: sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==} + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1755,8 +1755,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.45.1: - resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} + es-toolkit@1.46.0: + resolution: {integrity: sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA==} esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} @@ -2361,8 +2361,8 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-releases@2.0.37: - resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -2559,8 +2559,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.0.0-rc.16: - resolution: {integrity: sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2734,8 +2734,8 @@ packages: peerDependencies: stylelint: ^16.8.2 || ^17.0.0 - stylelint@17.8.0: - resolution: {integrity: sha512-oHkld9T60LDSaUQ4CSVc+tlt9eUoDlxhaGWShsUCKyIL14boZfmK5bSphZqx64aiC5tCqX+BsQMTMoSz8D1zIg==} + stylelint@17.9.0: + resolution: {integrity: sha512-xO0jeY6z1/urFL5L/BZLmB1yYlbRiRMQnYH6ArZIDWJ+SZXGssOY7XoYb1JIv/L220+EBnwwJXJS4Mt/F96SvA==} engines: {node: '>=20.19.0'} hasBin: true @@ -2911,12 +2911,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - - uuid@13.0.0: - resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true vfile-location@5.0.3: @@ -2944,8 +2940,8 @@ packages: svgo: optional: true - vite@8.0.9: - resolution: {integrity: sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2987,20 +2983,20 @@ packages: yaml: optional: true - vitest@4.1.4: - resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.4 - '@vitest/browser-preview': 4.1.4 - '@vitest/browser-webdriverio': 4.1.4 - '@vitest/coverage-istanbul': 4.1.4 - '@vitest/coverage-v8': 4.1.4 - '@vitest/ui': 4.1.4 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3283,7 +3279,7 @@ snapshots: dependencies: postcss-selector-parser: 7.1.1 - '@emnapi/core@1.9.2': + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 @@ -3294,11 +3290,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.2': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 @@ -3525,7 +3516,7 @@ snapshots: '@sinclair/typebox': 0.31.28 kysely: 0.28.16 sqlite-wasm-kysely: 0.3.0(kysely@0.28.16) - uuid: 13.0.0 + uuid: 14.0.0 transitivePeerDependencies: - babel-plugin-macros @@ -3564,16 +3555,16 @@ snapshots: js-sha256: 0.11.1 kysely: 0.28.16 sqlite-wasm-kysely: 0.3.0(kysely@0.28.16) - uuid: 10.0.0 + uuid: 14.0.0 transitivePeerDependencies: - babel-plugin-macros '@lix-js/server-protocol-schema@0.1.1': {} - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -3589,7 +3580,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@oxc-project/types@0.126.0': {} + '@oxc-project/types@0.127.0': {} '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -3680,56 +3671,56 @@ snapshots: react: 19.2.5 react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1) - '@rolldown/binding-android-arm64@1.0.0-rc.16': + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.16': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.16': + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.16': + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true - '@rolldown/pluginutils@1.0.0-rc.16': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} '@rolldown/pluginutils@1.0.0-rc.7': {} @@ -3828,7 +3819,7 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.6.0(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': + '@tanstack/devtools-vite@0.6.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -3840,7 +3831,7 @@ snapshots: chalk: 5.6.2 launch-editor: 2.13.2 picomatch: 4.0.4 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) transitivePeerDependencies: - bufferutil - supports-color @@ -3862,7 +3853,7 @@ snapshots: - csstype - utf-8-validate - '@tanstack/form-core@1.29.0': + '@tanstack/form-core@1.29.1': dependencies: '@tanstack/devtools-event-client': 0.4.3 '@tanstack/pacer-lite': 0.1.1 @@ -3889,9 +3880,9 @@ snapshots: - solid-js - utf-8-validate - '@tanstack/react-form@1.29.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@tanstack/react-form@1.29.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@tanstack/form-core': 1.29.0 + '@tanstack/form-core': 1.29.1 '@tanstack/react-store': 0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 transitivePeerDependencies: @@ -3975,7 +3966,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.167.22(@tanstack/react-router@1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': + '@tanstack/router-plugin@1.167.22(@tanstack/react-router@1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -3992,7 +3983,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) transitivePeerDependencies: - supports-color @@ -4111,60 +4102,60 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@6.0.1(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': + '@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) - '@vitest/expect@4.1.4': + '@vitest/expect@4.1.5': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0))': dependencies: - '@vitest/spy': 4.1.4 + '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) - '@vitest/pretty-format@4.1.4': + '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.4': + '@vitest/runner@4.1.5': dependencies: - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.5 pathe: 2.0.3 - '@vitest/snapshot@4.1.4': + '@vitest/snapshot@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.4': {} + '@vitest/spy@4.1.5': {} - '@vitest/ui@4.1.4(vitest@4.1.4)': + '@vitest/ui@4.1.5(vitest@4.1.5)': dependencies: - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.5 fflate: 0.8.2 flatted: 3.4.2 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vitest: 4.1.4(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + vitest: 4.1.5(@types/node@25.6.0)(@vitest/ui@4.1.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) - '@vitest/utils@4.1.4': + '@vitest/utils@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.4 + '@vitest/pretty-format': 4.1.5 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -4207,13 +4198,13 @@ snapshots: autoprefixer@10.5.0(postcss@8.5.10): dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001788 + caniuse-lite: 1.0.30001790 fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.10 postcss-value-parser: 4.2.0 - axios@1.15.1: + axios@1.15.2: dependencies: follow-redirects: 1.16.0 form-data: 4.0.5 @@ -4232,7 +4223,7 @@ snapshots: bail@2.0.2: {} - baseline-browser-mapping@2.10.20: {} + baseline-browser-mapping@2.10.21: {} binary-extensions@2.3.0: {} @@ -4242,10 +4233,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.20 - caniuse-lite: 1.0.30001788 - electron-to-chromium: 1.5.340 - node-releases: 2.0.37 + baseline-browser-mapping: 2.10.21 + caniuse-lite: 1.0.30001790 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) byte-size@9.0.1: {} @@ -4270,7 +4261,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001788: {} + caniuse-lite@1.0.30001790: {} ccount@2.0.1: {} @@ -4423,7 +4414,7 @@ snapshots: easy-file-picker@1.2.0: {} - electron-to-chromium@1.5.340: {} + electron-to-chromium@1.5.344: {} emoji-regex@8.0.0: {} @@ -4452,7 +4443,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.3 - es-toolkit@1.45.1: {} + es-toolkit@1.46.0: {} esbuild@0.27.7: optionalDependencies: @@ -5151,7 +5142,7 @@ snapshots: node-addon-api@7.1.1: optional: true - node-releases@2.0.37: {} + node-releases@2.0.38: {} normalize-path@3.0.0: {} @@ -5296,7 +5287,7 @@ snapshots: '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.45.1 + es-toolkit: 1.46.0 eventemitter3: 5.0.4 immer: 10.2.0 react: 19.2.5 @@ -5350,26 +5341,26 @@ snapshots: reusify@1.1.0: {} - rolldown@1.0.0-rc.16: + rolldown@1.0.0-rc.17: dependencies: - '@oxc-project/types': 0.126.0 - '@rolldown/pluginutils': 1.0.0-rc.16 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.16 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.16 - '@rolldown/binding-darwin-x64': 1.0.0-rc.16 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.16 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.16 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.16 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.16 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.16 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.16 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.16 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.16 + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 run-parallel@1.2.0: dependencies: @@ -5535,33 +5526,33 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylelint-config-recommended-scss@17.0.1(postcss@8.5.10)(stylelint@17.8.0(typescript@5.9.3)): + stylelint-config-recommended-scss@17.0.1(postcss@8.5.10)(stylelint@17.9.0(typescript@5.9.3)): dependencies: postcss-scss: 4.0.9(postcss@8.5.10) - stylelint: 17.8.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.8.0(typescript@5.9.3)) - stylelint-scss: 7.0.0(stylelint@17.8.0(typescript@5.9.3)) + stylelint: 17.9.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.9.0(typescript@5.9.3)) + stylelint-scss: 7.0.0(stylelint@17.9.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.10 - stylelint-config-recommended@18.0.0(stylelint@17.8.0(typescript@5.9.3)): + stylelint-config-recommended@18.0.0(stylelint@17.9.0(typescript@5.9.3)): dependencies: - stylelint: 17.8.0(typescript@5.9.3) + stylelint: 17.9.0(typescript@5.9.3) - stylelint-config-standard-scss@17.0.0(postcss@8.5.10)(stylelint@17.8.0(typescript@5.9.3)): + stylelint-config-standard-scss@17.0.0(postcss@8.5.10)(stylelint@17.9.0(typescript@5.9.3)): dependencies: - stylelint: 17.8.0(typescript@5.9.3) - stylelint-config-recommended-scss: 17.0.1(postcss@8.5.10)(stylelint@17.8.0(typescript@5.9.3)) - stylelint-config-standard: 40.0.0(stylelint@17.8.0(typescript@5.9.3)) + stylelint: 17.9.0(typescript@5.9.3) + stylelint-config-recommended-scss: 17.0.1(postcss@8.5.10)(stylelint@17.9.0(typescript@5.9.3)) + stylelint-config-standard: 40.0.0(stylelint@17.9.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.10 - stylelint-config-standard@40.0.0(stylelint@17.8.0(typescript@5.9.3)): + stylelint-config-standard@40.0.0(stylelint@17.9.0(typescript@5.9.3)): dependencies: - stylelint: 17.8.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.8.0(typescript@5.9.3)) + stylelint: 17.9.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.9.0(typescript@5.9.3)) - stylelint-scss@7.0.0(stylelint@17.8.0(typescript@5.9.3)): + stylelint-scss@7.0.0(stylelint@17.9.0(typescript@5.9.3)): dependencies: css-tree: 3.2.1 is-plain-object: 5.0.0 @@ -5571,9 +5562,9 @@ snapshots: postcss-resolve-nested-selector: 0.1.6 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - stylelint: 17.8.0(typescript@5.9.3) + stylelint: 17.9.0(typescript@5.9.3) - stylelint@17.8.0(typescript@5.9.3): + stylelint@17.9.0(typescript@5.9.3): dependencies: '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -5819,9 +5810,7 @@ snapshots: util-deprecate@1.0.2: {} - uuid@10.0.0: {} - - uuid@13.0.0: {} + uuid@14.0.0: {} vfile-location@5.0.3: dependencies: @@ -5855,20 +5844,20 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)): + vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)): dependencies: ansi-colors: 4.1.3 pathe: 2.0.3 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) optionalDependencies: sharp: 0.34.5 - vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0): + vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.10 - rolldown: 1.0.0-rc.16 + rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.6.0 @@ -5877,15 +5866,15 @@ snapshots: sass: 1.99.0 tsx: 4.21.0 - vitest@4.1.4(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)): + vitest@4.1.5(@types/node@25.6.0)(@vitest/ui@4.1.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -5897,11 +5886,11 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(sass@1.99.0)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.6.0 - '@vitest/ui': 4.1.4(vitest@4.1.4) + '@vitest/ui': 4.1.5(vitest@4.1.5) transitivePeerDependencies: - msw From a6f2b9a7e702a758aae2e215949a36a2422593b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 11:44:56 +0200 Subject: [PATCH 10/12] remove comment --- web/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/web/package.json b/web/package.json index bf293a1a2..6ca21fcd7 100644 --- a/web/package.json +++ b/web/package.json @@ -55,7 +55,6 @@ }, "pnpm": { "overrides": { - "//uuid": "Force uuid >= 14.0.0 to fix GHSA-w5hq-g745-h8pq (missing bounds check in v3/v5/v6). Both @inlang/sdk and @lix-js/sdk pull in older versions. Remove once upstream packages declare uuid@14 themselves.", "uuid": "^14.0.0" } }, From deb79c1dfe20694a039700b988bf7795963430dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 11:58:36 +0200 Subject: [PATCH 11/12] fix test setup --- crates/defguard_core/src/lib.rs | 29 +++++++++++++------ .../tests/integration/api/auth.rs | 8 +++-- .../tests/integration/api/common/mod.rs | 5 +++- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 77f44237e..de625351d 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -697,6 +697,25 @@ pub fn build_webapp( .layer(DefaultBodyLimit::max(REQUEST_BODY_LIMIT)) } +/// Wraps a router with the outermost security layers: the version header and the +/// baseline security headers middleware. +/// +/// Called by both `run_web_server` and the integration-test helper so that +/// test clients exercise the same middleware stack as the real server. +pub fn apply_security_layers(router: Router, tls_active: Arc) -> Router { + let tls_for_headers = Arc::clone(&tls_active); + // Version and security headers are the outermost layers so that ALL short-circuit + // responses (408 timeout, 413 body-too-large, 429 rate-limited) and swagger routes + // also carry the baseline security headers and the server version header. + router + .layer(DefguardVersionLayer::new( + Version::parse(VERSION).expect("VERSION is a valid semver string"), + )) + .layer(middleware::from_fn(move |req, next| { + headers::security_headers_middleware(Arc::clone(&tls_for_headers), req, next) + })) +} + /// Runs core web server exposing REST API. #[instrument(skip_all)] pub async fn run_web_server( @@ -765,15 +784,7 @@ pub async fn run_web_server( info!("Rate limiting disabled (per_second or burst is 0)"); } - // Version and security headers are the outermost layers so that ALL short-circuit - // responses (408 timeout, 413 body-too-large, 429 rate-limited) and swagger routes - // also carry the baseline security headers and the server version header. - let tls_for_headers = Arc::clone(&tls_active); - webapp = webapp - .layer(DefguardVersionLayer::new(Version::parse(VERSION)?)) - .layer(middleware::from_fn(move |req, next| { - headers::security_headers_middleware(Arc::clone(&tls_for_headers), req, next) - })); + webapp = apply_security_layers(webapp, Arc::clone(&tls_active)); let addr = SocketAddr::new( server_config diff --git a/crates/defguard_core/tests/integration/api/auth.rs b/crates/defguard_core/tests/integration/api/auth.rs index af2e8f8b9..8f2ec71ec 100644 --- a/crates/defguard_core/tests/integration/api/auth.rs +++ b/crates/defguard_core/tests/integration/api/auth.rs @@ -41,8 +41,12 @@ async fn dg25_19_clickjacking_vulnerability(_: PgPoolOptions, options: PgConnect let response = client.get("/").send().await; let headers = response.headers(); - let csp_header = headers.get("content-security-policy").unwrap(); - let csp_value = csp_header.to_str().unwrap(); + let csp_header = headers + .get("content-security-policy") + .expect("Content-Security-Policy header must be present on every response"); + let csp_value = csp_header + .to_str() + .expect("CSP header value must be valid ASCII"); assert!( csp_value.contains("frame-ancestors 'none'"), "CSP header should block all iframes with 'none' directive" diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 7f25686b0..f628aafe6 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -21,6 +21,7 @@ use defguard_common::{ secret::SecretStringWrapper, }; use defguard_core::{ + apply_security_layers, auth::failed_login::FailedLoginMap, build_webapp, db::AppEvent, @@ -138,6 +139,7 @@ pub(crate) async fn make_base_client( ); let (web_reload_tx, _web_reload_rx) = broadcast::channel::<()>(8); + let tls_active = Arc::new(AtomicBool::new(false)); let webapp = build_webapp( tx, rx, @@ -150,8 +152,9 @@ pub(crate) async fn make_base_client( api_event_tx, Arc::default(), proxy_control_tx, - Arc::new(AtomicBool::new(false)), + Arc::clone(&tls_active), ); + let webapp = apply_security_layers(webapp, tls_active); ( TestClient::new(webapp, listener, api_event_rx), From 0bddc29cc84cc8aafc973fc48afd50272df73f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Apr 2026 12:49:54 +0200 Subject: [PATCH 12/12] remove override --- web/package.json | 5 ----- web/pnpm-lock.yaml | 55 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/web/package.json b/web/package.json index 6ca21fcd7..0c4e319d0 100644 --- a/web/package.json +++ b/web/package.json @@ -53,11 +53,6 @@ "zod": "^4.3.6", "zustand": "^5.0.12" }, - "pnpm": { - "overrides": { - "uuid": "^14.0.0" - } - }, "devDependencies": { "@biomejs/biome": "2.4.12", "@inlang/paraglide-js": "2.16.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 6fd1c16ca..63a7f1b47 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -4,9 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - uuid: ^14.0.0 - importers: .: @@ -315,24 +312,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.4.12': resolution: {integrity: sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.12': resolution: {integrity: sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.4.12': resolution: {integrity: sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.4.12': resolution: {integrity: sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==} @@ -612,89 +613,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -812,36 +829,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -929,36 +952,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} @@ -2117,24 +2146,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -2875,8 +2908,12 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@14.0.0: - resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} hasBin: true vfile-location@5.0.3: @@ -3480,7 +3517,7 @@ snapshots: '@sinclair/typebox': 0.31.28 kysely: 0.28.16 sqlite-wasm-kysely: 0.3.0(kysely@0.28.16) - uuid: 14.0.0 + uuid: 13.0.0 transitivePeerDependencies: - babel-plugin-macros @@ -3519,7 +3556,7 @@ snapshots: js-sha256: 0.11.1 kysely: 0.28.16 sqlite-wasm-kysely: 0.3.0(kysely@0.28.16) - uuid: 14.0.0 + uuid: 10.0.0 transitivePeerDependencies: - babel-plugin-macros @@ -5774,7 +5811,9 @@ snapshots: util-deprecate@1.0.2: {} - uuid@14.0.0: {} + uuid@10.0.0: {} + + uuid@13.0.0: {} vfile-location@5.0.3: dependencies: