From 05c8834072783f7f53be342b441712c1eeb9c736 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:43:03 +0900 Subject: [PATCH 1/5] test(core): require current browser context origin --- .../browser_context_origin_revalidation.rs | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 crates/originweave-core/tests/browser_context_origin_revalidation.rs diff --git a/crates/originweave-core/tests/browser_context_origin_revalidation.rs b/crates/originweave-core/tests/browser_context_origin_revalidation.rs new file mode 100644 index 000000000..ca9109b5c --- /dev/null +++ b/crates/originweave-core/tests/browser_context_origin_revalidation.rs @@ -0,0 +1,95 @@ +use std::error::Error; +use std::io; + +use originweave_core::{BrowserAuthorityRegistry, BrowserRegistryError, Origin}; + +fn first_origin() -> Result { + Origin::parse("http://127.0.0.1:43127") + .map_err(|_error| io::Error::other("controlled first origin must be valid")) +} + +fn second_origin() -> Result { + Origin::parse("http://localhost:43127") + .map_err(|_error| io::Error::other("controlled second origin must be valid")) +} + +#[test] +fn current_context_origin_must_be_bound_before_revalidation() -> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let session = registry.register_session("webdriver-session")?; + let context = registry.register_context(session, "top-level-context")?; + let origin = first_origin()?; + + assert_eq!( + registry.require_context_origin(session, context, &origin), + Err(BrowserRegistryError::ContextOriginNotBound) + ); + + let epoch = registry.bind_context_origin(session, context, &origin)?; + assert_eq!( + registry.require_context_origin(session, context, &origin), + Ok(epoch) + ); + Ok(()) +} + +#[test] +fn current_context_origin_revalidation_fails_closed_on_mismatch() -> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let session = registry.register_session("webdriver-session")?; + let context = registry.register_context(session, "top-level-context")?; + let first = first_origin()?; + let second = second_origin()?; + + registry.bind_context_origin(session, context, &first)?; + assert_eq!( + registry.require_context_origin(session, context, &second), + Err(BrowserRegistryError::OriginChangedWithoutDocumentAdvance) + ); + assert!(registry + .require_context_origin(session, context, &first) + .is_ok()); + Ok(()) +} + +#[test] +fn document_rotation_requires_fresh_origin_binding() -> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let session = registry.register_session("webdriver-session")?; + let context = registry.register_context(session, "top-level-context")?; + let first = first_origin()?; + let second = second_origin()?; + + registry.bind_context_origin(session, context, &first)?; + let next_epoch = registry.advance_document(context)?; + assert_eq!( + registry.require_context_origin(session, context, &first), + Err(BrowserRegistryError::ContextOriginNotBound) + ); + + registry.bind_context_origin(session, context, &second)?; + assert_eq!( + registry.require_context_origin(session, context, &second), + Ok(next_epoch) + ); + Ok(()) +} + +#[test] +fn context_origin_revalidation_preserves_session_ownership() -> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let owner = registry.register_session("owner-session")?; + let attacker = registry.register_session("attacker-session")?; + let context = registry.register_context(owner, "top-level-context")?; + let origin = first_origin()?; + + registry.bind_context_origin(owner, context, &origin)?; + assert_eq!( + registry.require_context_origin(attacker, context, &origin), + Err(BrowserRegistryError::ContextSessionMismatch { + expected: owner, + actual: attacker, + }) + ); + Ok(()) +} From dae6566fc19fe4a30a77b8361bc24a7071af9a8b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:45:24 +0900 Subject: [PATCH 2/5] test(core): apply canonical origin revalidation formatting --- .../tests/browser_context_origin_revalidation.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/tests/browser_context_origin_revalidation.rs b/crates/originweave-core/tests/browser_context_origin_revalidation.rs index ca9109b5c..7f35fab1d 100644 --- a/crates/originweave-core/tests/browser_context_origin_revalidation.rs +++ b/crates/originweave-core/tests/browser_context_origin_revalidation.rs @@ -46,9 +46,11 @@ fn current_context_origin_revalidation_fails_closed_on_mismatch() -> Result<(), registry.require_context_origin(session, context, &second), Err(BrowserRegistryError::OriginChangedWithoutDocumentAdvance) ); - assert!(registry - .require_context_origin(session, context, &first) - .is_ok()); + assert!( + registry + .require_context_origin(session, context, &first) + .is_ok() + ); Ok(()) } From 61485d0ceb2b22dabadc9f5297eacbbb296c5f67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:50:26 +0900 Subject: [PATCH 3/5] feat(core): revalidate current browser context origin --- .../originweave-core/src/browser_registry.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/originweave-core/src/browser_registry.rs b/crates/originweave-core/src/browser_registry.rs index bf76f79c3..7be75ec6e 100644 --- a/crates/originweave-core/src/browser_registry.rs +++ b/crates/originweave-core/src/browser_registry.rs @@ -175,6 +175,34 @@ impl BrowserAuthorityRegistry { Ok(epoch) } + /// Revalidate the canonical origin bound to the exact current browser document. + /// + /// This read-only immediate-use boundary lets a trusted browser adapter prove that the exact + /// OriginWeave session/context still has the expected canonical origin in its current document + /// epoch. It fails closed when the current document has no origin binding, including directly + /// after [`Self::advance_document`], and rejects a different origin without mutating registry + /// state. The returned epoch is descriptive current state, not a reusable capability. + /// + /// This method does not authenticate the adapter or browser process, derive the current origin + /// from Chromium, authorize a destination or action, perform browser I/O, or attest that the + /// caller-supplied origin came from the running browser. + pub fn require_context_origin( + &self, + browser_session: BrowserSessionId, + browsing_context: BrowsingContextId, + origin: &Origin, + ) -> Result { + let epoch = self.current_context_epoch(browser_session, browsing_context)?; + let expected_origin = self + .context_origin + .get(&browsing_context) + .ok_or(BrowserRegistryError::ContextOriginNotBound)?; + if expected_origin != origin { + return Err(BrowserRegistryError::OriginChangedWithoutDocumentAdvance); + } + Ok(epoch) + } + /// Advance a browsing context to the next document epoch and invalidate old node bindings. /// /// Call this whenever navigation or document replacement invalidates actionable node identity. @@ -271,6 +299,8 @@ pub enum BrowserRegistryError { /// Session supplied by the current caller. actual: BrowserSessionId, }, + /// The current document has no canonical origin bound to the browsing context. + ContextOriginNotBound, /// The context origin changed without first rotating the document epoch. OriginChangedWithoutDocumentAdvance, /// The registry exhausted one of its monotonic internal identifier spaces. @@ -299,6 +329,8 @@ impl fmt::Display for BrowserRegistryError { expected.value(), actual.value() ), + Self::ContextOriginNotBound => formatter + .write_str("browsing context has no canonical origin bound for the current document"), Self::OriginChangedWithoutDocumentAdvance => formatter .write_str("browsing context origin changed without advancing the document epoch"), Self::IdentifierSpaceExhausted => { @@ -588,6 +620,7 @@ mod tests { expected: expected_values[0], actual: actual_values[0], }, + BrowserRegistryError::ContextOriginNotBound, BrowserRegistryError::OriginChangedWithoutDocumentAdvance, BrowserRegistryError::IdentifierSpaceExhausted, BrowserRegistryError::DocumentEpochExhausted, From 1eae12991eb5a2f91ce2d1486e9008c9ac3663e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:53:37 +0900 Subject: [PATCH 4/5] style(core): apply canonical context-origin formatting --- crates/originweave-core/src/browser_registry.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/browser_registry.rs b/crates/originweave-core/src/browser_registry.rs index 7be75ec6e..b9c494002 100644 --- a/crates/originweave-core/src/browser_registry.rs +++ b/crates/originweave-core/src/browser_registry.rs @@ -329,8 +329,9 @@ impl fmt::Display for BrowserRegistryError { expected.value(), actual.value() ), - Self::ContextOriginNotBound => formatter - .write_str("browsing context has no canonical origin bound for the current document"), + Self::ContextOriginNotBound => formatter.write_str( + "browsing context has no canonical origin bound for the current document", + ), Self::OriginChangedWithoutDocumentAdvance => formatter .write_str("browsing context origin changed without advancing the document epoch"), Self::IdentifierSpaceExhausted => { From 6996e6fe9915d7f270ce0265d148358d141aa21f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 06:24:05 +0900 Subject: [PATCH 5/5] docs: record context origin revalidation --- CHANGELOG.md | 3 ++- tests/test_repository_contract.py | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d0ef113..0ab2cb7b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Runtime browser-adapter version binding at the atomic protocol-use boundary: the caller-supplied bounded adapter-version token must exactly match the reviewed descriptor version before runtime revision or capability checks can succeed, preventing adapter-build drift from silently reusing otherwise matching protocol/browser metadata without authenticating or attesting the adapter process. - Same-call browser-protocol dispatch gating that validates current protocol family, adapter version, pinned protocol/browser revisions, OriginWeave generation, and required capability before invoking one callback, transferring the non-cloneable validation proof by ownership without turning metadata validation into browser or Agent authority. - Context-bound browser-protocol dispatch composition that revalidates the exact OriginWeave browser session/context pair, carries the registry's current document epoch into the immediate callback, and separately requires the same exact runtime protocol metadata/capability checks before dispatch without claiming origin, destination, typed-input, transport-authentication, or post-condition authority. +- Explicit `BrowserAuthorityRegistry::require_context_origin` revalidation that requires the exact registered canonical origin for the current browser session, browsing context, and document epoch before origin-sensitive protocol use, failing closed when binding is absent or mismatched without granting navigation or action authority. - Credential-safe browser-protocol validation evidence that copies only the already validated protocol family, OriginWeave generation, adapter version, pinned protocol/browser revisions, and exact capability into cloneable audit metadata without recreating the non-cloneable validation prerequisite or granting browser/Agent 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. @@ -80,4 +81,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD diff --git a/tests/test_repository_contract.py b/tests/test_repository_contract.py index 360e11143..1bd436c8d 100644 --- a/tests/test_repository_contract.py +++ b/tests/test_repository_contract.py @@ -175,6 +175,12 @@ def test_product_name_is_consistent_in_binding_documents(self) -> None: self.assertNotIn("TraceWeave", text, relative) self.assertNotIn("ProofRail", text, relative) + def test_context_origin_revalidation_is_recorded_in_the_changelog(self) -> None: + """The public origin-revalidation boundary must remain visible in release history.""" + + changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + self.assertIn("BrowserAuthorityRegistry::require_context_origin", changelog) + def test_database_contract_requires_two_word_snake_case(self) -> None: """Persistent naming policy must include the mandated canonical form.""" @@ -185,4 +191,4 @@ def test_database_contract_requires_two_word_snake_case(self) -> None: if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main()