diff --git a/CHANGELOG.md b/CHANGELOG.md index d1741992..485c48bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added +- Added a fail-closed Chrome-permission separation boundary so reviewed Manifest V3 compatibility permissions, including `downloads` and `nativeMessaging`, can never mint any OriginWeave Agent action authority. - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. diff --git a/crates/originweave-core/Cargo.toml b/crates/originweave-core/Cargo.toml index 35c83b19..15bd2316 100644 --- a/crates/originweave-core/Cargo.toml +++ b/crates/originweave-core/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true homepage.workspace = true publish = false +[lib] +path = "src/crate_root.rs" + [dependencies] [lints] diff --git a/crates/originweave-core/src/chrome_permission_authority.rs b/crates/originweave-core/src/chrome_permission_authority.rs new file mode 100644 index 00000000..264c45f1 --- /dev/null +++ b/crates/originweave-core/src/chrome_permission_authority.rs @@ -0,0 +1,73 @@ +//! Separation between Chrome extension compatibility permissions and Agent authority. + +use crate::ActionKind; +use std::fmt; + +/// Why a Chrome extension permission cannot authorize an OriginWeave Agent action. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ChromePermissionAuthorityError { + /// The permission names a reviewed Chrome compatibility surface, not Agent authority. + CompatibilitySurfaceOnly, + /// The permission is not a reviewed Chrome surface and still grants no Agent capability. + UnrecognizedPermission, +} + +impl fmt::Display for ChromePermissionAuthorityError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::CompatibilitySurfaceOnly => { + "Chrome compatibility permission cannot authorize an OriginWeave Agent action" + } + Self::UnrecognizedPermission => { + "Chrome permission is not a reviewed compatibility surface and cannot authorize an OriginWeave Agent action" + } + }; + formatter.write_str(message) + } +} + +impl std::error::Error for ChromePermissionAuthorityError {} + +const REVIEWED_CHROME_COMPATIBILITY_PERMISSIONS: &[&str] = &[ + "bookmarks", + "commands", + "declarativeNetRequest", + "declarativeNetRequestWithHostAccess", + "downloads", + "history", + "nativeMessaging", + "scripting", + "sidePanel", + "storage", + "tabs", + "windows", +]; + +/// Refuse to treat a Chrome extension permission as OriginWeave Agent authority. +/// +/// A successful Chrome compatibility proof never becomes an OriginWeave Agent +/// capability. Adapters must keep browser compatibility evidence and explicit +/// OriginWeave grants separate and call this boundary before exposing a typed +/// action to policy. The action is accepted only to make that separation +/// explicit at the adapter boundary; no action kind can make this function +/// return success. +pub fn chrome_permission_authorizes_agent_action( + permission: &str, + _action: ActionKind, +) -> Result<(), ChromePermissionAuthorityError> { + if !is_exact_chrome_permission_token(permission) { + return Err(ChromePermissionAuthorityError::UnrecognizedPermission); + } + if REVIEWED_CHROME_COMPATIBILITY_PERMISSIONS.contains(&permission) { + return Err(ChromePermissionAuthorityError::CompatibilitySurfaceOnly); + } + Err(ChromePermissionAuthorityError::UnrecognizedPermission) +} + +fn is_exact_chrome_permission_token(permission: &str) -> bool { + let mut characters = permission.chars(); + let Some(first) = characters.next() else { + return false; + }; + first.is_ascii_lowercase() && characters.all(|character| character.is_ascii_alphabetic()) +} diff --git a/crates/originweave-core/src/crate_root.rs b/crates/originweave-core/src/crate_root.rs new file mode 100644 index 00000000..399e21e5 --- /dev/null +++ b/crates/originweave-core/src/crate_root.rs @@ -0,0 +1,17 @@ +//! OriginWeave core contracts plus narrowly scoped adapter authority boundaries. +//! +//! The existing deterministic core remains implemented in `lib.rs`; this crate +//! root re-exports that protected-main API and adds the independently reviewed +//! Chrome-permission separation boundary without weakening existing authority. + +#![forbid(unsafe_code)] +#![deny(missing_docs)] + +#[path = "lib.rs"] +mod base; +pub use base::*; + +mod chrome_permission_authority; +pub use chrome_permission_authority::{ + ChromePermissionAuthorityError, chrome_permission_authorizes_agent_action, +}; diff --git a/crates/originweave-core/tests/chrome_permission_authority.rs b/crates/originweave-core/tests/chrome_permission_authority.rs new file mode 100644 index 00000000..c78bf733 --- /dev/null +++ b/crates/originweave-core/tests/chrome_permission_authority.rs @@ -0,0 +1,61 @@ +use originweave_core::{ + ActionKind, ChromePermissionAuthorityError, chrome_permission_authorizes_agent_action, +}; + +#[test] +fn chrome_compatibility_permissions_never_mint_agent_authority() { + for permission in [ + "downloads", + "bookmarks", + "history", + "storage", + "tabs", + "windows", + "scripting", + "commands", + "sidePanel", + "declarativeNetRequest", + "declarativeNetRequestWithHostAccess", + "nativeMessaging", + ] { + assert_eq!( + chrome_permission_authorizes_agent_action(permission, ActionKind::Download), + Err(ChromePermissionAuthorityError::CompatibilitySurfaceOnly) + ); + } +} + +#[test] +fn malformed_or_unreviewed_chrome_permissions_remain_unrecognized() { + for permission in [ + "", + "DOWNLOADS", + "downloads\nhttps://example.invalid", + "cookies", + "downloads ", + ] { + assert_eq!( + chrome_permission_authorizes_agent_action(permission, ActionKind::Download), + Err(ChromePermissionAuthorityError::UnrecognizedPermission) + ); + } +} + +#[test] +fn chrome_permission_authority_errors_are_standard_credential_safe_errors() { + let cases = [ + ( + ChromePermissionAuthorityError::CompatibilitySurfaceOnly, + "Chrome compatibility permission cannot authorize an OriginWeave Agent action", + ), + ( + ChromePermissionAuthorityError::UnrecognizedPermission, + "Chrome permission is not a reviewed compatibility surface and cannot authorize an OriginWeave Agent action", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + assert!(std::error::Error::source(&error).is_none()); + } +}