From 23f128fc84b2d2b5b4c188211575ef69c5c5d070 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 21:21:29 +0900 Subject: [PATCH 1/5] test(extension): require managed Agent Task allow-list --- .../tests/agent_task_extension_policy.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 crates/originweave-core/tests/agent_task_extension_policy.rs diff --git a/crates/originweave-core/tests/agent_task_extension_policy.rs b/crates/originweave-core/tests/agent_task_extension_policy.rs new file mode 100644 index 00000000..5dd5ed32 --- /dev/null +++ b/crates/originweave-core/tests/agent_task_extension_policy.rs @@ -0,0 +1,68 @@ +#![allow(clippy::expect_used)] + +use originweave_core::{ + AgentTaskExtensionDecision, AgentTaskExtensionPolicy, BrowserSessionId, BrowsingContextId, + ExtensionAccessDecision, ExtensionAccessRequest, ExtensionAgentCapability, ExtensionId, + evaluate_agent_task_extension, evaluate_extension_access, +}; + +fn extension_id(value: &str) -> ExtensionId { + ExtensionId::parse(value).expect("valid extension identifier") +} + +fn session(value: u64) -> BrowserSessionId { + BrowserSessionId::new(value).expect("nonzero browser session") +} + +fn context(value: u64) -> BrowsingContextId { + BrowsingContextId::new(value).expect("nonzero browsing context") +} + +#[test] +fn empty_agent_task_extension_policy_denies_every_extension() { + let policy = AgentTaskExtensionPolicy::new([]); + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy), + AgentTaskExtensionDecision::DenyNotManaged + ); +} + +#[test] +fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { + let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); + let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()]); + + assert_eq!( + evaluate_agent_task_extension(&allowed, &policy), + AgentTaskExtensionDecision::AllowManagedExtension + ); + assert_eq!( + evaluate_agent_task_extension(&other, &policy), + AgentTaskExtensionDecision::DenyNotManaged + ); +} + +#[test] +fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let policy = AgentTaskExtensionPolicy::new([extension.clone()]); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy), + AgentTaskExtensionDecision::AllowManagedExtension + ); + + let request = ExtensionAccessRequest::new( + extension, + session(31), + context(37), + ExtensionAgentCapability::ProposeTypedAction, + ); + assert_eq!( + evaluate_extension_access(&request, None), + ExtensionAccessDecision::DenyMissingGrant + ); +} From 7b1da43cfa3f859e22fd27d8b03be956208223e1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 21:40:33 +0900 Subject: [PATCH 2/5] test(extension): place managed admission at policy boundary --- .../tests/agent_task_extension_policy.rs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 crates/originweave-policy/tests/agent_task_extension_policy.rs diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs new file mode 100644 index 00000000..8ea2f0de --- /dev/null +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -0,0 +1,70 @@ +#![allow(clippy::expect_used)] + +use originweave_core::{ + BrowserSessionId, BrowsingContextId, ExtensionAccessDecision, ExtensionAccessRequest, + ExtensionAgentCapability, ExtensionId, evaluate_extension_access, +}; +use originweave_policy::{ + AgentTaskExtensionDecision, AgentTaskExtensionPolicy, evaluate_agent_task_extension, +}; + +fn extension_id(value: &str) -> ExtensionId { + ExtensionId::parse(value).expect("valid extension identifier") +} + +fn session(value: u64) -> BrowserSessionId { + BrowserSessionId::new(value).expect("nonzero browser session") +} + +fn context(value: u64) -> BrowsingContextId { + BrowsingContextId::new(value).expect("nonzero browsing context") +} + +#[test] +fn empty_agent_task_extension_policy_denies_every_extension() { + let policy = AgentTaskExtensionPolicy::new([]); + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy), + AgentTaskExtensionDecision::DenyNotManaged + ); +} + +#[test] +fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { + let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); + let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()]); + + assert_eq!( + evaluate_agent_task_extension(&allowed, &policy), + AgentTaskExtensionDecision::AllowManagedExtension + ); + assert_eq!( + evaluate_agent_task_extension(&other, &policy), + AgentTaskExtensionDecision::DenyNotManaged + ); +} + +#[test] +fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let policy = AgentTaskExtensionPolicy::new([extension.clone()]); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy), + AgentTaskExtensionDecision::AllowManagedExtension + ); + + let request = ExtensionAccessRequest::new( + extension, + session(31), + context(37), + ExtensionAgentCapability::ProposeTypedAction, + ); + assert_eq!( + evaluate_extension_access(&request, None), + ExtensionAccessDecision::DenyMissingGrant + ); +} From e476f595f4266c09c4ccd54da4f2547a22fdf7cd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 21:41:16 +0900 Subject: [PATCH 3/5] feat(extension): enforce managed Agent Task extension admission --- crates/originweave-policy/src/lib.rs | 53 +++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/originweave-policy/src/lib.rs b/crates/originweave-policy/src/lib.rs index 243ae8ce..3dda94b0 100644 --- a/crates/originweave-policy/src/lib.rs +++ b/crates/originweave-policy/src/lib.rs @@ -15,11 +15,62 @@ pub use sensitive_data::{ evaluate_handle_use, }; +use std::collections::BTreeSet; + use originweave_core::{ - ActionRequest, ApprovalEvidence, ApprovalScope, Capability, ExecutionPurpose, + ActionRequest, ApprovalEvidence, ApprovalScope, Capability, ExecutionPurpose, ExtensionId, InstructionSource, PolicyContext, RiskClass, RobotsDecision, SecretDelivery, SessionMode, }; +/// Exact extension identities that may be present in one managed Agent Task profile. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AgentTaskExtensionPolicy { + managed_extensions: BTreeSet, +} + +impl AgentTaskExtensionPolicy { + /// Build one fail-closed Agent Task extension admission policy. + /// + /// Duplicate identifiers collapse to one exact managed identity. An empty + /// iterator therefore represents the default policy that admits no extension. + #[must_use] + pub fn new(managed_extensions: I) -> Self + where + I: IntoIterator, + { + Self { + managed_extensions: managed_extensions.into_iter().collect(), + } + } +} + +/// Result of evaluating one extension identity for an isolated Agent Task profile. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AgentTaskExtensionDecision { + /// The exact canonical extension identity appears in the managed allow-list. + AllowManagedExtension, + /// The extension identity is absent from the managed allow-list. + DenyNotManaged, +} + +/// Evaluate extension admission without minting OriginWeave Agent capability. +/// +/// This pure boundary answers only whether the exact canonical extension may be +/// present in the caller's managed Agent Task profile. Chromium permissions, +/// installation state, native messaging, and [`originweave_core::ExtensionAgentGrant`] +/// remain separate authorities. +#[must_use] +pub fn evaluate_agent_task_extension( + extension_id: &ExtensionId, + policy: &AgentTaskExtensionPolicy, +) -> AgentTaskExtensionDecision { + if policy.managed_extensions.contains(extension_id) { + AgentTaskExtensionDecision::AllowManagedExtension + } else { + AgentTaskExtensionDecision::DenyNotManaged + } +} + /// The result of evaluating one typed action request. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Decision { From fb33d211d701de96b1f94daebd7c9bbadd642240 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 21:41:35 +0900 Subject: [PATCH 4/5] test(extension): remove misplaced core admission test --- .../tests/agent_task_extension_policy.rs | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 crates/originweave-core/tests/agent_task_extension_policy.rs diff --git a/crates/originweave-core/tests/agent_task_extension_policy.rs b/crates/originweave-core/tests/agent_task_extension_policy.rs deleted file mode 100644 index 5dd5ed32..00000000 --- a/crates/originweave-core/tests/agent_task_extension_policy.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![allow(clippy::expect_used)] - -use originweave_core::{ - AgentTaskExtensionDecision, AgentTaskExtensionPolicy, BrowserSessionId, BrowsingContextId, - ExtensionAccessDecision, ExtensionAccessRequest, ExtensionAgentCapability, ExtensionId, - evaluate_agent_task_extension, evaluate_extension_access, -}; - -fn extension_id(value: &str) -> ExtensionId { - ExtensionId::parse(value).expect("valid extension identifier") -} - -fn session(value: u64) -> BrowserSessionId { - BrowserSessionId::new(value).expect("nonzero browser session") -} - -fn context(value: u64) -> BrowsingContextId { - BrowsingContextId::new(value).expect("nonzero browsing context") -} - -#[test] -fn empty_agent_task_extension_policy_denies_every_extension() { - let policy = AgentTaskExtensionPolicy::new([]); - let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - - assert_eq!( - evaluate_agent_task_extension(&extension, &policy), - AgentTaskExtensionDecision::DenyNotManaged - ); -} - -#[test] -fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { - let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); - let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()]); - - assert_eq!( - evaluate_agent_task_extension(&allowed, &policy), - AgentTaskExtensionDecision::AllowManagedExtension - ); - assert_eq!( - evaluate_agent_task_extension(&other, &policy), - AgentTaskExtensionDecision::DenyNotManaged - ); -} - -#[test] -fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { - let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()]); - - assert_eq!( - evaluate_agent_task_extension(&extension, &policy), - AgentTaskExtensionDecision::AllowManagedExtension - ); - - let request = ExtensionAccessRequest::new( - extension, - session(31), - context(37), - ExtensionAgentCapability::ProposeTypedAction, - ); - assert_eq!( - evaluate_extension_access(&request, None), - ExtensionAccessDecision::DenyMissingGrant - ); -} From a0d767377a737b656ca8d095057b39be76b98a6a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 21:45:50 +0900 Subject: [PATCH 5/5] docs(extension): record managed Agent Task admission --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 569bb0cf..5b934daa 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 +- Fail-closed managed extension admission for isolated Agent Task profiles: an empty policy admits no extension, only exact canonical `ExtensionId` allow-list membership is accepted, duplicate entries cannot widen authority, and successful profile admission remains separate from `ExtensionAgentGrant` capability. - Explicit reduced-assurance classification for attached human tabs when trusted adapter evidence says an existing extension can influence page state; the narrow rule does not detect extensions, prove extension absence, grant Agent authority, or turn an unclassified context into high-assurance evidence. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. - 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. @@ -74,4 +75,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 +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file