From f4521bacf98f052f275fc87d0f75c108f6e096e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 06:24:51 +0900 Subject: [PATCH 1/3] test(extension): require managed policy freshness --- .../tests/agent_task_extension_policy.rs | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index 8ea2f0d..f82a624 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([]); + let policy = AgentTaskExtensionPolicy::new([], 10, 20); let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); assert_eq!( - evaluate_agent_task_extension(&extension, &policy), + evaluate_agent_task_extension(&extension, &policy, 15), AgentTaskExtensionDecision::DenyNotManaged ); } @@ -35,25 +35,75 @@ 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()]); + let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()], 10, 20); assert_eq!( - evaluate_agent_task_extension(&allowed, &policy), + evaluate_agent_task_extension(&allowed, &policy, 10), AgentTaskExtensionDecision::AllowManagedExtension ); assert_eq!( - evaluate_agent_task_extension(&other, &policy), + evaluate_agent_task_extension(&other, &policy, 19), AgentTaskExtensionDecision::DenyNotManaged ); } +#[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); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, 9), + AgentTaskExtensionDecision::DenyPolicyNotYetValid + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, 20), + AgentTaskExtensionDecision::DenyPolicyExpired + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, u64::MAX), + AgentTaskExtensionDecision::DenyPolicyExpired + ); +} + +#[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); + + assert_eq!( + evaluate_agent_task_extension(&extension, &reversed, 15), + AgentTaskExtensionDecision::DenyInvalidPolicyWindow + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &empty, 20), + AgentTaskExtensionDecision::DenyInvalidPolicyWindow + ); +} + +#[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); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, u64::MAX - 1), + AgentTaskExtensionDecision::AllowManagedExtension + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, u64::MAX), + AgentTaskExtensionDecision::DenyPolicyExpired + ); +} + #[test] fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()]); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); assert_eq!( - evaluate_agent_task_extension(&extension, &policy), + evaluate_agent_task_extension(&extension, &policy, 15), AgentTaskExtensionDecision::AllowManagedExtension ); From 66dbc574518740b8c09a558f7b54e7f5629256fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 06:27:57 +0900 Subject: [PATCH 2/3] feat(extension): fail closed on stale managed policy --- crates/originweave-policy/src/lib.rs | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/originweave-policy/src/lib.rs b/crates/originweave-policy/src/lib.rs index 3dda94b..53d8911 100644 --- a/crates/originweave-policy/src/lib.rs +++ b/crates/originweave-policy/src/lib.rs @@ -26,6 +26,8 @@ use originweave_core::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct AgentTaskExtensionPolicy { managed_extensions: BTreeSet, + valid_from: u64, + valid_until: u64, } impl AgentTaskExtensionPolicy { @@ -33,13 +35,20 @@ impl AgentTaskExtensionPolicy { /// /// 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. #[must_use] - pub fn new(managed_extensions: I) -> Self + pub fn new(managed_extensions: I, valid_from: u64, valid_until: u64) -> Self where I: IntoIterator, { Self { managed_extensions: managed_extensions.into_iter().collect(), + valid_from, + valid_until, } } } @@ -51,19 +60,38 @@ pub enum AgentTaskExtensionDecision { AllowManagedExtension, /// The extension identity is absent from the managed allow-list. DenyNotManaged, + /// The configured policy validity window is empty or reversed. + DenyInvalidPolicyWindow, + /// The trusted evaluation time precedes the policy validity window. + DenyPolicyNotYetValid, + /// The trusted evaluation time is at or beyond the policy expiry boundary. + DenyPolicyExpired, } /// 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. +/// present in the caller's managed Agent Task profile 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 +/// [`originweave_core::ExtensionAgentGrant`] remain separate authorities. #[must_use] pub fn evaluate_agent_task_extension( extension_id: &ExtensionId, policy: &AgentTaskExtensionPolicy, + trusted_time: u64, ) -> AgentTaskExtensionDecision { + if policy.valid_from >= policy.valid_until { + return AgentTaskExtensionDecision::DenyInvalidPolicyWindow; + } + if trusted_time < policy.valid_from { + return AgentTaskExtensionDecision::DenyPolicyNotYetValid; + } + if trusted_time >= policy.valid_until { + return AgentTaskExtensionDecision::DenyPolicyExpired; + } if policy.managed_extensions.contains(extension_id) { AgentTaskExtensionDecision::AllowManagedExtension } else { From 759d2f04d81dcf52bca29b88d860fb9aaeca56e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 07:16:58 +0900 Subject: [PATCH 3/3] docs(changelog): record extension policy freshness --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b934da..d89bd36 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 +- 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. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules.