fix(permissions): validate network access flow

This commit is contained in:
2026-07-26 10:52:46 +02:00
parent 3047dc9195
commit 13fb538880
70 changed files with 4784 additions and 158 deletions

View File

@ -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"
);
}
}