Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
36 changes: 32 additions & 4 deletions crates/originweave-policy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,29 @@ use originweave_core::{
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentTaskExtensionPolicy {
managed_extensions: BTreeSet<ExtensionId>,
valid_from: u64,
valid_until: u64,
}

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.
#[must_use]
pub fn new<I>(managed_extensions: I) -> Self
pub fn new<I>(managed_extensions: I, valid_from: u64, valid_until: u64) -> Self
where
I: IntoIterator<Item = ExtensionId>,
{
Self {
managed_extensions: managed_extensions.into_iter().collect(),
valid_from,
valid_until,
}
}
}
Expand All @@ -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 {
Expand Down
64 changes: 57 additions & 7 deletions crates/originweave-policy/tests/agent_task_extension_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
Expand All @@ -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
);

Expand Down
Loading