Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9128711
test(network): require node-bound BiDi text transport
seonghobae Sep 1, 2026
aa1a024
feat(network): transport node-bound BiDi text input
seonghobae Sep 1, 2026
46a05d7
style(network): apply canonical rustfmt diagnostics
seonghobae Sep 1, 2026
2b96000
test(network): reject text deadlines before correlation
seonghobae Sep 6, 2026
d903cf6
merge: adopt verified text-command parent into transport
seonghobae Sep 6, 2026
2d8f7c0
fix(network): validate text frame deadline before correlation
seonghobae Sep 6, 2026
447e8aa
fix(network): adopt typed text correlation and sealed dispatch
seonghobae Sep 6, 2026
e2b49e6
test(network): cover text preflight retirement and ambiguous writes
seonghobae Sep 6, 2026
3056b89
fix(network): retire text correlation only after no-write preflight r…
seonghobae Sep 6, 2026
4cffe5a
docs: record text transport adoption and bounded failure evidence
seonghobae Sep 6, 2026
04143c6
test(network): assert text family and rejection wire silence
seonghobae Sep 6, 2026
10131eb
test(network): reject text dispatch on a foreign transport session
seonghobae Sep 6, 2026
c3dc8e7
fix(network): bind admitted text node to verified transport session
seonghobae Sep 6, 2026
4435ce5
docs: record text session binding regression and authority boundary
seonghobae Sep 6, 2026
3346d8e
fix(network): retain text sender connection provenance
seonghobae Sep 6, 2026
4e020e1
test(network): replay pointer authority regressions on text sender
seonghobae Sep 7, 2026
7e4bd76
fix(network): adopt pointer session and reply authority in text stack
seonghobae Sep 7, 2026
ebd507a
docs: record text sender pointer safeguard adoption
seonghobae Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to OriginWeave are documented in this file. The format follo

## [Unreleased]

- Preserve text-entry safeguards while rejecting clicks sent to another browser session and click replies from replacement connections. These checks do not yet verify that the browser changed the requested field.
- Retain each pending text-entry request's original connection so a connection-aware response consumer can reject replies from a replacement socket. Consumer integration and observed field-value verification remain separate requirements.
- Text entry rejects a connection for a different browser session and invalid deadlines before reserving a pending request. Rejected writes that provably sent nothing release that request; uncertain writes remain pending and are not silently retried. Real-browser outcome verification remains unfinished.
- Retained text-input privacy and validation while adopting the latest click and subscription safeguards; text dispatch and browser outcome verification remain unfinished.
- Integrated the current navigation-subscription safeguards while preserving active-subscription admission, replay rejection and stale-document checks. A response from a replacement connection still cannot complete an earlier session shutdown; this source integration is not real-browser or release acceptance.

Expand Down
8 changes: 6 additions & 2 deletions crates/originweave-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
//! unmasked server-frame reads, assembles bounded WebDriver BiDi text messages,
//! binds received fragmented text to one exact verified connection, classifies
//! complete local-end JSON envelopes, tracks bounded command-response correlation,
//! transports a narrowly typed pointer click, admits its typed correlated protocol
//! response, sends a context-bound committed-navigation subscription and retains
//! transports narrowly typed pointer-click and node-bound non-secret text-input
//! actions, admits typed correlated protocol responses, sends a context-bound committed-navigation subscription and retains
//! its typed bounded correlated identifier, binds navigation-event admission to
//! that active command/receipt lifecycle with bounded fail-closed navigation replay
//! prevention, explicitly unsubscribes that exact
Expand Down Expand Up @@ -49,6 +49,7 @@ mod webdriver_bidi_session_end_response;
mod webdriver_bidi_session_status_command;
mod webdriver_bidi_session_status_response;
mod webdriver_bidi_session_teardown;
mod webdriver_bidi_type_text_transport;
mod webdriver_bidi_websocket_frame;
mod webdriver_bidi_websocket_handshake;
mod webdriver_bidi_websocket_message;
Expand Down Expand Up @@ -144,6 +145,9 @@ pub use webdriver_bidi_session_teardown::{
WebDriverBiDiSessionTeardownAssessment, WebDriverBiDiSessionTeardownAssessmentError,
WebDriverBiDiSessionTeardownDisposition, WebDriverBiDiSessionTeardownObservations,
};
pub use webdriver_bidi_type_text_transport::{
WebDriverBiDiTypeTextSendError, send_webdriver_bidi_type_text,
};
pub use webdriver_bidi_websocket_frame::{
MAX_WEBSOCKET_FRAME_PAYLOAD_SIZE, MAX_WEBSOCKET_FRAME_TIMEOUT,
WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrame,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub enum WebDriverBiDiCommandKind {
SessionEnd,
/// WebDriver BiDi `input.performActions` pointer click.
PointerClick,
/// WebDriver BiDi `input.performActions` node-bound non-secret text input.
TypeText,
/// Context-scoped WebDriver BiDi `session.subscribe` for committed navigation.
NavigationCommittedSubscription,
/// WebDriver BiDi `session.unsubscribe` for one retained committed-navigation subscription.
Expand Down
176 changes: 176 additions & 0 deletions crates/originweave-network/src/webdriver_bidi_type_text_transport.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use std::{error::Error, fmt, time::Duration};

use originweave_core::{
AdmittedNodeHandle, BrowserAuthorityRegistry, BrowserProtocolCapability, BrowserProtocolKind,
ValidatedBrowserProtocolUse, WebDriverBiDiRemoteNodeReference,
WebDriverBiDiTypeTextAuthorityError, WebDriverBiDiTypeTextCommand,
};

use crate::{
WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError,
WebDriverBiDiCommandKind, WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrameError,
WebDriverBiDiWebSocketMaskKey,
};

/// Fail-closed errors while transporting one current-authority text-input command.
#[derive(Debug)]
pub enum WebDriverBiDiTypeTextSendError {
/// The supplied protocol-use proof belongs to another browser protocol family.
UnsupportedProtocolKind(BrowserProtocolKind),
/// The supplied protocol-use proof did not validate typed-input capability.
UnsupportedCapability(BrowserProtocolCapability),
/// Text or current node, browser-context, document, or bounded command authority failed revalidation.
Authority {
/// Exact typed immediate-use authority failure.
source: WebDriverBiDiTypeTextAuthorityError,
},
/// The bounded correlation registry rejected the command before network I/O.
Correlation {
/// Exact typed correlation failure.
source: WebDriverBiDiCommandCorrelationError,
},
/// Writing the already-registered command frame failed and the transport is not reusable.
FrameWrite {
/// Exact typed bounded WebSocket frame-write failure.
source: WebDriverBiDiWebSocketFrameError,
},
}

impl fmt::Display for WebDriverBiDiTypeTextSendError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::UnsupportedProtocolKind(_) => {
"WebDriver BiDi text-input send requires a WebDriver BiDi proof"
}
Self::UnsupportedCapability(_) => {
"WebDriver BiDi text-input send requires typed-input capability"
}
Self::Authority { .. } => "WebDriver BiDi text-input authority was rejected",
Self::Correlation { .. } => {
"WebDriver BiDi text-input command correlation was rejected"
}
Self::FrameWrite { .. } => "WebDriver BiDi text-input command frame write failed",
})
}
}

impl Error for WebDriverBiDiTypeTextSendError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::UnsupportedProtocolKind(_) | Self::UnsupportedCapability(_) => None,
Self::Authority { source } => Some(source),
Self::Correlation { source } => Some(source),
Self::FrameWrite { source } => Some(source),
}
}
}

/// Revalidate, register, and write one node-bound `input.performActions` text-input command.
///
/// The caller must transfer a non-cloneable [`ValidatedBrowserProtocolUse`] whose protocol family
/// is exactly [`BrowserProtocolKind::WebDriverBiDi`] and whose capability is exactly
/// [`BrowserProtocolCapability::TypedInput`]. The proof is consumed before text/node authority,
/// command correlation, or frame I/O, so semantic-observation, navigation, CDP, or other protocol
/// proofs cannot dispatch text input through this boundary.
///
/// After protocol validation and immediately before correlation, this boundary reconstructs the
/// bounded command from the exact text, [`AdmittedNodeHandle`], external browsing-context
/// identifier, remote node reference, and live [`BrowserAuthorityRegistry`]. That immediate-use
/// check rejects invalid or over-budget text, stale document epochs, cross-registry handles,
/// changed origins, mismatched external contexts, and unadmitted wire node identifiers before any
/// command identifier is registered or any action frame is written. A previously constructed
/// command therefore cannot outlive its node authority and later bypass revalidation at transport
/// time. The admitted registry session must also match the established transport's verified
/// external session identifier; this read-only check cannot create or replace registry state.
///
/// Invalid local deadlines fail before registration. Registration occurs before the first possible
/// remote side effect and binds the exact established connection generation for received-response
/// verification. A correlation failure writes
/// nothing. A malformed-frame preflight rejection retires this exact typed identifier because no
/// write began. Other frame-write failures leave the identifier outstanding because a partial or
/// complete remote side effect is ambiguous and the identifier must not be silently reused.
///
/// The text value is intentionally non-secret and is never retained by this transport's error
/// variants. Secret material must use the separately governed broker/fill boundary. Typed-input
/// and node authority validation are still not policy authorization: a trusted caller must
/// separately establish deterministic policy approval and destination authority, then retain
/// correlated response and observed post-condition evidence afterward. This function does not
/// authenticate the browser, grant destination or secret authority, retry, reconnect, or choose
/// another destination.
#[expect(
clippy::too_many_arguments,
reason = "this immediate-use security boundary keeps text, command identity, live node authority, transport, correlation, masking, and deadline inputs explicit rather than persisting a reusable prevalidated command"
)]
pub fn send_webdriver_bidi_type_text(
validated: ValidatedBrowserProtocolUse,
command_id: u64,
browsing_context: &str,
text: &str,
handle: &AdmittedNodeHandle,
node: &WebDriverBiDiRemoteNodeReference,
registry: &BrowserAuthorityRegistry,
established: WebDriverBiDiWebSocketEstablished,
correlation: &mut WebDriverBiDiCommandCorrelation,
masking_key: WebDriverBiDiWebSocketMaskKey,
frame_timeout: Duration,
) -> Result<WebDriverBiDiWebSocketEstablished, WebDriverBiDiTypeTextSendError> {
if validated.kind() != BrowserProtocolKind::WebDriverBiDi {
return Err(WebDriverBiDiTypeTextSendError::UnsupportedProtocolKind(
validated.kind(),
));
}
if validated.capability() != BrowserProtocolCapability::TypedInput {
return Err(WebDriverBiDiTypeTextSendError::UnsupportedCapability(
validated.capability(),
));
}
let _consumed_typed_input_proof = validated;

let command = WebDriverBiDiTypeTextCommand::new_for_current_node(
command_id,
browsing_context,
text,
handle,
node,
registry,
)
.map_err(|source| WebDriverBiDiTypeTextSendError::Authority { source })?;

registry
.require_registered_session_external_identifier(
handle.browser_session(),
established
.transport_evidence()
.verified_peer()
.session_id(),
)
.map_err(|source| WebDriverBiDiTypeTextSendError::Authority {
source: WebDriverBiDiTypeTextAuthorityError::BrowserAuthority(source),
})?;
crate::webdriver_bidi_websocket_frame::validate_frame_timeout(frame_timeout)
.map_err(|source| WebDriverBiDiTypeTextSendError::FrameWrite { source })?;
correlation
.register_command_for_connection(
command.command_id(),
WebDriverBiDiCommandKind::TypeText,
established.transport_evidence().connection_generation(),
)
.map_err(|source| WebDriverBiDiTypeTextSendError::Correlation { source })?;
established
.write_command_frame(
command.command_id(),
command.as_json(),
masking_key,
frame_timeout,
)
.map_err(|source| {
if matches!(
source,
WebDriverBiDiWebSocketFrameError::MalformedFrame { .. }
) {
let _retirement = correlation
.retire_command_for(command.command_id(), WebDriverBiDiCommandKind::TypeText);
}
WebDriverBiDiTypeTextSendError::FrameWrite { source }
})
}
Loading
Loading