From 437492b41b598567da59cbf6b0c0071746d2cbd2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 11:47:00 +0900 Subject: [PATCH 01/18] test(network): require committed navigation origin binding --- ...ebdriver_bidi_navigation_origin_binding.rs | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs diff --git a/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs b/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs new file mode 100644 index 000000000..4e9530118 --- /dev/null +++ b/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs @@ -0,0 +1,176 @@ +use std::{ + error::Error, + io::{self, Read, Write}, + net::{TcpListener, TcpStream}, + thread, + time::Duration, +}; + +use originweave_core::{BrowserAuthorityRegistry, Origin, WebDriverBiDiWebSocketEndpoint}; +use originweave_network::{ + WebDriverBiDiNavigationCommittedObservation, WebDriverBiDiTcpConnectionPlan, + WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan, + WebDriverBiDiWebSocketMessageAssembler, WebDriverBiDiWebSocketMessageAssembly, + WebDriverBiDiWebSocketTextMessage, advance_and_bind_webdriver_bidi_navigation_document_origin, +}; + +const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; +const RFC6455_SAMPLE_KEY: &str = "dGhlIHNhbXBsZSBub25jZQ=="; +const OPENING_RESPONSE: &[u8] = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"; + +fn read_opening_request(stream: &mut TcpStream) -> io::Result<()> { + stream.set_read_timeout(Some(Duration::from_secs(2)))?; + let mut request = Vec::new(); + let mut buffer = [0_u8; 512]; + while !request.ends_with(b"\r\n\r\n") { + let count = stream.read(&mut buffer)?; + if count == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "client opening request ended before the header terminator", + )); + } + request.extend_from_slice(&buffer[..count]); + } + Ok(()) +} + +fn write_unmasked_text_frame(stream: &mut TcpStream, payload: &[u8]) -> io::Result<()> { + stream.write_all(&[0x81])?; + match payload.len() { + 0..=125 => stream.write_all(&[payload.len() as u8])?, + 126..=65_535 => { + stream.write_all(&[126])?; + stream.write_all(&(payload.len() as u16).to_be_bytes())?; + } + _ => { + stream.write_all(&[127])?; + stream.write_all(&(payload.len() as u64).to_be_bytes())?; + } + } + stream.write_all(payload) +} + +fn receive_navigation_event(url: &str) -> Result> { + let listener = TcpListener::bind(("127.0.0.1", 0))?; + let local_addr = listener.local_addr()?; + let payload = format!( + "{{\"type\":\"event\",\"method\":\"browsingContext.navigationCommitted\",\"params\":{{\"context\":\"context-a\",\"navigation\":\"navigation-a\",\"timestamp\":17,\"url\":\"{url}\"}}}}" + ) + .into_bytes(); + let server = thread::spawn(move || -> io::Result<()> { + let (mut stream, _) = listener.accept()?; + read_opening_request(&mut stream)?; + stream.write_all(OPENING_RESPONSE)?; + write_unmasked_text_frame(&mut stream, &payload) + }); + + let endpoint = format!("ws://{local_addr}/session/{SESSION_ID}"); + let target = WebDriverBiDiWebSocketEndpoint::new(&endpoint)? + .correlate_session_id(SESSION_ID)? + .into_explicit_connect_target()?; + let connection = + WebDriverBiDiTcpConnectionPlan::new(target, Duration::from_secs(1), 1)?.connect()?; + let key = WebDriverBiDiWebSocketClientKey::new(RFC6455_SAMPLE_KEY)?; + let established = WebDriverBiDiWebSocketHandshakePlan::new(connection, key)? + .write_opening_request(Duration::from_millis(500))? + .read_opening_response(Duration::from_millis(500))?; + let (_established, frame) = established.read_frame(Duration::from_millis(500))?; + let mut assembler = WebDriverBiDiWebSocketMessageAssembler::new(); + let text = match assembler.push_frame(frame)? { + WebDriverBiDiWebSocketMessageAssembly::Text(text) => text, + other => { + return Err(io::Error::other(format!( + "navigation origin-binding event produced unexpected assembly state: {other:?}" + )) + .into()); + } + }; + server + .join() + .map_err(|_| io::Error::other("navigation origin-binding server panicked"))??; + Ok(text) +} + +fn fixture_origin(value: &str) -> Result> { + Origin::parse(value) + .map_err(|error| io::Error::other(format!("fixture origin parse failed: {error:?}" )).into()) +} + +#[test] +fn committed_navigation_rotates_document_and_binds_canonical_observed_origin() +-> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let session = registry.register_session(SESSION_ID)?; + let context = registry.register_context(session, "context-a")?; + let before = registry.current_context_epoch(session, context)?; + let previous_origin = fixture_origin("https://before.example")?; + registry.bind_context_origin(session, context, &previous_origin)?; + + let observed_url = "https://EXAMPLE.TEST:443/after?from=originweave#done"; + let event = receive_navigation_event(observed_url)?; + let observation = WebDriverBiDiNavigationCommittedObservation::parse_and_match( + &event, + ®istry, + session, + context, + observed_url, + )?; + let binding = advance_and_bind_webdriver_bidi_navigation_document_origin( + observation, + &mut registry, + before, + )?; + + let expected_origin = fixture_origin("https://example.test")?; + assert_eq!(binding.browser_session(), session); + assert_eq!(binding.browsing_context(), context); + assert_eq!(binding.previous_epoch(), before); + assert_ne!(binding.current_epoch(), before); + assert_eq!(binding.origin(), &expected_origin); + assert_eq!( + registry.require_context_origin(session, context, &expected_origin)?, + binding.current_epoch() + ); + Ok(()) +} + +#[test] +fn invalid_observed_origin_fails_before_document_authority_is_rotated() +-> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let session = registry.register_session(SESSION_ID)?; + let context = registry.register_context(session, "context-a")?; + let before = registry.current_context_epoch(session, context)?; + let previous_origin = fixture_origin("https://before.example")?; + registry.bind_context_origin(session, context, &previous_origin)?; + + let observed_url = "https://user@example.test/after"; + let event = receive_navigation_event(observed_url)?; + let observation = WebDriverBiDiNavigationCommittedObservation::parse_and_match( + &event, + ®istry, + session, + context, + observed_url, + )?; + let error = advance_and_bind_webdriver_bidi_navigation_document_origin( + observation, + &mut registry, + before, + ) + .err() + .ok_or_else(|| io::Error::other("credential-bearing observed URL unexpectedly bound origin"))?; + + assert_eq!( + error.to_string(), + "WebDriver BiDi committed navigation URL cannot enter canonical origin authority" + ); + assert!(error.source().is_none()); + assert_eq!(registry.current_context_epoch(session, context)?, before); + assert_eq!( + registry.require_context_origin(session, context, &previous_origin)?, + before + ); + Ok(()) +} From fcf6206bc8f22947e87c1008cbef3f0dc83f3529 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 11:49:49 +0900 Subject: [PATCH 02/18] test(network): apply canonical origin-binding test formatting --- .../tests/webdriver_bidi_navigation_origin_binding.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs b/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs index 4e9530118..e25c06fc7 100644 --- a/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs +++ b/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs @@ -51,7 +51,9 @@ fn write_unmasked_text_frame(stream: &mut TcpStream, payload: &[u8]) -> io::Resu stream.write_all(payload) } -fn receive_navigation_event(url: &str) -> Result> { +fn receive_navigation_event( + url: &str, +) -> Result> { let listener = TcpListener::bind(("127.0.0.1", 0))?; let local_addr = listener.local_addr()?; let payload = format!( @@ -94,7 +96,7 @@ fn receive_navigation_event(url: &str) -> Result Result> { Origin::parse(value) - .map_err(|error| io::Error::other(format!("fixture origin parse failed: {error:?}" )).into()) + .map_err(|error| io::Error::other(format!("fixture origin parse failed: {error:?}")).into()) } #[test] @@ -136,8 +138,8 @@ fn committed_navigation_rotates_document_and_binds_canonical_observed_origin() } #[test] -fn invalid_observed_origin_fails_before_document_authority_is_rotated() --> Result<(), Box> { +fn invalid_observed_origin_fails_before_document_authority_is_rotated() -> Result<(), Box> +{ let mut registry = BrowserAuthorityRegistry::new(); let session = registry.register_session(SESSION_ID)?; let context = registry.register_context(session, "context-a")?; From f9b787e3e687a67bb87d66ebeebe02f33feca602 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:03:12 +0900 Subject: [PATCH 03/18] feat(network): bind committed navigation origin --- ...bdriver_bidi_navigation_document_origin.rs | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs new file mode 100644 index 000000000..bbf8b1825 --- /dev/null +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -0,0 +1,245 @@ +use std::{error::Error, fmt}; + +use originweave_core::{ + BrowserAuthorityRegistry, BrowserRegistryError, BrowserSessionId, BrowsingContextId, + DocumentEpoch, Origin, +}; + +use crate::{ + WebDriverBiDiNavigationCommittedDocumentAdvance, + WebDriverBiDiNavigationCommittedDocumentAdvanceError, + WebDriverBiDiNavigationCommittedObservation, advance_webdriver_bidi_navigation_document_epoch, +}; + +/// Immutable evidence that one accepted navigation rotated its document and bound its observed origin. +/// +/// The value records only the registry-local session/context/document transition and the canonical +/// origin derived from the exact serialized URL carried by the accepted WebDriver BiDi navigation +/// observation. It does not authenticate the browser adapter, prove which action caused the +/// navigation, authorize the destination, or grant browser, policy, node, credential, process, or +/// reusable Agent authority. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WebDriverBiDiNavigationCommittedDocumentOrigin { + browser_session: BrowserSessionId, + browsing_context: BrowsingContextId, + previous_epoch: DocumentEpoch, + current_epoch: DocumentEpoch, + origin: Origin, +} + +impl WebDriverBiDiNavigationCommittedDocumentOrigin { + /// Return the exact OriginWeave browser session whose context advanced. + #[must_use] + pub const fn browser_session(&self) -> BrowserSessionId { + self.browser_session + } + + /// Return the exact OriginWeave browsing context whose document advanced. + #[must_use] + pub const fn browsing_context(&self) -> BrowsingContextId { + self.browsing_context + } + + /// Return the caller-captured document epoch that was current before the navigation advance. + #[must_use] + pub const fn previous_epoch(&self) -> DocumentEpoch { + self.previous_epoch + } + + /// Return the new document epoch to which the observed canonical origin was bound. + #[must_use] + pub const fn current_epoch(&self) -> DocumentEpoch { + self.current_epoch + } + + /// Borrow the canonical OriginWeave origin derived from the accepted serialized navigation URL. + #[must_use] + pub const fn origin(&self) -> &Origin { + &self.origin + } +} + +/// Fail-closed failures while rotating document authority and binding the observed navigation origin. +#[derive(Debug)] +pub enum WebDriverBiDiNavigationCommittedDocumentOriginError { + /// The accepted serialized navigation URL could not enter canonical OriginWeave origin authority. + InvalidObservedOrigin, + /// The accepted navigation could not rotate the exact caller-captured document epoch. + DocumentAdvance { + /// Underlying typed document-advance failure. + source: WebDriverBiDiNavigationCommittedDocumentAdvanceError, + }, + /// The canonical observed origin could not bind to the newly advanced registered document. + RegistryState { + /// Underlying browser-registry authority failure. + source: BrowserRegistryError, + }, +} + +impl fmt::Display for WebDriverBiDiNavigationCommittedDocumentOriginError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidObservedOrigin => formatter.write_str( + "WebDriver BiDi committed navigation URL cannot enter canonical origin authority", + ), + Self::DocumentAdvance { .. } => formatter.write_str( + "WebDriver BiDi committed navigation cannot rotate registered document authority", + ), + Self::RegistryState { .. } => formatter.write_str( + "WebDriver BiDi committed navigation origin cannot bind registered document authority", + ), + } + } +} + +impl Error for WebDriverBiDiNavigationCommittedDocumentOriginError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::InvalidObservedOrigin => None, + Self::DocumentAdvance { source } => Some(source), + Self::RegistryState { source } => Some(source), + } + } +} + +fn origin_from_serialized_navigation_url(serialized_url: &str) -> Option { + let (scheme, remainder) = serialized_url.split_once("://")?; + let authority_end = remainder + .find(|character| matches!(character, '/' | '?' | '#')) + .unwrap_or(remainder.len()); + let authority = &remainder[..authority_end]; + Origin::parse(&format!("{scheme}://{authority}")).ok() +} + +fn bind_advanced_document_origin( + registry: &mut BrowserAuthorityRegistry, + advance: &WebDriverBiDiNavigationCommittedDocumentAdvance, + origin: &Origin, +) -> Result { + registry + .bind_context_origin( + advance.browser_session(), + advance.browsing_context(), + origin, + ) + .map_err( + |source| WebDriverBiDiNavigationCommittedDocumentOriginError::RegistryState { source }, + ) +} + +/// Consume one accepted navigation, rotate the exact expected document, and bind its canonical origin. +/// +/// Origin derivation is completed before any registry mutation. Only serialized HTTP(S) URLs whose +/// authority can enter [`Origin`] are accepted; credential-bearing, opaque, malformed, insecure +/// remote HTTP, and otherwise unsupported authorities therefore fail before document rotation. +/// The accepted observation is then consumed by the existing exact-epoch document-advance boundary, +/// which clears stale origin and node authority. Finally, the derived canonical origin is bound to +/// that newly advanced document through the canonical browser registry lifecycle. +/// +/// The caller must still treat the returned value as immediate-use registry evidence rather than as +/// proof of action causality, browser authenticity, destination authorization, or reusable authority. +pub fn advance_and_bind_webdriver_bidi_navigation_document_origin( + observation: WebDriverBiDiNavigationCommittedObservation, + registry: &mut BrowserAuthorityRegistry, + expected_previous_epoch: DocumentEpoch, +) -> Result< + WebDriverBiDiNavigationCommittedDocumentOrigin, + WebDriverBiDiNavigationCommittedDocumentOriginError, +> { + let origin = origin_from_serialized_navigation_url(observation.url()) + .ok_or(WebDriverBiDiNavigationCommittedDocumentOriginError::InvalidObservedOrigin)?; + let advance = advance_webdriver_bidi_navigation_document_epoch( + observation, + registry, + expected_previous_epoch, + ) + .map_err(|source| WebDriverBiDiNavigationCommittedDocumentOriginError::DocumentAdvance { + source, + })?; + bind_advanced_document_origin(registry, &advance, &origin).map(|current_epoch| { + WebDriverBiDiNavigationCommittedDocumentOrigin { + browser_session: advance.browser_session(), + browsing_context: advance.browsing_context(), + previous_epoch: advance.previous_epoch(), + current_epoch, + origin, + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn browser_sessions(value: u64) -> Vec { + BrowserSessionId::new(value).into_iter().collect() + } + + fn browsing_contexts(value: u64) -> Vec { + BrowsingContextId::new(value).into_iter().collect() + } + + #[test] + fn serialized_navigation_origin_is_canonical_and_rejects_non_authority_urls() { + let canonical = origin_from_serialized_navigation_url( + "HTTPS://EXAMPLE.TEST:443/path?query=value#fragment", + ); + assert_eq!(canonical.as_ref().map(Origin::as_str), Some("https://example.test")); + assert!(origin_from_serialized_navigation_url("https://user@example.test/path").is_none()); + assert!(origin_from_serialized_navigation_url("data:text/plain,originweave").is_none()); + assert!(origin_from_serialized_navigation_url("http://example.test/path").is_none()); + assert_eq!( + origin_from_serialized_navigation_url("http://127.0.0.1:8080/path") + .as_ref() + .map(Origin::as_str), + Some("http://127.0.0.1:8080") + ); + } + + #[test] + fn binding_helper_preserves_registry_failure_source() { + let mut registry = BrowserAuthorityRegistry::new(); + let sessions = browser_sessions(1); + let contexts = browsing_contexts(1); + assert_eq!(sessions.len(), 1); + assert_eq!(contexts.len(), 1); + let advance = WebDriverBiDiNavigationCommittedDocumentAdvance { + browser_session: sessions[0], + browsing_context: contexts[0], + previous_epoch: DocumentEpoch::new(1).expect("one is a valid document epoch"), + current_epoch: DocumentEpoch::new(2).expect("two is a valid document epoch"), + }; + let origin = Origin::parse("https://example.test").expect("fixture origin is valid"); + let error = bind_advanced_document_origin(&mut registry, &advance, &origin) + .expect_err("unknown registry session must fail closed"); + assert_eq!( + error.to_string(), + "WebDriver BiDi committed navigation origin cannot bind registered document authority" + ); + assert_eq!( + error + .source() + .and_then(|source| source.downcast_ref::()), + Some(&BrowserRegistryError::UnknownBrowserSession) + ); + } + + #[test] + fn public_diagnostics_preserve_only_causal_sources() { + let invalid = WebDriverBiDiNavigationCommittedDocumentOriginError::InvalidObservedOrigin; + assert_eq!( + invalid.to_string(), + "WebDriver BiDi committed navigation URL cannot enter canonical origin authority" + ); + assert!(invalid.source().is_none()); + + let advance = WebDriverBiDiNavigationCommittedDocumentOriginError::DocumentAdvance { + source: WebDriverBiDiNavigationCommittedDocumentAdvanceError::UnexpectedDocumentEpoch, + }; + assert_eq!( + advance.to_string(), + "WebDriver BiDi committed navigation cannot rotate registered document authority" + ); + assert!(advance.source().is_some()); + } +} From eaffe684d93d2ac193be5dad84bf5b00ca435d66 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:03:44 +0900 Subject: [PATCH 04/18] test(network): cover origin binding registry failures --- ...bdriver_bidi_navigation_document_origin.rs | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs index bbf8b1825..440e55b26 100644 --- a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -6,7 +6,6 @@ use originweave_core::{ }; use crate::{ - WebDriverBiDiNavigationCommittedDocumentAdvance, WebDriverBiDiNavigationCommittedDocumentAdvanceError, WebDriverBiDiNavigationCommittedObservation, advance_webdriver_bidi_navigation_document_epoch, }; @@ -113,15 +112,12 @@ fn origin_from_serialized_navigation_url(serialized_url: &str) -> Option fn bind_advanced_document_origin( registry: &mut BrowserAuthorityRegistry, - advance: &WebDriverBiDiNavigationCommittedDocumentAdvance, + browser_session: BrowserSessionId, + browsing_context: BrowsingContextId, origin: &Origin, ) -> Result { registry - .bind_context_origin( - advance.browser_session(), - advance.browsing_context(), - origin, - ) + .bind_context_origin(browser_session, browsing_context, origin) .map_err( |source| WebDriverBiDiNavigationCommittedDocumentOriginError::RegistryState { source }, ) @@ -156,14 +152,18 @@ pub fn advance_and_bind_webdriver_bidi_navigation_document_origin( .map_err(|source| WebDriverBiDiNavigationCommittedDocumentOriginError::DocumentAdvance { source, })?; - bind_advanced_document_origin(registry, &advance, &origin).map(|current_epoch| { - WebDriverBiDiNavigationCommittedDocumentOrigin { - browser_session: advance.browser_session(), - browsing_context: advance.browsing_context(), - previous_epoch: advance.previous_epoch(), - current_epoch, - origin, - } + bind_advanced_document_origin( + registry, + advance.browser_session(), + advance.browsing_context(), + &origin, + ) + .map(|current_epoch| WebDriverBiDiNavigationCommittedDocumentOrigin { + browser_session: advance.browser_session(), + browsing_context: advance.browsing_context(), + previous_epoch: advance.previous_epoch(), + current_epoch, + origin, }) } @@ -184,7 +184,10 @@ mod tests { let canonical = origin_from_serialized_navigation_url( "HTTPS://EXAMPLE.TEST:443/path?query=value#fragment", ); - assert_eq!(canonical.as_ref().map(Origin::as_str), Some("https://example.test")); + assert_eq!( + canonical.as_ref().map(Origin::as_str), + Some("https://example.test") + ); assert!(origin_from_serialized_navigation_url("https://user@example.test/path").is_none()); assert!(origin_from_serialized_navigation_url("data:text/plain,originweave").is_none()); assert!(origin_from_serialized_navigation_url("http://example.test/path").is_none()); @@ -203,15 +206,14 @@ mod tests { let contexts = browsing_contexts(1); assert_eq!(sessions.len(), 1); assert_eq!(contexts.len(), 1); - let advance = WebDriverBiDiNavigationCommittedDocumentAdvance { - browser_session: sessions[0], - browsing_context: contexts[0], - previous_epoch: DocumentEpoch::new(1).expect("one is a valid document epoch"), - current_epoch: DocumentEpoch::new(2).expect("two is a valid document epoch"), - }; let origin = Origin::parse("https://example.test").expect("fixture origin is valid"); - let error = bind_advanced_document_origin(&mut registry, &advance, &origin) - .expect_err("unknown registry session must fail closed"); + let error = bind_advanced_document_origin( + &mut registry, + sessions[0], + contexts[0], + &origin, + ) + .expect_err("unknown registry session must fail closed"); assert_eq!( error.to_string(), "WebDriver BiDi committed navigation origin cannot bind registered document authority" From 6fed822e2505bead273864211efcfd08db738196 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:04:08 +0900 Subject: [PATCH 05/18] feat(network): export committed navigation origin binding --- crates/originweave-network/src/lib.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/originweave-network/src/lib.rs b/crates/originweave-network/src/lib.rs index 07284c411..1e94f6075 100644 --- a/crates/originweave-network/src/lib.rs +++ b/crates/originweave-network/src/lib.rs @@ -12,12 +12,13 @@ //! correlated protocol acknowledgment, admits a bounded navigation-committed //! post-condition observation for one exact registered context and URL, rotates //! the matched context's document epoch only from an exact caller-captured -//! pre-action epoch, sends narrowly typed `session.status` and `session.end` -//! commands, admits typed correlated status and end responses, observes bounded -//! peer Close or clean-EOF transport cessation, and keeps protocol/transport -//! evidence separate from explicit operational teardown observations without -//! exposing generic JSON bodies or granting browser, TLS, policy, secret, -//! process, profile, or Agent authority. +//! pre-action epoch, derives and binds the committed HTTP(S) URL's canonical +//! origin to that newly advanced document, sends narrowly typed `session.status` +//! and `session.end` commands, admits typed correlated status and end responses, +//! observes bounded peer Close or clean-EOF transport cessation, and keeps +//! protocol/transport evidence separate from explicit operational teardown +//! observations without exposing generic JSON bodies or granting browser, TLS, +//! policy, secret, process, profile, or Agent authority. #![forbid(unsafe_code)] #![deny(missing_docs)] @@ -28,6 +29,7 @@ mod webdriver_bidi_connection; mod webdriver_bidi_json_envelope; mod webdriver_bidi_navigation_committed_postcondition; mod webdriver_bidi_navigation_document_advance; +mod webdriver_bidi_navigation_document_origin; mod webdriver_bidi_pointer_click_response; mod webdriver_bidi_pointer_click_transport; mod webdriver_bidi_session_end_command; @@ -72,6 +74,11 @@ pub use webdriver_bidi_navigation_document_advance::{ WebDriverBiDiNavigationCommittedDocumentAdvanceError, advance_webdriver_bidi_navigation_document_epoch, }; +pub use webdriver_bidi_navigation_document_origin::{ + WebDriverBiDiNavigationCommittedDocumentOrigin, + WebDriverBiDiNavigationCommittedDocumentOriginError, + advance_and_bind_webdriver_bidi_navigation_document_origin, +}; pub use webdriver_bidi_pointer_click_response::{ WebDriverBiDiPointerClickResponseError, WebDriverBiDiPointerClickResult, }; From 43a1ee5ce514d9267cab450880306974af323c3a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:06:30 +0900 Subject: [PATCH 06/18] fix(network): apply canonical Rust formatting --- ...bdriver_bidi_navigation_document_origin.rs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs index 440e55b26..6d2efb263 100644 --- a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -149,8 +149,8 @@ pub fn advance_and_bind_webdriver_bidi_navigation_document_origin( registry, expected_previous_epoch, ) - .map_err(|source| WebDriverBiDiNavigationCommittedDocumentOriginError::DocumentAdvance { - source, + .map_err(|source| { + WebDriverBiDiNavigationCommittedDocumentOriginError::DocumentAdvance { source } })?; bind_advanced_document_origin( registry, @@ -158,13 +158,15 @@ pub fn advance_and_bind_webdriver_bidi_navigation_document_origin( advance.browsing_context(), &origin, ) - .map(|current_epoch| WebDriverBiDiNavigationCommittedDocumentOrigin { - browser_session: advance.browser_session(), - browsing_context: advance.browsing_context(), - previous_epoch: advance.previous_epoch(), - current_epoch, - origin, - }) + .map( + |current_epoch| WebDriverBiDiNavigationCommittedDocumentOrigin { + browser_session: advance.browser_session(), + browsing_context: advance.browsing_context(), + previous_epoch: advance.previous_epoch(), + current_epoch, + origin, + }, + ) } #[cfg(test)] @@ -207,13 +209,8 @@ mod tests { assert_eq!(sessions.len(), 1); assert_eq!(contexts.len(), 1); let origin = Origin::parse("https://example.test").expect("fixture origin is valid"); - let error = bind_advanced_document_origin( - &mut registry, - sessions[0], - contexts[0], - &origin, - ) - .expect_err("unknown registry session must fail closed"); + let error = bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origin) + .expect_err("unknown registry session must fail closed"); assert_eq!( error.to_string(), "WebDriver BiDi committed navigation origin cannot bind registered document authority" From a088ed1556880351486a2f122087b9337bd50513 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:09:05 +0900 Subject: [PATCH 07/18] fix(network): satisfy strict origin binding lint contract --- ...bdriver_bidi_navigation_document_origin.rs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs index 6d2efb263..2135f8608 100644 --- a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -103,9 +103,7 @@ impl Error for WebDriverBiDiNavigationCommittedDocumentOriginError { fn origin_from_serialized_navigation_url(serialized_url: &str) -> Option { let (scheme, remainder) = serialized_url.split_once("://")?; - let authority_end = remainder - .find(|character| matches!(character, '/' | '?' | '#')) - .unwrap_or(remainder.len()); + let authority_end = remainder.find(['/', '?', '#']).unwrap_or(remainder.len()); let authority = &remainder[..authority_end]; Origin::parse(&format!("{scheme}://{authority}")).ok() } @@ -208,9 +206,21 @@ mod tests { let contexts = browsing_contexts(1); assert_eq!(sessions.len(), 1); assert_eq!(contexts.len(), 1); - let origin = Origin::parse("https://example.test").expect("fixture origin is valid"); - let error = bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origin) - .expect_err("unknown registry session must fail closed"); + + let origin = origin_from_serialized_navigation_url("https://example.test"); + assert_eq!( + origin.as_ref().map(Origin::as_str), + Some("https://example.test") + ); + let Some(origin) = origin else { + return; + }; + + let result = bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origin); + assert!(result.is_err()); + let Err(error) = result else { + return; + }; assert_eq!( error.to_string(), "WebDriver BiDi committed navigation origin cannot bind registered document authority" From fc739e712c3c157befea1d4f1981ab50f6611507 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 12:11:06 +0900 Subject: [PATCH 08/18] fix(network): apply exact Rust formatting --- .../src/webdriver_bidi_navigation_document_origin.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs index 2135f8608..903f95443 100644 --- a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -216,7 +216,8 @@ mod tests { return; }; - let result = bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origin); + let result = + bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origin); assert!(result.is_err()); let Err(error) = result else { return; From 0869f537d21dca1795860a902f4204b0231529cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 13:05:28 +0900 Subject: [PATCH 09/18] test(network): cover stale navigation origin advance --- ...ebdriver_bidi_navigation_origin_binding.rs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs b/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs index e25c06fc7..6ac899ca5 100644 --- a/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs +++ b/crates/originweave-network/tests/webdriver_bidi_navigation_origin_binding.rs @@ -8,6 +8,7 @@ use std::{ use originweave_core::{BrowserAuthorityRegistry, Origin, WebDriverBiDiWebSocketEndpoint}; use originweave_network::{ + WebDriverBiDiNavigationCommittedDocumentAdvanceError, WebDriverBiDiNavigationCommittedObservation, WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMessageAssembler, WebDriverBiDiWebSocketMessageAssembly, @@ -176,3 +177,61 @@ fn invalid_observed_origin_fails_before_document_authority_is_rotated() -> Resul ); Ok(()) } + +#[test] +fn stale_pre_action_epoch_fails_before_observed_origin_is_bound() -> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let session = registry.register_session(SESSION_ID)?; + let context = registry.register_context(session, "context-a")?; + let before = registry.current_context_epoch(session, context)?; + let previous_origin = fixture_origin("https://before.example")?; + registry.bind_context_origin(session, context, &previous_origin)?; + + let observed_url = "https://example.test/after"; + let event = receive_navigation_event(observed_url)?; + let observation = WebDriverBiDiNavigationCommittedObservation::parse_and_match( + &event, + ®istry, + session, + context, + observed_url, + )?; + + let intervening_epoch = registry.advance_document(context)?; + let intervening_origin = fixture_origin("https://intervening.example")?; + registry.bind_context_origin(session, context, &intervening_origin)?; + + let error = advance_and_bind_webdriver_bidi_navigation_document_origin( + observation, + &mut registry, + before, + ) + .err() + .ok_or_else(|| io::Error::other("stale pre-action epoch unexpectedly advanced document"))?; + + assert_eq!( + error.to_string(), + "WebDriver BiDi committed navigation cannot rotate registered document authority" + ); + assert_eq!( + error + .source() + .and_then(|source| { + source.downcast_ref::() + }) + .map(ToString::to_string) + .as_deref(), + Some( + "WebDriver BiDi navigation document advance does not match the expected pre-action document epoch" + ) + ); + assert_eq!( + registry.current_context_epoch(session, context)?, + intervening_epoch + ); + assert_eq!( + registry.require_context_origin(session, context, &intervening_origin)?, + intervening_epoch + ); + Ok(()) +} From b9d584d38beddc07bb63cecf195a8f49e00789dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 13:05:59 +0900 Subject: [PATCH 10/18] test(network): remove synthetic coverage fallbacks --- ...bdriver_bidi_navigation_document_origin.rs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs index 903f95443..6985c980a 100644 --- a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -207,27 +207,28 @@ mod tests { assert_eq!(sessions.len(), 1); assert_eq!(contexts.len(), 1); - let origin = origin_from_serialized_navigation_url("https://example.test"); - assert_eq!( - origin.as_ref().map(Origin::as_str), - Some("https://example.test") - ); - let Some(origin) = origin else { - return; - }; + let origins = origin_from_serialized_navigation_url("https://example.test") + .into_iter() + .collect::>(); + assert_eq!(origins.len(), 1); + assert_eq!(origins[0].as_str(), "https://example.test"); - let result = - bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origin); - assert!(result.is_err()); - let Err(error) = result else { - return; - }; + let errors = bind_advanced_document_origin( + &mut registry, + sessions[0], + contexts[0], + &origins[0], + ) + .err() + .into_iter() + .collect::>(); + assert_eq!(errors.len(), 1); assert_eq!( - error.to_string(), + errors[0].to_string(), "WebDriver BiDi committed navigation origin cannot bind registered document authority" ); assert_eq!( - error + errors[0] .source() .and_then(|source| source.downcast_ref::()), Some(&BrowserRegistryError::UnknownBrowserSession) From f10ba9e2b2802bd4cde0735c4fd58403ffd17bd0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 13:07:46 +0900 Subject: [PATCH 11/18] style(network): apply canonical rustfmt diagnostics --- .../webdriver_bidi_navigation_document_origin.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs index 6985c980a..1ef7a6fcb 100644 --- a/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs +++ b/crates/originweave-network/src/webdriver_bidi_navigation_document_origin.rs @@ -213,15 +213,11 @@ mod tests { assert_eq!(origins.len(), 1); assert_eq!(origins[0].as_str(), "https://example.test"); - let errors = bind_advanced_document_origin( - &mut registry, - sessions[0], - contexts[0], - &origins[0], - ) - .err() - .into_iter() - .collect::>(); + let errors = + bind_advanced_document_origin(&mut registry, sessions[0], contexts[0], &origins[0]) + .err() + .into_iter() + .collect::>(); assert_eq!(errors.len(), 1); assert_eq!( errors[0].to_string(), From 953669fda0db71c351f43703a5d580f9d96c98da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 16:05:58 +0900 Subject: [PATCH 12/18] fix(network): repair exact-head correlation contract checks --- .../webdriver_bidi_json_envelope_public_boundary_tests.rs | 7 ++++--- .../src/webdriver_bidi_pointer_click_response.rs | 5 ++--- .../src/webdriver_bidi_pointer_click_transport.rs | 4 ++-- .../src/webdriver_bidi_session_status_response.rs | 8 +++++--- .../tests/webdriver_bidi_pointer_click_send_failures.rs | 8 ++++---- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs index 4c78ccac9..be43af506 100644 --- a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs +++ b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs @@ -9,7 +9,8 @@ use std::{ use originweave_core::{BrowserAuthorityRegistry, WebDriverBiDiWebSocketEndpoint}; use crate::{ - WebDriverBiDiCommandCorrelation, WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError, + WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandKind, WebDriverBiDiJsonEnvelope, + WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind, WebDriverBiDiNavigationCommittedObservation, WebDriverBiDiNavigationCommittedObservationError, WebDriverBiDiSessionStatusResponseError, WebDriverBiDiSessionStatusResult, WebDriverBiDiTcpConnectionPlan, @@ -139,7 +140,7 @@ fn public_json_envelope_unit_build_covers_fail_closed_json_edges() -> Result<(), br#"{"type":"success","id":1,"result":[1 2]}"#, br##"{"type":"success","id":1,"result":{"bad":"\"##, br#"{"type":"success","id":1,"result":{"bad":"\ud800\0041"}}"#, - br##"{"type":"success","id":1,"result":{"bad":"\ud800\u"##, + br##"{"type":"success","id":1,"result":{"bad":"\ud800\u"#", ]; for document in malformed_documents { @@ -155,7 +156,7 @@ fn public_json_envelope_unit_build_covers_fail_closed_json_edges() -> Result<(), fn public_session_status_empty_result_fails_closed_from_unit_build() -> Result<(), Box> { let text = read_text_over_loopback(EMPTY_STATUS_RESULT)?; let mut correlation = WebDriverBiDiCommandCorrelation::new(); - correlation.register_command(7)?; + correlation.register_command_for(7, WebDriverBiDiCommandKind::SessionStatus)?; let parsed = WebDriverBiDiSessionStatusResult::parse_and_correlate(&text, &mut correlation); assert!(matches!( diff --git a/crates/originweave-network/src/webdriver_bidi_pointer_click_response.rs b/crates/originweave-network/src/webdriver_bidi_pointer_click_response.rs index f251af400..980388968 100644 --- a/crates/originweave-network/src/webdriver_bidi_pointer_click_response.rs +++ b/crates/originweave-network/src/webdriver_bidi_pointer_click_response.rs @@ -2,9 +2,8 @@ use std::{error::Error, fmt}; use crate::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError, - WebDriverBiDiCommandKind, WebDriverBiDiCorrelatedResponseOutcome, - WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError, - WebDriverBiDiWebSocketTextMessage, + WebDriverBiDiCommandKind, WebDriverBiDiCorrelatedResponseOutcome, WebDriverBiDiJsonEnvelope, + WebDriverBiDiJsonEnvelopeError, WebDriverBiDiWebSocketTextMessage, }; /// Typed protocol acknowledgment for one correlated WebDriver BiDi `input.performActions` diff --git a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs index 429e76127..1c74717af 100644 --- a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs +++ b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs @@ -4,8 +4,8 @@ use originweave_core::WebDriverBiDiPointerClickCommand; use crate::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError, - WebDriverBiDiCommandKind, WebDriverBiDiWebSocketEstablished, - WebDriverBiDiWebSocketFrameError, WebDriverBiDiWebSocketMaskKey, + WebDriverBiDiCommandKind, WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrameError, + WebDriverBiDiWebSocketMaskKey, }; /// Fail-closed errors while transporting one already validated pointer-click command. diff --git a/crates/originweave-network/src/webdriver_bidi_session_status_response.rs b/crates/originweave-network/src/webdriver_bidi_session_status_response.rs index e08a7efbb..b1e5596ba 100644 --- a/crates/originweave-network/src/webdriver_bidi_session_status_response.rs +++ b/crates/originweave-network/src/webdriver_bidi_session_status_response.rs @@ -71,9 +71,11 @@ impl WebDriverBiDiSessionStatusResult { retain_validated_error_code(envelope.error_code()).and_then(|error_code| { let completed = correlation .correlate_response_for(&envelope, WebDriverBiDiCommandKind::SessionStatus) - .map_err(|source| { - WebDriverBiDiSessionStatusResponseError::Correlation { source } - })?; + .map_err( + |source| WebDriverBiDiSessionStatusResponseError::Correlation { + source, + }, + )?; Err( WebDriverBiDiSessionStatusResponseError::RemoteProtocolError { command_id: completed.command_id(), diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs index 9d4a0ee38..c2d9cffcc 100644 --- a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs @@ -12,10 +12,10 @@ use originweave_core::{ }; use originweave_network::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError, - WebDriverBiDiCommandKind, WebDriverBiDiPointerClickSendError, - WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, - WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketHandshakePlan, - WebDriverBiDiWebSocketMaskKey, send_webdriver_bidi_pointer_click, + WebDriverBiDiCommandKind, WebDriverBiDiPointerClickSendError, WebDriverBiDiTcpConnectionPlan, + WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketEstablished, + WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMaskKey, + send_webdriver_bidi_pointer_click, }; const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; From 9769733a2dee21cd0d9be5e020be7a998a4168a3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 16:11:08 +0900 Subject: [PATCH 13/18] fix(network): restore malformed JSON regression literal --- .../src/webdriver_bidi_json_envelope_public_boundary_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs index be43af506..4abf15803 100644 --- a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs +++ b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs @@ -140,7 +140,7 @@ fn public_json_envelope_unit_build_covers_fail_closed_json_edges() -> Result<(), br#"{"type":"success","id":1,"result":[1 2]}"#, br##"{"type":"success","id":1,"result":{"bad":"\"##, br#"{"type":"success","id":1,"result":{"bad":"\ud800\0041"}}"#, - br##"{"type":"success","id":1,"result":{"bad":"\ud800\u"#", + br##"{"type":"success","id":1,"result":{"bad":"\ud800\u"##, ]; for document in malformed_documents { From f9d29180bd0f8649ea278567270d8c35efeb2137 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:06:34 +0900 Subject: [PATCH 14/18] style(network): apply hosted rustfmt diagnostics --- ...iver_bidi_json_envelope_public_boundary_tests.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs index 4abf15803..c5dcc9712 100644 --- a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs +++ b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs @@ -10,13 +10,12 @@ use originweave_core::{BrowserAuthorityRegistry, WebDriverBiDiWebSocketEndpoint} use crate::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandKind, WebDriverBiDiJsonEnvelope, - WebDriverBiDiJsonEnvelopeError, - WebDriverBiDiJsonEnvelopeKind, WebDriverBiDiNavigationCommittedObservation, - WebDriverBiDiNavigationCommittedObservationError, WebDriverBiDiSessionStatusResponseError, - WebDriverBiDiSessionStatusResult, WebDriverBiDiTcpConnectionPlan, - WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan, - WebDriverBiDiWebSocketMessageAssembler, WebDriverBiDiWebSocketMessageAssembly, - WebDriverBiDiWebSocketTextMessage, + WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind, + WebDriverBiDiNavigationCommittedObservation, WebDriverBiDiNavigationCommittedObservationError, + WebDriverBiDiSessionStatusResponseError, WebDriverBiDiSessionStatusResult, + WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, + WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMessageAssembler, + WebDriverBiDiWebSocketMessageAssembly, WebDriverBiDiWebSocketTextMessage, }; const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; From 127e02503e48938e29a9a07410574c7e72fc661a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:07:43 +0900 Subject: [PATCH 15/18] fix(network): remove impossible success-id coverage branch --- .../src/webdriver_bidi_command_correlation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_command_correlation.rs b/crates/originweave-network/src/webdriver_bidi_command_correlation.rs index 18a408922..99b6b2ba7 100644 --- a/crates/originweave-network/src/webdriver_bidi_command_correlation.rs +++ b/crates/originweave-network/src/webdriver_bidi_command_correlation.rs @@ -200,9 +200,9 @@ impl WebDriverBiDiCommandCorrelation { ) } WebDriverBiDiJsonEnvelopeKind::Success => { - let Some(command_id) = envelope.command_id() else { - return Err(WebDriverBiDiCommandCorrelationError::CommandNotOutstanding); - }; + let command_id = envelope + .command_id() + .expect("validated WebDriver BiDi success envelopes always carry a command id"); self.complete( command_id, expected_kind, From 84195ec7bec17a828b49ac7e4d6dbe0863296353 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:03:53 +0900 Subject: [PATCH 16/18] style(network): apply pinned rustfmt Signed-off-by: Seongho Bae --- .../src/webdriver_bidi_command_correlation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_command_correlation.rs b/crates/originweave-network/src/webdriver_bidi_command_correlation.rs index 548d83930..dbc7ada17 100644 --- a/crates/originweave-network/src/webdriver_bidi_command_correlation.rs +++ b/crates/originweave-network/src/webdriver_bidi_command_correlation.rs @@ -1,8 +1,8 @@ use std::{collections::BTreeMap, error::Error, fmt}; use crate::{ - webdriver_bidi_json_envelope::WebDriverBiDiJsonEnvelopeRouting, MAX_WEBDRIVER_BIDI_JS_UINT, - WebDriverBiDiJsonEnvelope, + MAX_WEBDRIVER_BIDI_JS_UINT, WebDriverBiDiJsonEnvelope, + webdriver_bidi_json_envelope::WebDriverBiDiJsonEnvelopeRouting, }; /// Maximum number of local WebDriver BiDi commands retained as outstanding at once. From c03e0dcc63cf452b64fcd3d9895f734e7044169c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 00:09:28 +0900 Subject: [PATCH 17/18] test: reject pointer reply from replacement connection (cherry picked from commit 8193fcd50125d9e9a43b4755e0f7626801b74374) Signed-off-by: Seongho Bae --- ...er_click_response_connection_provenance.rs | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 crates/originweave-network/tests/webdriver_bidi_pointer_click_response_connection_provenance.rs diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_response_connection_provenance.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_response_connection_provenance.rs new file mode 100644 index 000000000..814f4f9d9 --- /dev/null +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_response_connection_provenance.rs @@ -0,0 +1,188 @@ +use std::{ + error::Error, + io::{self, Read, Write}, + net::{SocketAddr, TcpListener, TcpStream}, + thread, + time::Duration, +}; + +use originweave_core::{ + WebDriverBiDiPointerClickCommand, WebDriverBiDiRemoteNodeReference, + WebDriverBiDiWebSocketEndpoint, +}; +use originweave_network::{ + WebDriverBiDiCommandCorrelation, WebDriverBiDiPointerClickResult, + WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, + WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketHandshakePlan, + WebDriverBiDiWebSocketMaskKey, WebDriverBiDiWebSocketMessageAssembler, + WebDriverBiDiWebSocketMessageAssembly, WebDriverBiDiWebSocketTextMessage, + send_webdriver_bidi_pointer_click, +}; + +const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; +const RFC6455_SAMPLE_KEY: &str = "dGhlIHNhbXBsZSBub25jZQ=="; +const OPENING_RESPONSE: &[u8] = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"; +const CLICK_SUCCESS_RESPONSE: &[u8] = + br#"{"type":"success","id":42,"result":{"vendorExtension":{"observed":false}}}"#; + +fn read_opening_request(stream: &mut TcpStream) -> io::Result<()> { + stream.set_read_timeout(Some(Duration::from_secs(2)))?; + let mut request = Vec::new(); + let mut buffer = [0_u8; 512]; + while !request.ends_with(b"\r\n\r\n") { + let count = stream.read(&mut buffer)?; + if count == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "client opening request ended before the header terminator", + )); + } + request.extend_from_slice(&buffer[..count]); + } + Ok(()) +} + +fn read_masked_text_frame(stream: &mut TcpStream) -> io::Result> { + let mut header = [0_u8; 2]; + stream.read_exact(&mut header)?; + if header[0] != 0x81 || header[1] & 0x80 == 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "expected one final masked client text frame", + )); + } + let marker = header[1] & 0x7f; + let length = match marker { + 0..=125 => usize::from(marker), + 126 => { + let mut extended = [0_u8; 2]; + stream.read_exact(&mut extended)?; + usize::from(u16::from_be_bytes(extended)) + } + 127 => { + let mut extended = [0_u8; 8]; + stream.read_exact(&mut extended)?; + let length = u64::from_be_bytes(extended); + usize::try_from(length).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "pointer frame length exceeds usize") + })? + } + _ => unreachable!(), + }; + let mut mask = [0_u8; 4]; + stream.read_exact(&mut mask)?; + let mut payload = vec![0_u8; length]; + stream.read_exact(&mut payload)?; + for (index, byte) in payload.iter_mut().enumerate() { + *byte ^= mask[index % mask.len()]; + } + Ok(payload) +} + +fn establish(local_addr: SocketAddr) -> Result> { + let endpoint = format!("ws://{local_addr}/session/{SESSION_ID}"); + let target = WebDriverBiDiWebSocketEndpoint::new(&endpoint)? + .correlate_session_id(SESSION_ID)? + .into_explicit_connect_target()?; + let connection = + WebDriverBiDiTcpConnectionPlan::new(target, Duration::from_secs(1), 1)?.connect()?; + Ok(WebDriverBiDiWebSocketHandshakePlan::new( + connection, + WebDriverBiDiWebSocketClientKey::new(RFC6455_SAMPLE_KEY)?, + )? + .write_opening_request(Duration::from_millis(500))? + .read_opening_response(Duration::from_millis(500))?) +} + +fn receive_replacement_response( + listener: TcpListener, +) -> Result> { + let local_addr = listener.local_addr()?; + let server = thread::spawn(move || -> io::Result<()> { + let (mut stream, _) = listener.accept()?; + read_opening_request(&mut stream)?; + stream.write_all(OPENING_RESPONSE)?; + stream.write_all(&[0x81, CLICK_SUCCESS_RESPONSE.len() as u8])?; + stream.write_all(CLICK_SUCCESS_RESPONSE) + }); + + let established = establish(local_addr)?; + let (_established, frame) = established.read_frame(Duration::from_millis(500))?; + let mut assembler = WebDriverBiDiWebSocketMessageAssembler::new(); + let text = match assembler.push_frame(frame)? { + WebDriverBiDiWebSocketMessageAssembly::Text(text) => text, + other => { + return Err(io::Error::other(format!( + "replacement pointer connection produced unexpected assembly state: {other:?}" + )) + .into()); + } + }; + server + .join() + .map_err(|_| io::Error::other("replacement pointer server panicked"))??; + Ok(text) +} + +#[test] +fn pointer_response_from_same_session_replacement_connection_cannot_consume_original_pending_command( +) -> Result<(), Box> { + let original_listener = TcpListener::bind(("127.0.0.1", 0))?; + let original_addr = original_listener.local_addr()?; + let expected = WebDriverBiDiPointerClickCommand::new( + 42, + "context-a", + &WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?, + )?; + let expected_json = expected.as_json().as_bytes().to_vec(); + let original_server = thread::spawn(move || -> io::Result<()> { + let (mut stream, _) = original_listener.accept()?; + read_opening_request(&mut stream)?; + stream.write_all(OPENING_RESPONSE)?; + let command = read_masked_text_frame(&mut stream)?; + if command != expected_json { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unexpected pointer command on original connection", + )); + } + Ok(()) + }); + + let original = establish(original_addr)?; + let command = WebDriverBiDiPointerClickCommand::new( + 42, + "context-a", + &WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?, + )?; + let mut correlation = WebDriverBiDiCommandCorrelation::new(); + let _original = send_webdriver_bidi_pointer_click( + &command, + original, + &mut correlation, + WebDriverBiDiWebSocketMaskKey::new([1, 2, 3, 4]), + Duration::from_millis(500), + )?; + original_server + .join() + .map_err(|_| io::Error::other("original pointer server panicked"))??; + assert_eq!(correlation.outstanding_count(), 1); + + let replacement_listener = TcpListener::bind(("127.0.0.1", 0))?; + let replacement_response = receive_replacement_response(replacement_listener)?; + let parsed = WebDriverBiDiPointerClickResult::parse_and_correlate( + &replacement_response, + &mut correlation, + ); + + assert!( + parsed.is_err(), + "same-session replacement connection unexpectedly consumed the original pointer command" + ); + assert_eq!( + correlation.outstanding_count(), + 1, + "foreign-connection rejection must leave the original pointer command pending" + ); + Ok(()) +} From ba100fbc39e1ac4f10ee4faade38418551bb8298 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 00:56:41 +0900 Subject: [PATCH 18/18] docs: record origin binding receipt parent adoption Commit-Message-Assisted-by: Codex (via Codex) Signed-off-by: Seongho Bae --- CHANGELOG.md | 1 + ...i-received-response-connection-provenance.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16e8e6e03..8afeb8cae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Changed +- Carried replacement-connection click-reply rejection into navigation origin binding, preserving invalid-URL and stale-document rejection before changes to the registered origin. - Integrated current document-advance prerequisites into committed-navigation origin binding, preserving URL validation before mutation and stale-epoch rejection while restoring the inherited executable release contract. - Carried replacement-connection click-reply rejection into document advancement while preserving rejection of stale or retired contexts; a successful reply still does not authenticate a later navigation. - Integrated current navigation-observation prerequisites into document-epoch advancement, preserving stale-epoch and retired-context rejection and the Proposed architecture decision without granting a new origin or action authority. diff --git a/docs/doctoring/webdriver-bidi-received-response-connection-provenance.md b/docs/doctoring/webdriver-bidi-received-response-connection-provenance.md index aadb4dca4..b38dded4f 100644 --- a/docs/doctoring/webdriver-bidi-received-response-connection-provenance.md +++ b/docs/doctoring/webdriver-bidi-received-response-connection-provenance.md @@ -123,6 +123,23 @@ measurements. A connection-bound click acknowledgment does not authenticate the later navigation event, prove causation, bind a new origin or establish a released browser workflow. Those acceptance gaps are not closed by this parent adoption. +### Origin-binding owner adopts the pointer receipt repair + +On #261 `572fc4224ddc09c010bd9ccf100764076899495a`, canonical replay +`c03e0dcc` reproduced the replacement-connection defect (zero passed, one failed). +Ordinary merge `80cfa186` adopts #260 +`3807aabeb22f9622610c3c8d504d1c686d25d896`, preserving the receipt safeguards, +stronger regressions and repaired navigation fixture from its parent chain. +The origin-binding production module and its three integration tests remain +byte-identical to the child predecessor. URL validation still precedes registry +mutation, and stale-epoch rejection preserves the intervening origin binding. + +All twenty-one focused origin-binding, document-advance, navigation and pointer +response tests pass locally. Full exact-head checks and coverage are separate +measurements. The resulting origin is registry-local evidence derived from the +accepted observation; it does not authenticate the observation, authorize a +destination, prove click causality or establish released browser behavior. + ## References Fette, I., & Melnikov, A. (2011). *The WebSocket Protocol* (RFC 6455). Internet Engineering Task Force. https://www.rfc-editor.org/rfc/rfc6455