From ac780f08ff825fae08c64d993eaaab6fe6817e3b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:32:14 +0900 Subject: [PATCH 1/4] test(core): require canonical protocol version parsing --- .../tests/protocol_version_parsing.rs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 crates/originweave-core/tests/protocol_version_parsing.rs diff --git a/crates/originweave-core/tests/protocol_version_parsing.rs b/crates/originweave-core/tests/protocol_version_parsing.rs new file mode 100644 index 000000000..189bde6be --- /dev/null +++ b/crates/originweave-core/tests/protocol_version_parsing.rs @@ -0,0 +1,59 @@ +#![allow(clippy::expect_used)] + +use std::error::Error; +use std::str::FromStr; + +use originweave_core::{OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError}; + +#[test] +fn canonical_protocol_versions_parse_and_round_trip() -> Result<(), Box> { + let current = OriginWeaveProtocolVersion::from_str("originweave/0.1")?; + assert_eq!(current, OriginWeaveProtocolVersion::new(0, 1)); + assert_eq!(current.to_string(), "originweave/0.1"); + + let maximum = OriginWeaveProtocolVersion::from_str("originweave/65535.65535")?; + assert_eq!(maximum, OriginWeaveProtocolVersion::new(u16::MAX, u16::MAX)); + assert_eq!(maximum.to_string(), "originweave/65535.65535"); + Ok(()) +} + +#[test] +fn malformed_or_noncanonical_protocol_versions_fail_closed() { + let malformed = [ + "", + "originweave/", + "originweave/0", + "originweave/0.", + "originweave/.1", + "originweave/0.1.0", + "OriginWeave/0.1", + "originweave/00.1", + "originweave/0.01", + "originweave/+0.1", + "originweave/0.+1", + "originweave/-0.1", + "originweave/0.-1", + "originweave/65536.1", + "originweave/0.65536", + " originweave/0.1", + "originweave/0.1 ", + "originweave/0.1", + ]; + + for value in malformed { + assert_eq!( + OriginWeaveProtocolVersion::from_str(value), + Err(OriginWeaveProtocolVersionParseError::InvalidFormat) + ); + } +} + +#[test] +fn protocol_version_parse_error_is_stable_and_source_free() { + let error = OriginWeaveProtocolVersionParseError::InvalidFormat; + assert_eq!( + error.to_string(), + "OriginWeave protocol version must use canonical originweave/. syntax" + ); + assert!(error.source().is_none()); +} From eec3f462b6df93e1dd10a05aeee23393ddd7bfb2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:36:55 +0900 Subject: [PATCH 2/4] feat(core): parse canonical protocol versions --- .../originweave-core/src/browser_protocol.rs | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/src/browser_protocol.rs b/crates/originweave-core/src/browser_protocol.rs index 4b7fe0f00..981597e03 100644 --- a/crates/originweave-core/src/browser_protocol.rs +++ b/crates/originweave-core/src/browser_protocol.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, str::FromStr}; /// Maximum UTF-8 byte length for browser protocol adapter metadata tokens. pub const MAX_BROWSER_PROTOCOL_METADATA_BYTES: usize = 128; @@ -42,6 +42,53 @@ impl fmt::Display for OriginWeaveProtocolVersion { } } +impl FromStr for OriginWeaveProtocolVersion { + type Err = OriginWeaveProtocolVersionParseError; + + fn from_str(value: &str) -> Result { + let Some(remainder) = value.strip_prefix("originweave/") else { + return Err(OriginWeaveProtocolVersionParseError::InvalidFormat); + }; + let Some((major_text, minor_text)) = remainder.split_once('.') else { + return Err(OriginWeaveProtocolVersionParseError::InvalidFormat); + }; + if minor_text.contains('.') { + return Err(OriginWeaveProtocolVersionParseError::InvalidFormat); + } + let Ok(major) = major_text.parse::() else { + return Err(OriginWeaveProtocolVersionParseError::InvalidFormat); + }; + let Ok(minor) = minor_text.parse::() else { + return Err(OriginWeaveProtocolVersionParseError::InvalidFormat); + }; + + let version = Self::new(major, minor); + if version.to_string() != value { + return Err(OriginWeaveProtocolVersionParseError::InvalidFormat); + } + Ok(version) + } +} + +/// Failure to parse a canonical serialized OriginWeave Protocol generation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OriginWeaveProtocolVersionParseError { + /// The value did not use the exact canonical `originweave/.` syntax. + InvalidFormat, +} + +impl fmt::Display for OriginWeaveProtocolVersionParseError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidFormat => formatter.write_str( + "OriginWeave protocol version must use canonical originweave/. syntax", + ), + } + } +} + +impl std::error::Error for OriginWeaveProtocolVersionParseError {} + /// Browser automation protocol family used by one versioned adapter. /// /// The protocol family is descriptive metadata only. Selecting a kind does not From aeb9629704dfc1d6dcc14a6e9de2ddb85853a302 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:37:16 +0900 Subject: [PATCH 3/4] feat(core): export protocol version parse error --- crates/originweave-core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index ed33e341e..5b6275eec 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -18,7 +18,7 @@ pub use browser_protocol::{ BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolCapabilityRequirementError, BrowserProtocolDescriptorError, BrowserProtocolKind, BrowserProtocolVersionRequirementError, MAX_BROWSER_PROTOCOL_METADATA_BYTES, - OriginWeaveProtocolVersion, + OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError, }; pub use browser_registry::{ BrowserAuthorityRegistry, BrowserRegistryError, MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES, From ea17243bf3e0a332bc4a62e25207c80697fde067 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:40:58 +0900 Subject: [PATCH 4/4] docs(changelog): record canonical protocol version parsing --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cabdcc5d..7d197d67e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. - Versioned browser-protocol adapter metadata that distinguishes WebDriver BiDi from pinned CDP, binds bounded adapter/browser revision tokens to an explicit duplicate-free capability set, normalizes capability-set identity independently of caller ordering, and exposes typed fail-closed capability requirements without granting browser, action, network, or secret authority by protocol kind alone. +- Canonical OriginWeave protocol-version parsing for exact `originweave/.` syntax, with typed fail-closed rejection of malformed, ambiguous, overflowed, or noncanonical serialized generations; parsing does not negotiate compatibility or grant adapter authority. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets.