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

@ -154,8 +154,12 @@ pub struct CodexExecSession {
command: String,
/// Répertoire de travail (run dir isolé §14.1).
cwd: String,
/// Codex CLI sandbox mode passed to `codex exec --sandbox`.
sandbox_mode: String,
/// Project/workspace roots that must be writable in Codex's CLI sandbox.
writable_roots: Vec<String>,
/// Structured policy projection of Codex workspace-write sandbox network access.
network_access: Option<bool>,
/// Variables d'environnement préparées au lancement (ex. `CODEX_HOME` isolé).
env: Vec<(String, String)>,
/// Id de conversation **du moteur** Codex, capté au premier tour, `None` avant.
@ -183,12 +187,44 @@ impl CodexExecSession {
env: Vec<(String, String)>,
sandbox: Option<SandboxPlan>,
sandbox_enforcer: Option<Arc<dyn SandboxEnforcer>>,
) -> Self {
Self::new_with_policy(
id,
command,
cwd,
seed_conversation_id,
"workspace-write",
writable_roots,
None,
env,
sandbox,
sandbox_enforcer,
)
}
/// Construit l'adapter avec la politique compatible `codex exec` résolue par
/// `LaunchAgent`.
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new_with_policy(
id: SessionId,
command: impl Into<String>,
cwd: impl Into<String>,
seed_conversation_id: Option<String>,
sandbox_mode: impl Into<String>,
writable_roots: Vec<String>,
network_access: Option<bool>,
env: Vec<(String, String)>,
sandbox: Option<SandboxPlan>,
sandbox_enforcer: Option<Arc<dyn SandboxEnforcer>>,
) -> Self {
Self {
id,
command: command.into(),
cwd: cwd.into(),
sandbox_mode: sandbox_mode.into(),
writable_roots,
network_access,
env,
conversation_id: Mutex::new(seed_conversation_id),
sandbox,
@ -217,28 +253,54 @@ impl CodexExecSession {
let conversation_id = self.conversation_id.lock().expect("mutex sain").clone();
args.push("--json".to_owned());
args.push("--skip-git-repo-check".to_owned());
args.push("--sandbox".to_owned());
args.push("workspace-write".to_owned());
for root in self.writable_roots.iter().filter(|root| !root.is_empty()) {
args.push("--add-dir".to_owned());
args.push(root.clone());
if !self.sandbox_mode.trim().is_empty() {
args.push("--sandbox".to_owned());
args.push(self.sandbox_mode.clone());
}
if self.sandbox_mode == "workspace-write" {
for root in self.writable_roots.iter().filter(|root| !root.is_empty()) {
args.push("--add-dir".to_owned());
args.push(root.clone());
}
}
if let Some(network_access) = self.network_access {
args.push("-c".to_owned());
args.push(format!(
"sandbox_workspace_write.network_access={network_access}"
));
}
if let Some(id) = conversation_id {
args.push("resume".to_owned());
args.push(id);
}
args.push(prompt.to_owned());
let mut env = self.env.clone();
if let Some(network_access) = self.network_access {
upsert_env(
&mut env,
"CODEX_SANDBOX_NETWORK_DISABLED",
if network_access { "0" } else { "1" },
);
}
SpawnLine {
command: self.command.clone(),
args,
cwd: self.cwd.clone(),
env: self.env.clone(),
env,
stdin: None,
sandbox: self.sandbox.clone(),
}
}
}
fn upsert_env(env: &mut Vec<(String, String)>, key: &str, value: &str) {
if let Some((_, existing)) = env.iter_mut().find(|(k, _)| k == key) {
*existing = value.to_owned();
} else {
env.push((key.to_owned(), value.to_owned()));
}
}
#[async_trait]
impl AgentSession for CodexExecSession {
fn id(&self) -> SessionId {