diff --git a/crates/originweave-policy/tests/extension_secret_isolation.rs b/crates/originweave-policy/tests/extension_secret_isolation.rs new file mode 100644 index 00000000..ad4293d0 --- /dev/null +++ b/crates/originweave-policy/tests/extension_secret_isolation.rs @@ -0,0 +1,90 @@ +#![allow(clippy::expect_used)] + +use std::collections::BTreeSet; + +use originweave_core::{ + ActionIntentDigest, ActionKind, ActionRequest, ApprovalEvidence, BrowserSessionId, + BrowsingContextId, Capability, ExecutionPurpose, ExtensionAccessDecision, + ExtensionAccessRequest, ExtensionAgentCapability, ExtensionAgentGrant, ExtensionId, + InstructionSource, Origin, PolicyContext, RiskClass, RobotsDecision, SecretDelivery, + SessionMode, evaluate_extension_access, +}; +use originweave_policy::{Decision, evaluate}; + +const VALID_INTENT: &str = + "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + +fn extension_id() -> ExtensionId { + ExtensionId::parse("abcdefghijklmnopabcdefghijklmnop").expect("valid extension id") +} + +fn browser_session() -> BrowserSessionId { + BrowserSessionId::new(7).expect("nonzero browser session") +} + +fn browsing_context() -> BrowsingContextId { + BrowsingContextId::new(11).expect("nonzero browsing context") +} + +fn origin() -> Origin { + Origin::parse("https://login.example").expect("valid test origin") +} + +fn intent() -> ActionIntentDigest { + ActionIntentDigest::parse(VALID_INTENT).expect("valid intent digest") +} + +fn action_proposal_grant() -> ExtensionAgentGrant { + ExtensionAgentGrant::new( + extension_id(), + browser_session(), + browsing_context(), + [ExtensionAgentCapability::ProposeTypedAction], + ) +} + +fn assert_extension_can_propose(grant: &ExtensionAgentGrant) { + let request = ExtensionAccessRequest::new( + extension_id(), + browser_session(), + browsing_context(), + ExtensionAgentCapability::ProposeTypedAction, + ); + assert_eq!( + evaluate_extension_access(&request, Some(grant)), + ExtensionAccessDecision::Allow + ); +} + +fn secret_context(site: &Origin) -> PolicyContext { + PolicyContext::new( + SessionMode::AgentTask, + ExecutionPurpose::UserDelegatedTask, + BTreeSet::from([Capability::FillSecret]), + BTreeSet::from([site.clone()]), + BTreeSet::from([site.clone()]), + RobotsDecision::Allowed, + ApprovalEvidence::None, + ) +} + +#[test] +fn extension_action_grant_cannot_skip_secret_broker_approval() { + let grant = action_proposal_grant(); + assert_extension_can_propose(&grant); + + let site = origin(); + let proposed = ActionRequest::new( + ActionKind::FillSecret, + site.clone(), + site.clone(), + InstructionSource::User, + SecretDelivery::BrokerHandle, + intent(), + ); + + assert_eq!( + evaluate(&proposed, &secret_context(&site)), + Decision::RequireApproval(RiskClass::R3) + ); +}