Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Typed browser-protocol operations that derive the exact adapter capability inside the strongest current context/origin/document-epoch dispatch boundary, preventing callers from independently selecting mismatched operation and capability metadata without performing browser I/O or granting policy authority.
- Bounded WebDriver BiDi accessibility-node query parameters for `browsingContext.locateNodes`, with reviewed selector/result budgets, exact-token role and control-free accessible-name admission, minimal remote-value serialization options, and fail-closed revalidation that rejects an untrusted adapter response whose returned node count exceeds the exact request budget before node normalization or retention.
- Deterministic WebDriver BiDi `browsingContext.locateNodes` command serialization that accepts only protocol-range `js-uint` identifiers and bounded injection-safe browsing-context identifiers, JSON-escapes reviewed query text, and emits the exact accessibility locator, finite node budget, and minimal serialization options without performing transport I/O or granting browser or Agent authority.
- Fail-closed WebDriver BiDi `locateNodes` response correlation that consumes the exact serialized command, rejects out-of-range or mismatched response `id` values, and returns non-cloneable correlation evidence carrying only the matched command identifier and browsing context without parsing the response or granting browser/Agent authority.
- Fail-closed WebDriver BiDi `script.NodeRemoteValue` admission that requires the exact remote type `node` and a non-empty `sharedId` within the same UTF-8 identifier budget as browser session and context identifiers, rejecting control and whitespace so an untrusted `locateNodes` item cannot be retained as a later typed-input handle without a usable shared node identity.
- Same-call `locateNodes` result admission that revalidates the exact current session, browsing context, canonical origin, and document epoch, rejects an over-budget or non-node result, and translates each admitted `sharedId` through the authority registry into an `ObservedNodeHandle` without performing browser I/O.
- Same-call QueryNodes admission that transfers a non-cloneable SemanticObservation protocol-use proof by ownership into `bind_current_nodes` before an untrusted `locateNodes` result can become current `ObservedNodeHandle` values, so Navigation-only or TypedInput-only proofs cannot mint observation handles.
Expand Down
5 changes: 3 additions & 2 deletions crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub use contracts::{
SecretDelivery, SessionMode, evaluate_extension_access,
};
pub use webdriver_bidi_command::{
MAX_WEBDRIVER_BIDI_COMMAND_ID, WebDriverBiDiLocateNodesCommand,
WebDriverBiDiLocateNodesCommandError,
MAX_WEBDRIVER_BIDI_COMMAND_ID, ValidatedWebDriverBiDiLocateNodesResponse,
WebDriverBiDiLocateNodesCommand, WebDriverBiDiLocateNodesCommandError,
WebDriverBiDiLocateNodesResponseCorrelationError,
};
89 changes: 89 additions & 0 deletions crates/originweave-core/src/webdriver_bidi_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,64 @@ impl Display for WebDriverBiDiLocateNodesCommandError {

impl Error for WebDriverBiDiLocateNodesCommandError {}

/// Fail-closed errors while correlating one WebDriver BiDi response with its exact command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WebDriverBiDiLocateNodesResponseCorrelationError {
/// The returned response identifier exceeds WebDriver BiDi's `js-uint` range.
InvalidResponseId,
/// The returned response identifier belongs to a different in-flight command.
ResponseIdMismatch {
/// Exact command identifier that this response must carry.
expected: u64,
/// Untrusted response identifier returned by the adapter.
actual: u64,
},
}

impl Display for WebDriverBiDiLocateNodesResponseCorrelationError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidResponseId => {
formatter.write_str("WebDriver BiDi response id is outside the js-uint range")
}
Self::ResponseIdMismatch { expected, actual } => write!(
formatter,
"WebDriver BiDi response id {actual} does not match command id {expected}"
),
}
}
}

impl Error for WebDriverBiDiLocateNodesResponseCorrelationError {}

/// Non-cloneable evidence that one `locateNodes` response matched the exact command id.
///
/// Only [`WebDriverBiDiLocateNodesCommand::correlate_response_id`] can construct this value. It
/// retains the exact command identifier and bounded browsing-context identifier so a later trusted
/// transport boundary can carry correlation evidence forward without reconstructing it from
/// ambient metadata. It does not authenticate a browser or adapter, prove current OriginWeave
/// session/context/origin authority, validate response payload shape, admit nodes, or authorize an
/// Agent action.
#[derive(Debug, PartialEq, Eq)]
pub struct ValidatedWebDriverBiDiLocateNodesResponse {
command_id: u64,
browsing_context: String,
}

impl ValidatedWebDriverBiDiLocateNodesResponse {
/// Return the exact command identifier proven to match the response.
#[must_use]
pub const fn command_id(&self) -> u64 {
self.command_id
}

/// Return the bounded browsing-context identifier serialized by the matched command.
#[must_use]
pub fn browsing_context(&self) -> &str {
&self.browsing_context
}
}

/// Deterministic serialized command envelope for one bounded WebDriver BiDi accessibility query.
///
/// Construction accepts only a WebDriver BiDi `js-uint` command identifier, a bounded opaque
Expand Down Expand Up @@ -129,6 +187,37 @@ impl WebDriverBiDiLocateNodesCommand {
pub fn as_json(&self) -> &str {
&self.json
}

/// Consume this command and correlate one untrusted response identifier with it.
///
/// The response identifier is validated against WebDriver BiDi's `js-uint` range before exact
/// equality is checked. Success consumes the command and returns non-cloneable correlation
/// evidence, preventing this command value from being reused to validate another response.
/// This does not parse a response, authenticate the transport, or grant browser/Agent authority.
pub fn correlate_response_id(
self,
response_id: u64,
) -> Result<
ValidatedWebDriverBiDiLocateNodesResponse,
WebDriverBiDiLocateNodesResponseCorrelationError,
> {
if response_id > MAX_WEBDRIVER_BIDI_COMMAND_ID {
return Err(WebDriverBiDiLocateNodesResponseCorrelationError::InvalidResponseId);
}
if response_id != self.command_id {
return Err(
WebDriverBiDiLocateNodesResponseCorrelationError::ResponseIdMismatch {
expected: self.command_id,
actual: response_id,
},
);
}

Ok(ValidatedWebDriverBiDiLocateNodesResponse {
command_id: self.command_id,
browsing_context: self.browsing_context,
})
}
}

fn push_json_string(output: &mut String, value: &str) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::error::Error;

use originweave_core::{
MAX_WEBDRIVER_BIDI_COMMAND_ID, WebDriverBiDiAccessibilityQuery,
WebDriverBiDiLocateNodesCommand, WebDriverBiDiLocateNodesResponseCorrelationError,
};

fn locate_nodes_command(
command_id: u64,
) -> Result<WebDriverBiDiLocateNodesCommand, Box<dyn Error>> {
let query = WebDriverBiDiAccessibilityQuery::new(Some("button"), Some("Submit task"), 4)?;
Ok(WebDriverBiDiLocateNodesCommand::new(
command_id,
"context-a",
&query,
)?)
}

#[test]
fn locate_nodes_response_requires_exact_command_id() -> Result<(), Box<dyn Error>> {
let correlated = locate_nodes_command(42)?.correlate_response_id(42)?;

assert_eq!(correlated.command_id(), 42);
assert_eq!(correlated.browsing_context(), "context-a");
Ok(())
}

#[test]
fn locate_nodes_response_rejects_mismatched_command_id() -> Result<(), Box<dyn Error>> {
let error = locate_nodes_command(42)?.correlate_response_id(41);

assert_eq!(
error,
Err(
WebDriverBiDiLocateNodesResponseCorrelationError::ResponseIdMismatch {
expected: 42,
actual: 41,
}
)
);
Ok(())
}

#[test]
fn locate_nodes_response_rejects_out_of_range_id_before_correlation() -> Result<(), Box<dyn Error>>
{
let error = locate_nodes_command(1)?.correlate_response_id(MAX_WEBDRIVER_BIDI_COMMAND_ID + 1);

assert_eq!(
error,
Err(WebDriverBiDiLocateNodesResponseCorrelationError::InvalidResponseId)
);
Ok(())
}

#[test]
fn response_correlation_error_contract_is_source_free() {
let errors = [
WebDriverBiDiLocateNodesResponseCorrelationError::InvalidResponseId,
WebDriverBiDiLocateNodesResponseCorrelationError::ResponseIdMismatch {
expected: 2,
actual: 1,
},
];

for error in errors {
assert!(error.source().is_none());
assert!(!error.to_string().is_empty());
}
}
4 changes: 3 additions & 1 deletion docs/doctoring/browser-agent-protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The latest published W3C technical-report baseline reviewed here remains the 1 J

For the bounded `browsingContext.locateNodes` command-serialization boundary, the reviewed Editor’s Draft defines a command envelope with `id: js-uint`, defines `js-uint` as `0..9007199254740991`, and defines `browsingContext.locateNodes` parameters containing a browsing context, locator, optional positive `maxNodeCount`, optional `serializationOptions`, and optional `startNodes`. OriginWeave serializes only its separately reviewed accessibility-locator subset and fixed minimal serialization options; this deterministic JSON value is not transport authentication or browser/Agent authority.

WebDriver BiDi commands may execute concurrently and finish out of order. The Editor’s Draft defines the command id as the local end’s correlation identifier and sets a successful `CommandResponse.id` to that exact command id; an `ErrorResponse.id` may be `null` when no valid command id can be recovered. OriginWeave therefore fails closed unless a non-null protocol-range response id exactly matches the consumed command before later payload admission. Parsing success/error envelopes, handling nullable malformed-command errors, and authenticating the browser transport remain separate adapter boundaries.

Primary sources: World Wide Web Consortium, *WebDriver BiDi* (published Working Draft and current Editor’s Draft).

## Chrome Manifest V3
Expand Down Expand Up @@ -49,7 +51,7 @@ The main [`docs/doctoring.md`](../doctoring.md) records the stable W3C PROV-O Re
## Product consequences

1. Version adapter contracts independently from OriginWeave session/context/action/evidence types. Admit a BiDi `script.NodeRemoteValue` only as an untrusted transport handle when its type is exactly `node` and a usable control-free `sharedId` is present; do not treat a realm-local `handle`, a missing shared identifier, or control/whitespace-bearing protocol text as OriginWeave node authority. Treat an accessibility-query role as one exact WAI-ARIA token, not a whitespace-separated fallback list.
2. Serialize reviewed BiDi commands from already validated bounded values only; a protocol-shaped JSON envelope never substitutes for authenticated browser transport, current session/context/origin/document authority, policy authorization, or post-condition evidence.
2. Serialize reviewed BiDi commands from already validated bounded values only, then correlate each non-null response id to the exact consumed command before payload admission; a protocol-shaped JSON envelope or matching id never substitutes for authenticated browser transport, current session/context/origin/document authority, policy authorization, or post-condition evidence.
3. Pin exact Chromium/CDP compatibility evidence at release time.
4. Keep WebDriver BiDi's Working Draft status visible in compatibility claims.
5. Keep WebMCP experimental/optional and propagate untrusted-content semantics.
Expand Down
Loading