From f9c0aca4eec42852ae4c0a75475d741576153cc1 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Fri, 12 Dec 2025 09:24:06 +0100 Subject: [PATCH 1/9] test: disable http proxy when running cargo test --- crate/server/src/tests/test_utils.rs | 57 ++++++++++++++++++ crate/test_kms_server/src/test_server.rs | 77 ++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/crate/server/src/tests/test_utils.rs b/crate/server/src/tests/test_utils.rs index 7c4e867a9e..1767731054 100644 --- a/crate/server/src/tests/test_utils.rs +++ b/crate/server/src/tests/test_utils.rs @@ -39,6 +39,9 @@ pub(crate) fn https_clap_config() -> ClapConfig { } pub(crate) fn https_clap_config_opts(kms_public_url: Option) -> ClapConfig { + // Ensure local test traffic bypasses any corporate proxy + ensure_no_proxy_for_localhost(); + disable_proxies_for_tests(); let sqlite_path = get_tmp_sqlite_path(); // In FIPS mode, disable TLS with P12 certificates since PKCS12KDF is not FIPS-approved @@ -82,6 +85,60 @@ pub(crate) fn https_clap_config_opts(kms_public_url: Option) -> ClapConf } } +/// Ensure localhost bypasses any corporate proxy for tests. +fn ensure_no_proxy_for_localhost() { + let has_http_proxy = std::env::var_os("HTTP_PROXY").is_some() + || std::env::var_os("http_proxy").is_some() + || std::env::var_os("HTTPS_PROXY").is_some() + || std::env::var_os("https_proxy").is_some(); + + if !has_http_proxy { + return; + } + + let existing = std::env::var("NO_PROXY") + .ok() + .or_else(|| std::env::var("no_proxy").ok()) + .unwrap_or_default(); + + let required = ["localhost", "127.0.0.1", "::1"]; + let mut parts: Vec = existing + .split(',') + .map(|s| s.trim().to_owned()) + .filter(|s| !s.is_empty()) + .collect(); + + for &r in &required { + if !parts.iter().any(|p| p.eq_ignore_ascii_case(r)) { + parts.push(r.to_owned()); + } + } + + let updated = parts.join(","); + // Set both uppercase and lowercase to cover different libraries' expectations + unsafe { + std::env::set_var("NO_PROXY", &updated); + std::env::set_var("no_proxy", &updated); + } +} + +/// Clear proxy env vars for the test process so localhost traffic is never proxied. +fn disable_proxies_for_tests() { + let has_proxy = std::env::var_os("HTTP_PROXY").is_some() + || std::env::var_os("http_proxy").is_some() + || std::env::var_os("HTTPS_PROXY").is_some() + || std::env::var_os("https_proxy").is_some(); + if !has_proxy { + return; + } + unsafe { + std::env::remove_var("HTTP_PROXY"); + std::env::remove_var("http_proxy"); + std::env::remove_var("HTTPS_PROXY"); + std::env::remove_var("https_proxy"); + } +} + pub(crate) fn get_tmp_sqlite_path() -> PathBuf { // Set the absolute path of the project directory let project_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) diff --git a/crate/test_kms_server/src/test_server.rs b/crate/test_kms_server/src/test_server.rs index e1e633812a..7bdfdb4b98 100644 --- a/crate/test_kms_server/src/test_server.rs +++ b/crate/test_kms_server/src/test_server.rs @@ -74,6 +74,73 @@ fn resolve_test_port(preferred_port: u16) -> Result { Ok(port) } +/// Ensure localhost bypasses any corporate proxy for tests. +/// When `HTTP_PROXY`/`HTTPS_PROXY` are set, add standard loopback hosts +/// to `NO_PROXY` so local test servers are reachable. +#[allow(unsafe_code)] +fn ensure_no_proxy_for_localhost() { + let has_http_proxy = env::var_os("HTTP_PROXY").is_some() + || env::var_os("http_proxy").is_some() + || env::var_os("HTTPS_PROXY").is_some() + || env::var_os("https_proxy").is_some(); + + if !has_http_proxy { + return; + } + + // Existing NO_PROXY entries, normalized to a comma-separated list + let existing = env::var("NO_PROXY") + .ok() + .or_else(|| env::var("no_proxy").ok()) + .unwrap_or_default(); + + // Always include common loopback hosts + let required = ["localhost", "127.0.0.1", "::1"]; + + // Build a normalized set + let mut parts: Vec = existing + .split(',') + .map(|s| s.trim().to_owned()) + .filter(|s| !s.is_empty()) + .collect(); + + for &r in &required { + if !parts.iter().any(|p| p.eq_ignore_ascii_case(r)) { + parts.push(r.to_owned()); + } + } + + let updated = parts.join(","); + // Set both uppercase and lowercase to cover different libraries' expectations + unsafe { + env::set_var("NO_PROXY", &updated); + env::set_var("no_proxy", &updated); + } + trace!("Ensured NO_PROXY for localhost: {}", updated); +} + +/// As a last resort for reliability, clear proxy env vars for the test process +/// so localhost traffic is never sent through a corporate proxy. +#[allow(unsafe_code)] +fn disable_proxies_for_tests() { + // Only clear if a proxy is set; keep environment untouched otherwise. + let has_proxy = env::var_os("HTTP_PROXY").is_some() + || env::var_os("http_proxy").is_some() + || env::var_os("HTTPS_PROXY").is_some() + || env::var_os("https_proxy").is_some(); + if !has_proxy { + return; + } + // Remove all common proxy variables to avoid library-specific behaviors. + unsafe { + env::remove_var("HTTP_PROXY"); + env::remove_var("http_proxy"); + env::remove_var("HTTPS_PROXY"); + env::remove_var("https_proxy"); + } + trace!("Disabled HTTP(S)_PROXY for test run to protect localhost"); +} + // Small utilities to reduce repetition #[inline] fn root_dir() -> PathBuf { @@ -241,6 +308,10 @@ pub async fn start_test_kms_server_with_config(config: ClapConfig) -> &'static T #[allow(clippy::unwrap_used)] pub async fn start_default_test_kms_server() -> &'static TestsContext { trace!("Starting default test server"); + // Ensure corporate proxies do not intercept localhost requests in tests + ensure_no_proxy_for_localhost(); + // If NO_PROXY is not sufficient for the HTTP stack, hard-disable proxies + disable_proxies_for_tests(); ONCE.get_or_try_init(|| async move { let use_kek = env::var_os("KMS_USE_KEK"); let port = resolve_test_port(DEFAULT_KMS_SERVER_PORT)?; @@ -731,6 +802,9 @@ pub async fn start_test_server_with_options( non_revocable_key_id: Option>, privileged_users: Option>, ) -> Result { + // Protect local test connections from corporate proxies + ensure_no_proxy_for_localhost(); + disable_proxies_for_tests(); // Destructure options to avoid borrow/move conflicts let AuthenticationOptions { client, @@ -1014,6 +1088,9 @@ fn generate_server_params( async fn start_from_server_params( server_params: ServerParams, ) -> Result { + // Protect local test connections from corporate proxies + ensure_no_proxy_for_localhost(); + disable_proxies_for_tests(); // Create a (object owner) conf let owner_client_config = generate_owner_conf(&server_params, &ClientAuthOptions::default())?; From 4cab26067d183ef47dc4821014d584d2c8bd69bf Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 24 Mar 2026 15:10:20 +0100 Subject: [PATCH 2/9] fix: use proxy params to fetch CRLs --- crate/server/src/core/operations/import.rs | 2 +- crate/server/src/core/operations/validate.rs | 59 ++++++++++++++++++-- crate/server/src/tests/test_utils.rs | 18 ++++++ crate/server/src/tests/test_validate.rs | 11 ++-- crate/test_kms_server/src/test_server.rs | 29 +++++----- 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/crate/server/src/core/operations/import.rs b/crate/server/src/core/operations/import.rs index 84ac39d0ed..e0a468391e 100644 --- a/crate/server/src/core/operations/import.rs +++ b/crate/server/src/core/operations/import.rs @@ -131,7 +131,7 @@ pub(crate) async fn import( }) = &request.object { if let Ok(cert) = X509::from_der(certificate_value) { - match verify_crls(vec![cert]).await { + match verify_crls(vec![cert], kms.params.proxy_params.as_ref()).await { Err(KmsError::Certificate(_)) => { debug!( "Import: certificate is revoked per CRL check, \ diff --git a/crate/server/src/core/operations/validate.rs b/crate/server/src/core/operations/validate.rs index 6e258ae9b9..3a6f793a7d 100644 --- a/crate/server/src/core/operations/validate.rs +++ b/crate/server/src/core/operations/validate.rs @@ -21,6 +21,7 @@ use openssl::{ }; use crate::{ + config::ProxyParams, core::{KMS, retrieve_object_utils::retrieve_object_for_operation}, error::KmsError, result::KResult, @@ -130,7 +131,7 @@ pub(crate) async fn validate_operation( verify_chain_signature(&certificates)?; validate_chain_date(&certificates, &request.validity_time)?; - verify_crls(certificates).await?; + verify_crls(certificates, kms.params.proxy_params.as_ref()).await?; Ok(ValidateResponse { validity_indicator: ValidityIndicator::Valid, @@ -433,7 +434,10 @@ enum UriType { /// - There is an error in retrieving the CRL from a URL. /// - There is an error in reading the CRL from a file path. /// ``` -async fn get_crl_bytes(uri_list: Vec) -> KResult>> { +async fn get_crl_bytes( + uri_list: Vec, + proxy_params: Option<&ProxyParams>, +) -> KResult>> { trace!("get_crl_bytes: entering: uri_list: {uri_list:?}"); let mut result = HashMap::new(); @@ -470,7 +474,49 @@ async fn get_crl_bytes(uri_list: Vec) -> KResult continue; } - let response = reqwest::Client::new().get(&url).send().await?; + let mut client_builder = reqwest::Client::builder(); + if let Some(proxy_params) = proxy_params { + let mut proxy = reqwest::Proxy::all(proxy_params.url.clone()).map_err(|e| { + KmsError::Certificate(format!( + "Failed to configure the HTTPS proxy for CRL fetch: {e}" + )) + })?; + if let Some(ref username) = proxy_params.basic_auth_username { + proxy = proxy.basic_auth( + username, + proxy_params + .basic_auth_password + .as_deref() + .unwrap_or_default(), + ); + } else if let Some(ref custom_auth_header) = proxy_params.custom_auth_header { + proxy = proxy.custom_http_auth( + reqwest::header::HeaderValue::from_str(custom_auth_header).map_err( + |e| { + KmsError::Certificate(format!( + "Failed to set custom HTTP auth header for CRL fetch: {e}" + )) + }, + )?, + ); + } + if !proxy_params.exclusion_list.is_empty() { + proxy = proxy.no_proxy(reqwest::NoProxy::from_string( + &proxy_params.exclusion_list.join(","), + )); + } + client_builder = client_builder.proxy(proxy); + } + let response = client_builder + .build() + .map_err(|e| { + KmsError::Certificate(format!( + "Failed to build reqwest client for CRL fetch: {e}" + )) + })? + .get(&url) + .send() + .await?; debug!("after getting CRL: url: {url}"); if response.status().is_success() { let crl_bytes = @@ -545,7 +591,10 @@ async fn get_crl_bytes(uri_list: Vec) -> KResult /// * If the CRL signature is invalid. /// * If there is an issue fetching the CRL bytes from the URIs. /// ``` -pub(crate) async fn verify_crls(certificates: Vec) -> KResult { +pub(crate) async fn verify_crls( + certificates: Vec, + proxy_params: Option<&ProxyParams>, +) -> KResult { let mut current_crls: HashMap> = HashMap::new(); for (idx, certificate) in certificates.iter().enumerate() { @@ -590,7 +639,7 @@ pub(crate) async fn verify_crls(certificates: Vec) -> KResult ClapConfig { https_clap_config_opts(None) } +/// Like `https_clap_config`, but additionally captures any `HTTPS_PROXY` / `HTTP_PROXY` +/// environment variable that is set *before* the test helpers clear it, then assigns it +/// to the server's `proxy_params` so that server-side outbound requests (e.g. CRL fetches) +/// are routed through the configured corporate proxy. +pub(crate) fn https_clap_config_with_external_proxy() -> ClapConfig { + // Capture proxy URL before disable_proxies_for_tests() removes it from the environment. + let proxy_url = std::env::var("HTTPS_PROXY") + .ok() + .or_else(|| std::env::var("https_proxy").ok()) + .or_else(|| std::env::var("HTTP_PROXY").ok()) + .or_else(|| std::env::var("http_proxy").ok()); + let mut config = https_clap_config_opts(None); + if let Some(url) = proxy_url { + config.proxy.proxy_url = Some(url); + } + config +} + pub(crate) fn https_clap_config_opts(kms_public_url: Option) -> ClapConfig { // Ensure local test traffic bypasses any corporate proxy ensure_no_proxy_for_localhost(); diff --git a/crate/server/src/tests/test_validate.rs b/crate/server/src/tests/test_validate.rs index f88905a2e3..2a6f5c8e70 100644 --- a/crate/server/src/tests/test_validate.rs +++ b/crate/server/src/tests/test_validate.rs @@ -14,10 +14,11 @@ use cosmian_kms_server_database::reexport::cosmian_kmip::{ use cosmian_logger::debug; use crate::{ - config::ServerParams, core::KMS, error::KmsError, tests::test_utils::https_clap_config, + config::ServerParams, core::KMS, error::KmsError, + tests::test_utils::https_clap_config_with_external_proxy, }; -#[ignore = "Requires network access to perform certificate validation"] +#[ignore = "Requires network access to perform certificate validation since CRL is fetched from https://package.cosmian.com/kms/crl_tests/intermediate.crl.pem"] #[tokio::test] pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsError> { // Skip this test in Nix sandbox (no network access) @@ -39,7 +40,7 @@ pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsErr let leaf1_cert = fs::read(leaf1_path)?; let leaf2_cert = fs::read(leaf2_path)?; - let clap_config = https_clap_config(); + let clap_config = https_clap_config_with_external_proxy(); let kms = Arc::new(KMS::instantiate(Arc::new(ServerParams::try_from(clap_config)?)).await?); let owner = "eyJhbGciOiJSUzI1Ni"; let request = Validate { @@ -116,7 +117,7 @@ pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsErr Ok(()) } -#[ignore = "Requires network access to perform certificate validation"] +#[ignore = "Requires network access to perform certificate validation since CRL is fetched from https://package.cosmian.com/kms/crl_tests/intermediate.crl.pem"] #[tokio::test] pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError> { // Skip this test in Nix sandbox (no network access) @@ -139,7 +140,7 @@ pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError let leaf1_cert = fs::read(leaf1_path)?; let leaf2_cert = fs::read(leaf2_path)?; - let clap_config = https_clap_config(); + let clap_config = https_clap_config_with_external_proxy(); let kms = Arc::new(KMS::instantiate(Arc::new(ServerParams::try_from(clap_config)?)).await?); let owner = "eyJhbGciOiJSUzI1Ni"; // add certificates to kms diff --git a/crate/test_kms_server/src/test_server.rs b/crate/test_kms_server/src/test_server.rs index 7bdfdb4b98..1d5776ba4e 100644 --- a/crate/test_kms_server/src/test_server.rs +++ b/crate/test_kms_server/src/test_server.rs @@ -312,12 +312,12 @@ pub async fn start_default_test_kms_server() -> &'static TestsContext { ensure_no_proxy_for_localhost(); // If NO_PROXY is not sufficient for the HTTP stack, hard-disable proxies disable_proxies_for_tests(); - ONCE.get_or_try_init(|| async move { + Box::pin(ONCE.get_or_try_init(|| async move { let use_kek = env::var_os("KMS_USE_KEK"); let port = resolve_test_port(DEFAULT_KMS_SERVER_PORT)?; match use_kek { Some(_use_kek) => { - let server_params = create_server_params_with_kek(port).await.unwrap(); + let server_params = Box::pin(create_server_params_with_kek(port)).await.unwrap(); start_from_server_params(server_params).await } None => { @@ -331,7 +331,7 @@ pub async fn start_default_test_kms_server() -> &'static TestsContext { .await } } - }) + })) .await .unwrap_or_else(|e| { error!("failed to start default test server: {e}"); @@ -551,7 +551,7 @@ async fn create_kek_in_db() -> Result<(PathBuf, String), KmsClientError> { } async fn create_server_params_with_kek(port: u16) -> Result { - let (workspace_dir, kek_id) = create_kek_in_db().await?; + let (workspace_dir, kek_id) = Box::pin(create_kek_in_db()).await?; trace!( "Key encryption key created: {kek_id} in workspace {}", workspace_dir.display() @@ -598,18 +598,17 @@ async fn create_server_params_with_kek(port: u16) -> Result &'static TestsContext { trace!("Starting test server with Utimaco HSM and KEK"); // Build ServerParams with HSM fields directly and start from them - ONCE_SERVER_WITH_KEK - .get_or_try_init(|| async move { - let port = resolve_test_port(DEFAULT_KMS_SERVER_PORT + 4)?; - let server_params = create_server_params_with_kek(port).await.unwrap(); + Box::pin(ONCE_SERVER_WITH_KEK.get_or_try_init(|| async move { + let port = resolve_test_port(DEFAULT_KMS_SERVER_PORT + 4)?; + let server_params = Box::pin(create_server_params_with_kek(port)).await.unwrap(); - start_from_server_params(server_params).await - }) - .await - .unwrap_or_else(|e| { - error!("failed to start test server with utimaco hsm: {e}"); - std::process::abort(); - }) + start_from_server_params(server_params).await + })) + .await + .unwrap_or_else(|e| { + error!("failed to start test server with utimaco hsm: {e}"); + std::process::abort(); + }) } /// Privileged users From 55e080686094a02310519ed4d1535adc35cac9f9 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Mon, 6 Apr 2026 22:15:59 +0200 Subject: [PATCH 3/9] fix: pin test_validate_with_certificates --- .github/scripts/common.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/scripts/common.sh b/.github/scripts/common.sh index e9a99ffee8..b4347f64f7 100644 --- a/.github/scripts/common.sh +++ b/.github/scripts/common.sh @@ -381,6 +381,19 @@ _run_workspace_tests() { cargo test "${cargo_test_args[@]}" + # Run the network-dependent certificate validation tests (marked #[ignore]) for all DB + # backends. For postgresql/mysql/redis-findex these tests were already included in the + # --ignored run above; for sqlite they need an explicit additional invocation. + if [ "$KMS_TEST_DB" = "sqlite" ]; then + local -a cargo_test_validate_args + cargo_test_validate_args=(--workspace --lib) + if [ ${#FEATURES_FLAG[@]} -gt 0 ]; then + cargo_test_validate_args+=("${FEATURES_FLAG[@]}") + fi + cargo_test_validate_args+=(-- --nocapture --ignored test_validate_with_certificates) + cargo test "${cargo_test_validate_args[@]}" + fi + # For database backends (postgresql, mysql, redis), also run the regular non-ignored tests # For sqlite, skip this step since all non-ignored tests already ran above if [ "$KMS_TEST_DB" != "sqlite" ]; then From 1c662c59fc885001b3ba2523661259a0a2679bcb Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 7 Apr 2026 09:00:00 +0200 Subject: [PATCH 4/9] test: enable validation cert-tests on Nix --- crate/server/src/tests/test_validate.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crate/server/src/tests/test_validate.rs b/crate/server/src/tests/test_validate.rs index 2a6f5c8e70..9c062b7fcf 100644 --- a/crate/server/src/tests/test_validate.rs +++ b/crate/server/src/tests/test_validate.rs @@ -21,14 +21,6 @@ use crate::{ #[ignore = "Requires network access to perform certificate validation since CRL is fetched from https://package.cosmian.com/kms/crl_tests/intermediate.crl.pem"] #[tokio::test] pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsError> { - // Skip this test in Nix sandbox (no network access) - if option_env!("IN_NIX_SHELL").is_some() || std::env::var("IN_NIX_SHELL").is_ok() { - eprintln!( - "Skipping test_validate_with_certificates_bytes: running in Nix sandbox without network access" - ); - return Ok(()); - } - cosmian_logger::log_init(None); let root_path = path::Path::new("../../test_data/certificates/chain/ca.cert.der"); let intermediate_path = @@ -120,14 +112,6 @@ pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsErr #[ignore = "Requires network access to perform certificate validation since CRL is fetched from https://package.cosmian.com/kms/crl_tests/intermediate.crl.pem"] #[tokio::test] pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError> { - // Skip this test in Nix sandbox (no network access) - if option_env!("IN_NIX_SHELL").is_some() || std::env::var("IN_NIX_SHELL").is_ok() { - eprintln!( - "Skipping test_validate_with_certificates_ids: running in Nix sandbox without network access" - ); - return Ok(()); - } - cosmian_logger::log_init(None); let root_path = path::Path::new("../../test_data/certificates/chain/ca.cert.der"); let intermediate_path = From 521fb839240521b50af129eede2ca37352c4a970 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 7 Apr 2026 12:31:14 +0200 Subject: [PATCH 5/9] ci: fix bench error --- .github/workflows/benchmark.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 50c3b43d46..018a1c6468 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -31,6 +31,9 @@ jobs: timeout-minutes: 120 steps: + - name: Clean workspace (fix self-hosted runner permission issues) + run: sudo chown -R "$USER:$USER" "$GITHUB_WORKSPACE" || true + - uses: actions/checkout@v6 with: submodules: recursive From 86375bfecd289b3d80f7aa0d45487b21d1bf5ae6 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 7 Apr 2026 18:30:31 +0200 Subject: [PATCH 6/9] chore: update Nix expected hashes --- nix/expected-hashes/ui.pnpm.darwin.sha256 | 2 +- nix/expected-hashes/ui.pnpm.linux.sha256 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/expected-hashes/ui.pnpm.darwin.sha256 b/nix/expected-hashes/ui.pnpm.darwin.sha256 index 4f47d36ab4..b4d99b9d72 100644 --- a/nix/expected-hashes/ui.pnpm.darwin.sha256 +++ b/nix/expected-hashes/ui.pnpm.darwin.sha256 @@ -1 +1 @@ -sha256-/8YcIdLmqyMm26iydx2E5kUppLukbbmBddMn6qfvvng= +sha256-CP5chV+SAyEWVxBbZ7sGWk3cCW7PL5yvuW3kBHFdEQE= diff --git a/nix/expected-hashes/ui.pnpm.linux.sha256 b/nix/expected-hashes/ui.pnpm.linux.sha256 index 08bcf542e8..87784cbc26 100644 --- a/nix/expected-hashes/ui.pnpm.linux.sha256 +++ b/nix/expected-hashes/ui.pnpm.linux.sha256 @@ -1 +1 @@ -sha256-z9zREPpkDZasNI+miZIXJf1iS3NTrSeeDekxINsv6Vk= +sha256-DF+WasQ0X2csvxFP4Gz181yQDJ/05NRDDYDHIPFLiJY= From be327f68c6683b2b21ebeaf2aa59762bf6fc5b08 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 7 Apr 2026 20:16:36 +0200 Subject: [PATCH 7/9] ci: fix Nix auto hashes update --- .github/scripts/release/nix_build_update_hash.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/scripts/release/nix_build_update_hash.sh b/.github/scripts/release/nix_build_update_hash.sh index 54f91cdac4..236b05cd9d 100755 --- a/.github/scripts/release/nix_build_update_hash.sh +++ b/.github/scripts/release/nix_build_update_hash.sh @@ -101,7 +101,7 @@ build_attr() { echo "$output" # Parse hash mismatches - local last_drv="" updated=0 + local last_drv="" updated=0 skipped=0 while IFS= read -r line; do if [[ "$line" =~ hash\ mismatch\ in\ fixed-output\ derivation.*\'(/nix/store/[^\']+)\' ]]; then local drv_path="${BASH_REMATCH[1]}" @@ -118,6 +118,9 @@ build_attr() { echo " Updating $(basename "$target_file"): $got_hash" echo "$got_hash" > "$target_file" updated=$((updated + 1)) + else + echo " Skipping (managed by peer OS runner): $last_drv" + skipped=$((skipped + 1)) fi last_drv="" fi @@ -129,6 +132,14 @@ build_attr() { continue fi + # All remaining failures are hash files owned by the peer OS runner (e.g. + # ui.vendor.*.sha256 on macOS, or ui.pnpm.darwin.sha256 on Linux). Those + # will be fixed by the other matrix job; this runner is done. + if [[ "$updated" -eq 0 && "$skipped" -gt 0 ]]; then + echo "==> ${skipped} hash mismatch(es) are managed by the peer OS runner — skipping." + return 0 + fi + echo "ERROR: nix-build -A ${attr} failed after ${attempt} attempt(s)." >&2 return 1 done From ef3fa84f5a196164227344c41a2c533da38ca33d Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 7 Apr 2026 21:39:06 +0200 Subject: [PATCH 8/9] fix: hardcode wasm-bindgen-cli version --- crate/clients/wasm/Cargo.toml | 5 ++++- nix/ui.nix | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crate/clients/wasm/Cargo.toml b/crate/clients/wasm/Cargo.toml index 2357e610dc..ec18e0cd07 100644 --- a/crate/clients/wasm/Cargo.toml +++ b/crate/clients/wasm/Cargo.toml @@ -34,7 +34,10 @@ pem = { workspace = true } serde = { workspace = true } serde-wasm-bindgen = "0.6.5" serde_json = { workspace = true } -wasm-bindgen = "0.2.108" +# Pinned to exact version: nix/ui.nix builds wasm-bindgen-cli at the same version. +# If you upgrade this, also update `wasmBindgenCli.version` in nix/ui.nix +# (along with its src sha256 and cargoHash). +wasm-bindgen = "=0.2.108" x509-cert = { workspace = true, features = ["pem"] } zeroize = { workspace = true } diff --git a/nix/ui.nix b/nix/ui.nix index ec723b7ef2..72b8409c7b 100644 --- a/nix/ui.nix +++ b/nix/ui.nix @@ -68,7 +68,10 @@ let else pkgs.rustPlatform; - # Build a matching wasm-bindgen-cli to the version used by the crates + # Build a matching wasm-bindgen-cli to the version used by the crates. + # This version MUST match the exact `wasm-bindgen` pin in + # crate/clients/wasm/Cargo.toml. If you upgrade either one, update both + # (and regenerate src sha256 + cargoHash below via nix-prefetch-url). wasmBindgenCli = rustPlatform.buildRustPackage rec { pname = "wasm-bindgen-cli"; version = "0.2.108"; From 50d066691c419ea4a7d03f640971b10c1be5a50d Mon Sep 17 00:00:00 2001 From: Manuthor Date: Tue, 7 Apr 2026 22:02:10 +0200 Subject: [PATCH 9/9] ci: fix release workflow --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8110346fd..91f036b4ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -172,8 +172,8 @@ jobs: with: toolchain: 1.90.0 - - name: Regenerate Cargo.lock - run: cargo generate-lockfile + - name: Update Cargo.lock + run: cargo build --workspace - name: Regenerate CBOM run: |