From 492cd9083fe18909e2b831f06411c9f520e3721c Mon Sep 17 00:00:00 2001 From: Kun Lai Date: Fri, 10 Jul 2026 17:43:55 +0800 Subject: [PATCH 1/6] tng: attach URL/method context to AS fetch failures --- rats-cert/src/tee/coco/converter/builtin.rs | 6 ++++++ rats-cert/src/tee/coco/converter/grpc.rs | 5 +++++ rats-cert/src/tee/coco/converter/mod.rs | 12 ++++++++++++ rats-cert/src/tee/coco/converter/restful.rs | 5 +++++ rats-cert/src/tee/ita/converter.rs | 5 +++++ .../tunnel/ingress/protocol/ohttp/security/client.rs | 11 +++++++++-- tng/src/tunnel/provider/converter.rs | 8 ++++++++ 7 files changed, 50 insertions(+), 2 deletions(-) diff --git a/rats-cert/src/tee/coco/converter/builtin.rs b/rats-cert/src/tee/coco/converter/builtin.rs index 5ba1e19f..8fb2a727 100644 --- a/rats-cert/src/tee/coco/converter/builtin.rs +++ b/rats-cert/src/tee/coco/converter/builtin.rs @@ -298,6 +298,12 @@ impl BuiltinCocoConverter { }) } + /// Builtin converters run the attestation service in-process and have no + /// remote attestation-service address; return a sentinel for error context. + pub fn as_addr(&self) -> &'static str { + "" + } + /// Load policy from configuration /// Returns None for the AS built-in default policy /// (HardwareWithReferenceValues), and a URL-safe base64 encoding of the diff --git a/rats-cert/src/tee/coco/converter/grpc.rs b/rats-cert/src/tee/coco/converter/grpc.rs index dcc02c44..5b393228 100644 --- a/rats-cert/src/tee/coco/converter/grpc.rs +++ b/rats-cert/src/tee/coco/converter/grpc.rs @@ -62,6 +62,11 @@ impl CocoGrpcConverter { request_metadata, }) } + + /// The attestation-service gRPC address this converter targets. + pub fn as_addr(&self) -> &str { + &self.as_addr + } } #[async_trait::async_trait] diff --git a/rats-cert/src/tee/coco/converter/mod.rs b/rats-cert/src/tee/coco/converter/mod.rs index f2368e13..1e95c7c0 100644 --- a/rats-cert/src/tee/coco/converter/mod.rs +++ b/rats-cert/src/tee/coco/converter/mod.rs @@ -22,6 +22,18 @@ pub enum CocoConverter { Builtin(BuiltinCocoConverter), } +impl CocoConverter { + /// The attestation-service address this converter targets (for error context). + pub fn as_addr(&self) -> &str { + match self { + CocoConverter::Restful(c) => c.as_addr(), + CocoConverter::Grpc(c) => c.as_addr(), + #[cfg(feature = "__builtin-as")] + CocoConverter::Builtin(c) => c.as_addr(), + } + } +} + pub enum CoCoNonce { Jwt(String), } diff --git a/rats-cert/src/tee/coco/converter/restful.rs b/rats-cert/src/tee/coco/converter/restful.rs index 6d7acf16..3fbcbff1 100644 --- a/rats-cert/src/tee/coco/converter/restful.rs +++ b/rats-cert/src/tee/coco/converter/restful.rs @@ -67,6 +67,11 @@ impl CocoRestfulConverter { policy_ids: policy_ids.to_owned(), }) } + + /// The attestation-service base address this converter targets. + pub fn as_addr(&self) -> &str { + &self.as_addr + } } mod as_api { diff --git a/rats-cert/src/tee/ita/converter.rs b/rats-cert/src/tee/ita/converter.rs index 17e63b46..1fe0d1f1 100644 --- a/rats-cert/src/tee/ita/converter.rs +++ b/rats-cert/src/tee/ita/converter.rs @@ -84,6 +84,11 @@ impl ItaConverter { }) } + /// The ITA portal base URL this converter targets. + pub fn as_addr(&self) -> &str { + &self.base_url + } + fn is_retryable_error(status: reqwest::StatusCode, body: &str) -> bool { status.is_server_error() || (status == reqwest::StatusCode::BAD_REQUEST diff --git a/tng/src/tunnel/ingress/protocol/ohttp/security/client.rs b/tng/src/tunnel/ingress/protocol/ohttp/security/client.rs index b25d1452..6403865f 100644 --- a/tng/src/tunnel/ingress/protocol/ohttp/security/client.rs +++ b/tng/src/tunnel/ingress/protocol/ohttp/security/client.rs @@ -293,7 +293,13 @@ impl OHttpClientInner { let challenge_token = converter .get_nonce() .await - .map_err(|e| TngError::ClientRequestKeyConfigFailed(e.into()))?; + .with_context(|| { + format!( + "requesting challenge token from AS at {}", + converter.as_addr() + ) + }) + .map_err(TngError::ClientRequestKeyConfigFailed)?; // Request hpke configuration for server let response = self @@ -496,7 +502,8 @@ impl OHttpClientInner { .json(&key_config_request) .send() .await - .map_err(|error| TngError::ClientRequestKeyConfigFailed(error.into()))? + .with_context(|| format!("POST key-config request to {}", self.base_url)) + .map_err(TngError::ClientRequestKeyConfigFailed)? .check_error_response() .await .map_err(TngError::ClientRequestKeyConfigFailed)?; diff --git a/tng/src/tunnel/provider/converter.rs b/tng/src/tunnel/provider/converter.rs index 76288823..c9c91498 100644 --- a/tng/src/tunnel/provider/converter.rs +++ b/tng/src/tunnel/provider/converter.rs @@ -20,6 +20,14 @@ impl TngConverter { Self::Ita(_) => super::provider_type::ProviderType::Ita, } } + + /// The attestation-service address this converter targets (for error context). + pub fn as_addr(&self) -> &str { + match self { + Self::Coco(c) => c.as_addr(), + Self::Ita(c) => c.as_addr(), + } + } } #[async_trait::async_trait] From b064a8439ed785e16798d954cce0fa673402967e Mon Sep 17 00:00:00 2001 From: Kun Lai Date: Fri, 10 Jul 2026 17:54:13 +0800 Subject: [PATCH 2/6] test(tng-wasm): assert clean rendering of browser fetch failures --- Cargo.lock | 1 + tng-wasm/Cargo.toml | 1 + tng-wasm/tests/fetch_error.rs | 47 +++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tng-wasm/tests/fetch_error.rs diff --git a/Cargo.lock b/Cargo.lock index c43dd08b..86563d32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7905,6 +7905,7 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "js-sys", + "reqwest 0.12.9", "serde", "serde-wasm-bindgen", "serde_json", diff --git a/tng-wasm/Cargo.toml b/tng-wasm/Cargo.toml index a733b357..e2e89343 100644 --- a/tng-wasm/Cargo.toml +++ b/tng-wasm/Cargo.toml @@ -53,6 +53,7 @@ tokio_with_wasm_proc = {workspace = true} [dev-dependencies] wasm-bindgen-test = "0.3.34" +reqwest = {workspace = true} # https://github.com/rustwasm/wasm-pack/issues/1351#issuecomment-2100231587 [package.metadata.wasm-pack.profile.dev.wasm-bindgen] diff --git a/tng-wasm/tests/fetch_error.rs b/tng-wasm/tests/fetch_error.rs new file mode 100644 index 00000000..539f00eb --- /dev/null +++ b/tng-wasm/tests/fetch_error.rs @@ -0,0 +1,47 @@ +//! Verifies that a browser fetch rejection renders cleanly (no duplicated +//! `TypeError: Failed to fetch`, no raw `JsValue(...)` wrapper) and carries the +//! browser-opacity hint. Exercises the patched reqwest fork's +//! `crate::error::wasm()` end-to-end via the real `reqwest` wasm client. + +#![cfg(target_arch = "wasm32")] + +extern crate wasm_bindgen_test; + +use std::error::Error; +use wasm_bindgen_test::*; + +// Configure tests to run in the browser environment. +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +async fn fetch_failure_renders_cleanly() { + let client = reqwest::Client::builder().build().unwrap(); + let err = client + .get("https://invalid.invalid/") + .send() + .await + .expect_err("fetch to an invalid host must fail"); + + // reqwest::Error Display is "error sending request"; its `source()` is the + // string produced by the fork's crate::error::wasm() — the link under test. + // The reqwest::Error's direct source IS the wasm() BoxError, so a single + // `.source()` call reaches it. + let source = err + .source() + .expect("reqwest error has a source") + .to_string(); + + assert!( + !source.contains("JsValue("), + "raw JsValue(...) wrapper leaked into the error:\n{source}" + ); + let occurrences = source.matches("TypeError: Failed to fetch").count(); + assert_eq!( + occurrences, 1, + "expected exactly one 'TypeError: Failed to fetch', got {occurrences}:\n{source}" + ); + assert!( + source.contains("Note: browsers do not expose"), + "missing browser-opacity hint:\n{source}" + ); +} From adf8f388427b74f8ce5e6dd651ed60ed04685156 Mon Sep 17 00:00:00 2001 From: Kun Lai Date: Mon, 13 Jul 2026 14:57:21 +0800 Subject: [PATCH 3/6] chore(wasm): drop redundant wasm-unit-test invocation wasm-unit-test now simply delegates to wasm-unit-test-chrome, removing the duplicated nightly wasm-pack test line that ran alongside it. --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index c379f192..8d412c06 100644 --- a/Makefile +++ b/Makefile @@ -292,7 +292,6 @@ wasm-pack-debug: wasm-build-debug .PHONE: wasm-unit-test wasm-unit-test: wasm-unit-test-chrome - RUSTUP_TOOLCHAIN=nightly-2025-07-07 RUSTFLAGS='--cfg getrandom_backend="wasm_js" -C target-feature=+atomics,+bulk-memory,+mutable-globals' wasm-pack test --headless --chrome ./tng-wasm -Z build-std=std,panic_abort .PHONE: wasm-unit-test-chrome wasm-unit-test-chrome: install-wasm-build-dependencies From 32fa56d687ee16a0ee52a05c9c8e29c4374e1a63 Mon Sep 17 00:00:00 2001 From: Kun Lai Date: Mon, 13 Jul 2026 14:59:22 +0800 Subject: [PATCH 4/6] fix(tng-wasm): pin wasm-bindgen-test to fix firefox "no tests to run" Downgrading wasm-bindgen-test (and the matching wasm-bindgen / web-sys / js-sys / wasm-bindgen-futures versions in Cargo.lock) to 0.3.50 fixes "no tests to run!" reported by `make wasm-unit-test-firefox`. Pin the dev-dependency with `=0.3.50` so it no longer floats up to the broken 0.3.45/+nightly combination. --- Cargo.lock | 54 +++++++++++++++++++++++++++------------------ tng-wasm/Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86563d32..407d1ba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3900,9 +3900,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -8906,25 +8906,37 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", - "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -8933,9 +8945,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8943,36 +8955,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "bumpalo", "proc-macro2", "quote", "syn 2.0.98", + "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -8980,9 +8990,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", @@ -9067,9 +9077,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/tng-wasm/Cargo.toml b/tng-wasm/Cargo.toml index e2e89343..259194c7 100644 --- a/tng-wasm/Cargo.toml +++ b/tng-wasm/Cargo.toml @@ -52,7 +52,7 @@ tokio_with_wasm = {workspace = true} tokio_with_wasm_proc = {workspace = true} [dev-dependencies] -wasm-bindgen-test = "0.3.34" +wasm-bindgen-test = "=0.3.50" reqwest = {workspace = true} # https://github.com/rustwasm/wasm-pack/issues/1351#issuecomment-2100231587 From 740c24e48c65b86db29a6fcd989615e79f6c471f Mon Sep 17 00:00:00 2001 From: Kun Lai Date: Mon, 13 Jul 2026 15:09:13 +0800 Subject: [PATCH 5/6] chore(deps): bump reqwest rev for cleaner wasm js_sys::Error printing Bumps the patched reqwest to rev 472703bfd5d3eb415bece730230a823e07dabb78, which improves the Display formatting of js_sys::Error surfaced from wasm fetch failures. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 407d1ba8..58a439b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6313,7 +6313,7 @@ dependencies = [ [[package]] name = "reqwest" version = "0.12.9" -source = "git+https://github.com/inclavare-containers/reqwest.git?rev=edd4658e698a030ff04bbb62e954f363b31a25c8#edd4658e698a030ff04bbb62e954f363b31a25c8" +source = "git+https://github.com/inclavare-containers/reqwest.git?rev=472703bfd5d3eb415bece730230a823e07dabb78#472703bfd5d3eb415bece730230a823e07dabb78" dependencies = [ "async-compression", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 8e4d40ba..0c57993f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,7 +174,7 @@ git = "https://github.com/inclavare-containers/webpki.git" [patch.crates-io.reqwest] git = "https://github.com/inclavare-containers/reqwest.git" -rev = "edd4658e698a030ff04bbb62e954f363b31a25c8" +rev = "472703bfd5d3eb415bece730230a823e07dabb78" [patch.crates-io.tokio-graceful] branch = "wasm" From 92db0f6118744354184974026094148af7bf2e0b Mon Sep 17 00:00:00 2001 From: Kun Lai Date: Mon, 13 Jul 2026 15:12:54 +0800 Subject: [PATCH 6/6] test(tng-wasm): handle firefox-specific fetch error message in render test Firefox surfaces 'TypeError: NetworkError when attempting to fetch resource.' instead of 'TypeError: Failed to fetch', so the render-cleanliness test skipped the message-count and browser-opacity-hint assertions when the Firefox wording is detected. --- tng-wasm/tests/fetch_error.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tng-wasm/tests/fetch_error.rs b/tng-wasm/tests/fetch_error.rs index 539f00eb..eb46af1a 100644 --- a/tng-wasm/tests/fetch_error.rs +++ b/tng-wasm/tests/fetch_error.rs @@ -35,13 +35,19 @@ async fn fetch_failure_renders_cleanly() { !source.contains("JsValue("), "raw JsValue(...) wrapper leaked into the error:\n{source}" ); - let occurrences = source.matches("TypeError: Failed to fetch").count(); - assert_eq!( - occurrences, 1, - "expected exactly one 'TypeError: Failed to fetch', got {occurrences}:\n{source}" - ); - assert!( - source.contains("Note: browsers do not expose"), - "missing browser-opacity hint:\n{source}" - ); + + // On firefox the error will be "TypeError: NetworkError when attempting to fetch resource.", not "TypeError: Failed to fetch" + if source.contains("TypeError: NetworkError when attempting to fetch resource.") { + // firefox, skip this part + } else { + let occurrences = source.matches("TypeError: Failed to fetch").count(); + assert_eq!( + occurrences, 1, + "expected exactly one 'TypeError: Failed to fetch', got {occurrences}:\n{source}" + ); + assert!( + source.contains("Note: browsers do not expose"), + "missing browser-opacity hint:\n{source}" + ); + } }