From d268e003d4619aff7f2a7a7ff46a4bb98ae3ad45 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:12:35 +0900 Subject: [PATCH 01/10] test(extension): bind managed policy to Agent Task session --- .../tests/agent_task_extension_policy.rs | 66 ++++++++++++++----- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index f82a6249a..3f0bd768f 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -22,11 +22,11 @@ fn context(value: u64) -> BrowsingContextId { #[test] fn empty_agent_task_extension_policy_denies_every_extension() { - let policy = AgentTaskExtensionPolicy::new([], 10, 20); + let policy = AgentTaskExtensionPolicy::new(session(31), [], 10, 20); let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, 15), + evaluate_agent_task_extension(&extension, &policy, session(31), 15), AgentTaskExtensionDecision::DenyNotManaged ); } @@ -35,33 +35,58 @@ fn empty_agent_task_extension_policy_denies_every_extension() { 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()], 10, 20); + let policy = AgentTaskExtensionPolicy::new( + session(31), + [allowed.clone(), allowed.clone()], + 10, + 20, + ); assert_eq!( - evaluate_agent_task_extension(&allowed, &policy, 10), + evaluate_agent_task_extension(&allowed, &policy, session(31), 10), AgentTaskExtensionDecision::AllowManagedExtension ); assert_eq!( - evaluate_agent_task_extension(&other, &policy, 19), + evaluate_agent_task_extension(&other, &policy, session(31), 19), AgentTaskExtensionDecision::DenyNotManaged ); } +#[test] +fn managed_agent_task_extension_policy_is_not_reusable_across_sessions() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let policy = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, session(37), 15), + AgentTaskExtensionDecision::DenySessionMismatch + ); + assert_eq!( + evaluate_agent_task_extension( + &extension_id("bcdefghijklmnopabcdefghijklmnopa"), + &policy, + session(37), + 15, + ), + AgentTaskExtensionDecision::DenySessionMismatch + ); +} + #[test] fn managed_agent_task_extension_policy_fails_closed_outside_its_validity_window() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); + let policy = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, 9), + evaluate_agent_task_extension(&extension, &policy, session(31), 9), AgentTaskExtensionDecision::DenyPolicyNotYetValid ); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, 20), + evaluate_agent_task_extension(&extension, &policy, session(31), 20), AgentTaskExtensionDecision::DenyPolicyExpired ); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, u64::MAX), + evaluate_agent_task_extension(&extension, &policy, session(31), u64::MAX), AgentTaskExtensionDecision::DenyPolicyExpired ); } @@ -69,15 +94,15 @@ fn managed_agent_task_extension_policy_fails_closed_outside_its_validity_window( #[test] fn invalid_managed_extension_policy_window_fails_closed_before_membership() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let reversed = AgentTaskExtensionPolicy::new([extension.clone()], 20, 10); - let empty = AgentTaskExtensionPolicy::new([extension.clone()], 20, 20); + let reversed = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 20, 10); + let empty = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 20, 20); assert_eq!( - evaluate_agent_task_extension(&extension, &reversed, 15), + evaluate_agent_task_extension(&extension, &reversed, session(31), 15), AgentTaskExtensionDecision::DenyInvalidPolicyWindow ); assert_eq!( - evaluate_agent_task_extension(&extension, &empty, 20), + evaluate_agent_task_extension(&extension, &empty, session(31), 20), AgentTaskExtensionDecision::DenyInvalidPolicyWindow ); } @@ -85,14 +110,19 @@ fn invalid_managed_extension_policy_window_fails_closed_before_membership() { #[test] fn maximum_timestamp_window_remains_half_open_without_overflow() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()], u64::MAX - 1, u64::MAX); + let policy = AgentTaskExtensionPolicy::new( + session(31), + [extension.clone()], + u64::MAX - 1, + u64::MAX, + ); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, u64::MAX - 1), + evaluate_agent_task_extension(&extension, &policy, session(31), u64::MAX - 1), AgentTaskExtensionDecision::AllowManagedExtension ); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, u64::MAX), + evaluate_agent_task_extension(&extension, &policy, session(31), u64::MAX), AgentTaskExtensionDecision::DenyPolicyExpired ); } @@ -100,10 +130,10 @@ fn maximum_timestamp_window_remains_half_open_without_overflow() { #[test] fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); + let policy = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20); assert_eq!( - evaluate_agent_task_extension(&extension, &policy, 15), + evaluate_agent_task_extension(&extension, &policy, session(31), 15), AgentTaskExtensionDecision::AllowManagedExtension ); From 95e8d795b288bea5f53938733e20f43ff95227f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:16:44 +0900 Subject: [PATCH 02/10] style(extension): apply canonical session-policy rustfmt --- .../tests/agent_task_extension_policy.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index 3f0bd768f..acee680bb 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -35,12 +35,8 @@ fn empty_agent_task_extension_policy_denies_every_extension() { fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); - let policy = AgentTaskExtensionPolicy::new( - session(31), - [allowed.clone(), allowed.clone()], - 10, - 20, - ); + let policy = + AgentTaskExtensionPolicy::new(session(31), [allowed.clone(), allowed.clone()], 10, 20); assert_eq!( evaluate_agent_task_extension(&allowed, &policy, session(31), 10), @@ -110,12 +106,8 @@ fn invalid_managed_extension_policy_window_fails_closed_before_membership() { #[test] fn maximum_timestamp_window_remains_half_open_without_overflow() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new( - session(31), - [extension.clone()], - u64::MAX - 1, - u64::MAX, - ); + let policy = + AgentTaskExtensionPolicy::new(session(31), [extension.clone()], u64::MAX - 1, u64::MAX); assert_eq!( evaluate_agent_task_extension(&extension, &policy, session(31), u64::MAX - 1), From ce85f8ad87d8bafa31e9a7bc30704568ca3f8245 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:20:23 +0900 Subject: [PATCH 03/10] feat(extension): bind managed policy to browser session --- crates/originweave-policy/src/lib.rs | 44 ++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/originweave-policy/src/lib.rs b/crates/originweave-policy/src/lib.rs index 53d89119a..b38504863 100644 --- a/crates/originweave-policy/src/lib.rs +++ b/crates/originweave-policy/src/lib.rs @@ -18,13 +18,15 @@ pub use sensitive_data::{ use std::collections::BTreeSet; use originweave_core::{ - ActionRequest, ApprovalEvidence, ApprovalScope, Capability, ExecutionPurpose, ExtensionId, - InstructionSource, PolicyContext, RiskClass, RobotsDecision, SecretDelivery, SessionMode, + ActionRequest, ApprovalEvidence, ApprovalScope, BrowserSessionId, 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 { + browser_session: BrowserSessionId, managed_extensions: BTreeSet, valid_from: u64, valid_until: u64, @@ -33,19 +35,28 @@ pub struct AgentTaskExtensionPolicy { 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. - /// `valid_from` is inclusive and `valid_until` is exclusive. Both values are - /// opaque timestamps in the same caller-defined trusted time domain that will - /// be supplied to [`evaluate_agent_task_extension`]. This constructor does not - /// authenticate policy provenance or attest a clock; an invalid or empty - /// validity window is retained so evaluation can fail closed deterministically. + /// `browser_session` binds this policy to one OriginWeave browser-session + /// authority. It does not prove which Chromium profile is attached to that + /// session or authenticate enterprise-policy provenance. Duplicate identifiers + /// collapse to one exact managed identity. An empty iterator therefore + /// represents the default policy that admits no extension. `valid_from` is + /// inclusive and `valid_until` is exclusive. Both values are opaque timestamps + /// in the same caller-defined trusted time domain supplied to + /// [`evaluate_agent_task_extension`]. This constructor does not authenticate + /// policy provenance or attest a clock; an invalid or empty validity window is + /// retained so evaluation can fail closed deterministically. #[must_use] - pub fn new(managed_extensions: I, valid_from: u64, valid_until: u64) -> Self + pub fn new( + browser_session: BrowserSessionId, + managed_extensions: I, + valid_from: u64, + valid_until: u64, + ) -> Self where I: IntoIterator, { Self { + browser_session, managed_extensions: managed_extensions.into_iter().collect(), valid_from, valid_until, @@ -66,21 +77,27 @@ pub enum AgentTaskExtensionDecision { DenyPolicyNotYetValid, /// The trusted evaluation time is at or beyond the policy expiry boundary. DenyPolicyExpired, + /// The current OriginWeave browser session differs from the policy-bound session. + DenySessionMismatch, } /// 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 at `trusted_time`. +/// present in the policy-bound Agent Task session at `trusted_time`. /// `trusted_time`, [`AgentTaskExtensionPolicy::new`] `valid_from`, and `valid_until` /// must use the same caller-attested time domain; this function does not read or /// attest a clock. The validity window is half-open: `valid_from <= trusted_time < -/// valid_until`. Chromium permissions, installation state, native messaging, and +/// valid_until`. The current session is checked before allow-list membership so a +/// policy cannot be replayed across OriginWeave browser sessions or used there as +/// an extension-membership oracle. This does not attest Chromium profile identity. +/// 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, + current_session: BrowserSessionId, trusted_time: u64, ) -> AgentTaskExtensionDecision { if policy.valid_from >= policy.valid_until { @@ -92,6 +109,9 @@ pub fn evaluate_agent_task_extension( if trusted_time >= policy.valid_until { return AgentTaskExtensionDecision::DenyPolicyExpired; } + if policy.browser_session != current_session { + return AgentTaskExtensionDecision::DenySessionMismatch; + } if policy.managed_extensions.contains(extension_id) { AgentTaskExtensionDecision::AllowManagedExtension } else { From 912e0909169ed2fee1b26bce126f14e9390822bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:21:29 +0900 Subject: [PATCH 04/10] docs(changelog): record session-bound extension policy --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d89bd3675..47bbe8b16 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 +- Session-bound managed extension admission for isolated Agent Task profiles: policy is tied to one exact OriginWeave `BrowserSessionId`, mismatched sessions fail closed before allow-list membership is considered, and the boundary remains distinct from Chromium profile attestation and Agent capability grants. - Time-bounded managed extension admission for isolated Agent Task profiles with an explicit half-open validity window and caller-supplied trusted evaluation time; invalid, not-yet-valid, and expired policy states fail closed before exact extension allow-list membership is considered. - 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. From 676efa306e1877402640d021d6df6a5a7f215ad9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 09:12:32 +0900 Subject: [PATCH 05/10] style(extension): apply canonical policy test formatting --- .../tests/agent_task_extension_policy.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index 812c55488..605660b88 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -35,13 +35,8 @@ fn empty_agent_task_extension_policy_denies_every_extension() { fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); - let policy = AgentTaskExtensionPolicy::new( - session(31), - [allowed.clone(), allowed.clone()], - 10, - 20, - 10, - ); + let policy = + AgentTaskExtensionPolicy::new(session(31), [allowed.clone(), allowed.clone()], 10, 20, 10); assert_eq!( evaluate_agent_task_extension(&allowed, &policy, session(31), 10), @@ -132,13 +127,8 @@ fn managed_extension_policy_window_cannot_exceed_local_maximum() { #[test] fn maximum_timestamp_window_remains_half_open_without_overflow() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new( - session(31), - [extension.clone()], - u64::MAX - 1, - u64::MAX, - 1, - ); + let policy = + AgentTaskExtensionPolicy::new(session(31), [extension.clone()], u64::MAX - 1, u64::MAX, 1); assert_eq!( evaluate_agent_task_extension(&extension, &policy, session(31), u64::MAX - 1), From 33048e22b634f42d50ac26bc68dc3f4364612508 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 08:30:17 +0900 Subject: [PATCH 06/10] fix(stack): preserve live native-messaging authority --- crates/originweave-core/src/lib.rs | 24 +++++++++++++++++++ .../tests/native_messaging_authority.rs | 5 ++++ 2 files changed, 29 insertions(+) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index de817f130..b8cce88af 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1125,6 +1125,18 @@ impl NativeMessagingHostGrant { host_name, } } + + /// Return the extension identity granted native-messaging access. + #[must_use] + pub const fn extension_id(&self) -> &ExtensionId { + &self.extension_id + } + + /// Return the exact native-messaging host identity in this grant. + #[must_use] + pub const fn host_name(&self) -> &NativeMessagingHostName { + &self.host_name + } } /// One extension request to connect to an exact native-messaging host. @@ -1143,6 +1155,18 @@ impl NativeMessagingAccessRequest { host_name, } } + + /// Return the extension identity requesting native-messaging access. + #[must_use] + pub const fn extension_id(&self) -> &ExtensionId { + &self.extension_id + } + + /// Return the exact native-messaging host identity requested. + #[must_use] + pub const fn host_name(&self) -> &NativeMessagingHostName { + &self.host_name + } } /// Result of evaluating native-messaging access against one explicit host grant. diff --git a/crates/originweave-core/tests/native_messaging_authority.rs b/crates/originweave-core/tests/native_messaging_authority.rs index f0a830cda..5f4f611f1 100644 --- a/crates/originweave-core/tests/native_messaging_authority.rs +++ b/crates/originweave-core/tests/native_messaging_authority.rs @@ -54,7 +54,12 @@ fn native_messaging_requires_an_explicit_exact_extension_and_host_grant() { let other_host = host_name("com.contextualwisdom.other_host"); let grant = NativeMessagingHostGrant::new(allowed_extension.clone(), allowed_host.clone()); + assert_eq!(grant.extension_id(), &allowed_extension); + assert_eq!(grant.host_name(), &allowed_host); + let exact = NativeMessagingAccessRequest::new(allowed_extension.clone(), allowed_host.clone()); + assert_eq!(exact.extension_id(), &allowed_extension); + assert_eq!(exact.host_name(), &allowed_host); assert_eq!( evaluate_native_messaging_access(&exact, Some(&grant)), NativeMessagingAccessDecision::Allow From 6cc5211e4b7de3262368651446ea8be8227b650d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:01:57 +0900 Subject: [PATCH 07/10] test(policy): hide managed policy state across sessions --- .../tests/agent_task_extension_policy.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index 605660b88..221e702e6 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -68,6 +68,37 @@ fn managed_agent_task_extension_policy_is_not_reusable_across_sessions() { ); } +#[test] +fn mismatched_session_cannot_probe_policy_window_state() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let current_session = session(37); + + let invalid = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 20, 10, 10); + assert_eq!( + evaluate_agent_task_extension(&extension, &invalid, current_session, 15), + AgentTaskExtensionDecision::DenySessionMismatch + ); + + let overlong = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 21, 10); + assert_eq!( + evaluate_agent_task_extension(&extension, &overlong, current_session, 15), + AgentTaskExtensionDecision::DenySessionMismatch + ); + + let not_yet_valid = + AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20, 10); + assert_eq!( + evaluate_agent_task_extension(&extension, ¬_yet_valid, current_session, 9), + AgentTaskExtensionDecision::DenySessionMismatch + ); + + let expired = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20, 10); + assert_eq!( + evaluate_agent_task_extension(&extension, &expired, current_session, 20), + AgentTaskExtensionDecision::DenySessionMismatch + ); +} + #[test] fn managed_agent_task_extension_policy_fails_closed_outside_its_validity_window() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); From 52e33a06d665a2c8c299edac61ffb1b3d5d51c70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:05:13 +0900 Subject: [PATCH 08/10] style(policy): apply canonical session-isolation test formatting --- crates/originweave-policy/tests/agent_task_extension_policy.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index 221e702e6..ec23d0f13 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -85,8 +85,7 @@ fn mismatched_session_cannot_probe_policy_window_state() { AgentTaskExtensionDecision::DenySessionMismatch ); - let not_yet_valid = - AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20, 10); + let not_yet_valid = AgentTaskExtensionPolicy::new(session(31), [extension.clone()], 10, 20, 10); assert_eq!( evaluate_agent_task_extension(&extension, ¬_yet_valid, current_session, 9), AgentTaskExtensionDecision::DenySessionMismatch From 85a9fa723cc9efddac7008dcb5ef92e81a8049b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:07:45 +0900 Subject: [PATCH 09/10] fix(policy): hide managed policy state across sessions --- crates/originweave-policy/src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/originweave-policy/src/lib.rs b/crates/originweave-policy/src/lib.rs index 4658d28c1..8ac0ae50f 100644 --- a/crates/originweave-policy/src/lib.rs +++ b/crates/originweave-policy/src/lib.rs @@ -95,11 +95,12 @@ pub enum AgentTaskExtensionDecision { /// and `maximum_window` must use one caller-attested time domain and compatible /// units; this function does not read or attest a clock. The validity window is /// half-open (`valid_from <= trusted_time < valid_until`) and must not exceed the -/// reviewed local maximum. The current session is checked before allow-list -/// membership so a policy cannot be replayed across OriginWeave browser sessions -/// or used there as an extension-membership oracle. This does not attest Chromium -/// profile identity. Chromium permissions, installation state, native messaging, -/// and [`originweave_core::ExtensionAgentGrant`] remain separate authorities. +/// reviewed local maximum. The current session is checked before policy-window +/// or allow-list evaluation so a policy cannot be replayed across OriginWeave +/// browser sessions or used there as a policy-state or extension-membership +/// oracle. This does not attest Chromium profile identity. Chromium permissions, +/// installation state, native messaging, and [`originweave_core::ExtensionAgentGrant`] +/// remain separate authorities. #[must_use] pub fn evaluate_agent_task_extension( extension_id: &ExtensionId, @@ -107,6 +108,9 @@ pub fn evaluate_agent_task_extension( current_session: BrowserSessionId, trusted_time: u64, ) -> AgentTaskExtensionDecision { + if policy.browser_session != current_session { + return AgentTaskExtensionDecision::DenySessionMismatch; + } if policy.valid_from >= policy.valid_until || policy.maximum_window == 0 { return AgentTaskExtensionDecision::DenyInvalidPolicyWindow; } @@ -119,9 +123,6 @@ pub fn evaluate_agent_task_extension( if trusted_time >= policy.valid_until { return AgentTaskExtensionDecision::DenyPolicyExpired; } - if policy.browser_session != current_session { - return AgentTaskExtensionDecision::DenySessionMismatch; - } if policy.managed_extensions.contains(extension_id) { AgentTaskExtensionDecision::AllowManagedExtension } else { From 7b37d07b5e5167f9bb920c565b56edf426170b53 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:14:56 +0900 Subject: [PATCH 10/10] docs(changelog): record session-first policy evaluation --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47bbe8b16..07fa0fb7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added -- Session-bound managed extension admission for isolated Agent Task profiles: policy is tied to one exact OriginWeave `BrowserSessionId`, mismatched sessions fail closed before allow-list membership is considered, and the boundary remains distinct from Chromium profile attestation and Agent capability grants. +- Session-bound managed extension admission for isolated Agent Task profiles: policy is tied to one exact OriginWeave `BrowserSessionId`, mismatched sessions fail closed before policy-window or allow-list state is evaluated, and the boundary remains distinct from Chromium profile attestation and Agent capability grants. - Time-bounded managed extension admission for isolated Agent Task profiles with an explicit half-open validity window and caller-supplied trusted evaluation time; invalid, not-yet-valid, and expired policy states fail closed before exact extension allow-list membership is considered. - 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.