fix(permissions): validate network access flow
This commit is contained in:
@ -9,15 +9,17 @@ use application::{
|
||||
use async_trait::async_trait;
|
||||
use domain::ports::{
|
||||
AgentSession, AgentSessionError, AgentSessionFactory, ReplyStream, RuntimeError, SessionPlan,
|
||||
StructuredSessionEnvironment, StructuredSessionEnvironmentPreparer,
|
||||
StoreError, StructuredProviderLaunchPolicy, StructuredSessionEnvironment,
|
||||
StructuredSessionEnvironmentPreparer, SystemPermissionStore,
|
||||
};
|
||||
use domain::profile::StructuredAdapter;
|
||||
use domain::{
|
||||
AgentProfile, AgentToolPolicy, AgentToolPolicyStore, AssistantContextError,
|
||||
AssistantContextProvider, ContextInjection, DomainEvent, EventBus, EventStream, Issue,
|
||||
IssueActor, IssueCarnet, IssueId, IssueListFilter, IssueNumber, IssuePriority, IssueRef,
|
||||
IssueStatus, IssueStore, IssueStoreError, IssueVersion, MarkdownDoc, PreparedContext,
|
||||
ProfileId, ProfileStore, Project, ProjectId, ProjectPath, RemoteRef, SessionId, StoreError,
|
||||
IssueStatus, IssueStore, IssueStoreError, IssueVersion, MarkdownDoc, NetworkPolicy,
|
||||
PreparedContext, ProfileId, ProfileStore, Project, ProjectId, ProjectPath,
|
||||
ProjectSystemPermissions, RemoteRef, SessionId, SystemPermissionSet,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -215,6 +217,7 @@ impl StructuredSessionEnvironmentPreparer for FakeEnvironmentPreparer {
|
||||
"CODEX_HOME".to_owned(),
|
||||
"/tmp/app-data/assistant/tickets/1/7/.codex".to_owned(),
|
||||
)],
|
||||
structured_policy: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -254,6 +257,7 @@ struct FakeFactory {
|
||||
Option<String>,
|
||||
Vec<(String, String)>,
|
||||
Option<domain::SandboxPlan>,
|
||||
Option<StructuredProviderLaunchPolicy>,
|
||||
)>,
|
||||
>,
|
||||
shutdowns: Arc<Mutex<usize>>,
|
||||
@ -274,6 +278,7 @@ impl AgentSessionFactory for FakeFactory {
|
||||
requester: Option<&str>,
|
||||
env: &[(String, String)],
|
||||
sandbox: Option<&domain::SandboxPlan>,
|
||||
structured_policy: Option<&StructuredProviderLaunchPolicy>,
|
||||
) -> Result<Arc<dyn AgentSession>, AgentSessionError> {
|
||||
self.starts.lock().unwrap().push((
|
||||
ctx.clone(),
|
||||
@ -282,6 +287,7 @@ impl AgentSessionFactory for FakeFactory {
|
||||
requester.map(str::to_owned),
|
||||
env.to_vec(),
|
||||
sandbox.cloned(),
|
||||
structured_policy.cloned(),
|
||||
));
|
||||
Ok(Arc::new(FakeSession {
|
||||
id: SessionId::new_random(),
|
||||
@ -320,6 +326,26 @@ impl EventBus for SpyBus {
|
||||
}
|
||||
}
|
||||
|
||||
struct FakeSystemPermissionStore(ProjectSystemPermissions);
|
||||
|
||||
#[async_trait]
|
||||
impl SystemPermissionStore for FakeSystemPermissionStore {
|
||||
async fn load_system_permissions(
|
||||
&self,
|
||||
_project: &Project,
|
||||
) -> Result<ProjectSystemPermissions, StoreError> {
|
||||
Ok(self.0.clone())
|
||||
}
|
||||
|
||||
async fn save_system_permissions(
|
||||
&self,
|
||||
_project: &Project,
|
||||
_permissions: &ProjectSystemPermissions,
|
||||
) -> Result<(), StoreError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn open_then_close_ticket_assistant_sets_policy_injects_context_and_emits_events() {
|
||||
let issue = issue(7);
|
||||
@ -415,3 +441,56 @@ async fn open_then_close_ticket_assistant_sets_policy_injects_context_and_emits_
|
||||
DomainEvent::TicketAssistantClosed { issue_ref } if issue_ref == issue.reference()
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_ticket_assistant_uses_project_network_permission_for_structured_policy() {
|
||||
for (policy, network_access) in [
|
||||
(Some(NetworkPolicy::Allow), true),
|
||||
(Some(NetworkPolicy::Deny), false),
|
||||
(Some(NetworkPolicy::Ask), false),
|
||||
(None, false),
|
||||
] {
|
||||
let issue = issue(7);
|
||||
let profile_id = ProfileId::from_uuid(Uuid::from_u128(9));
|
||||
let structured = Arc::new(StructuredSessions::new());
|
||||
let policies = Arc::new(FakePolicies::default());
|
||||
let events = Arc::new(SpyBus::default());
|
||||
let factory = Arc::new(FakeFactory::default());
|
||||
let system_doc =
|
||||
ProjectSystemPermissions::new(Some(SystemPermissionSet::new(policy)), Vec::new());
|
||||
let open = OpenTicketAssistant::new(
|
||||
Arc::new(FakeIssues {
|
||||
issue: issue.clone(),
|
||||
}),
|
||||
Arc::new(FakeProfiles {
|
||||
profile: profile(profile_id).with_structured_adapter(StructuredAdapter::Codex),
|
||||
}),
|
||||
Arc::new(FakeAssistantContext::default()),
|
||||
Arc::new(FakeEnvironmentPreparer::default()),
|
||||
factory.clone(),
|
||||
structured,
|
||||
policies,
|
||||
events,
|
||||
)
|
||||
.with_system_permission_store(Arc::new(FakeSystemPermissionStore(system_doc)));
|
||||
|
||||
open.execute(OpenTicketAssistantInput {
|
||||
project: project(),
|
||||
issue_ref: issue.reference(),
|
||||
profile_id,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let starts = factory.starts.lock().unwrap();
|
||||
assert_eq!(
|
||||
starts[0].6,
|
||||
Some(StructuredProviderLaunchPolicy::Codex {
|
||||
sandbox_mode: "workspace-write".to_owned(),
|
||||
writable_roots: vec!["/tmp/project".to_owned()],
|
||||
network_access,
|
||||
}),
|
||||
"policy {policy:?} must map to the Codex structured network flag"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user