fix(permissions): validate network access flow
This commit is contained in:
@ -513,6 +513,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start Claude ok");
|
||||
@ -531,6 +532,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start Codex ok");
|
||||
@ -563,6 +565,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start OpenAI-compatible ok");
|
||||
@ -592,7 +595,16 @@ mod tests {
|
||||
};
|
||||
|
||||
let session = factory
|
||||
.start(&codex, &ctx, &cwd(), &SessionPlan::None, None, &[], None)
|
||||
.start(
|
||||
&codex,
|
||||
&ctx,
|
||||
&cwd(),
|
||||
&SessionPlan::None,
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start Codex ok");
|
||||
let content = drain_final(session.as_ref()).await;
|
||||
@ -629,6 +641,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start resume ok");
|
||||
@ -1444,6 +1457,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start resume codex");
|
||||
@ -1469,6 +1483,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start assign");
|
||||
@ -1501,6 +1516,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -1667,8 +1683,7 @@ mod tests {
|
||||
// =====================================================================
|
||||
// DURCISSEMENT QA (lot D3, §17.9 D3 — fix codex 0.137) — autonomie
|
||||
// d'écriture Codex : la commande générée porte EXACTEMENT
|
||||
// [exec, --json, --skip-git-repo-check, --sandbox, workspace-write,
|
||||
// --add-dir, <project-root>, <prompt>]
|
||||
// [exec, --json, --skip-git-repo-check, --sandbox, <mode>, --add-dir, <project-root>, <prompt>]
|
||||
// (`resume <id>` après les options `exec` pour une reprise). Le flag `--ask-for-approval never`
|
||||
// a été RETIRÉ : `codex exec` 0.137 ne le connaît pas (`error: unexpected
|
||||
// argument`) et est déjà non-interactif. Ce test verrouille l'argv exact pour
|
||||
@ -1759,6 +1774,186 @@ mod tests {
|
||||
let _ = std::fs::remove_file(&argv);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_read_only_policy_omits_add_dir_and_approval_flag() {
|
||||
let (cmd, argv) = make_recording_fake(&[
|
||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
||||
]);
|
||||
let session = CodexExecSession::new_with_policy(
|
||||
SessionId::new_random(),
|
||||
cmd.clone(),
|
||||
"/",
|
||||
None,
|
||||
"read-only",
|
||||
vec!["/project/root".to_owned()],
|
||||
Some(false),
|
||||
Vec::new(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let _ = session.send("analyse").await.expect("send ok");
|
||||
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
||||
let args: Vec<&str> = recorded.lines().collect();
|
||||
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"exec",
|
||||
"--json",
|
||||
"--skip-git-repo-check",
|
||||
"--sandbox",
|
||||
"read-only",
|
||||
"-c",
|
||||
"sandbox_workspace_write.network_access=false",
|
||||
"analyse",
|
||||
],
|
||||
"read-only must not carry --add-dir or --ask-for-approval and must disable Codex network, got: {args:?}"
|
||||
);
|
||||
let _ = std::fs::remove_file(&cmd);
|
||||
let _ = std::fs::remove_file(&argv);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_workspace_write_policy_carries_network_config_override() {
|
||||
let (cmd, argv) = make_recording_fake(&[
|
||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
||||
]);
|
||||
let session = CodexExecSession::new_with_policy(
|
||||
SessionId::new_random(),
|
||||
cmd.clone(),
|
||||
"/",
|
||||
None,
|
||||
"workspace-write",
|
||||
vec!["/project/root".to_owned()],
|
||||
Some(true),
|
||||
Vec::new(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let _ = session.send("analyse").await.expect("send ok");
|
||||
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
||||
let args: Vec<&str> = recorded.lines().collect();
|
||||
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"exec",
|
||||
"--json",
|
||||
"--skip-git-repo-check",
|
||||
"--sandbox",
|
||||
"workspace-write",
|
||||
"--add-dir",
|
||||
"/project/root",
|
||||
"-c",
|
||||
"sandbox_workspace_write.network_access=true",
|
||||
"analyse",
|
||||
],
|
||||
"workspace-write Allow must pass the official Codex network override, got: {args:?}"
|
||||
);
|
||||
let _ = std::fs::remove_file(&cmd);
|
||||
let _ = std::fs::remove_file(&argv);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_structured_session_forwards_network_env() {
|
||||
use std::io::Write as _;
|
||||
|
||||
let dir = std::env::current_dir()
|
||||
.expect("cwd")
|
||||
.join("target")
|
||||
.join("test-fakes")
|
||||
.join("session");
|
||||
std::fs::create_dir_all(&dir).expect("fake dir");
|
||||
let bin = dir.join(format!("idea-env-cli-{}", std::process::id()));
|
||||
let env_file = dir.join(format!("idea-env-rec-{}", std::process::id()));
|
||||
let script = format!(
|
||||
"#!/bin/sh\nprintf '%s\\n' \"$CODEX_SANDBOX_NETWORK_DISABLED\" > '{}'\nprintf '%s\\n' '{}'\n",
|
||||
env_file.display(),
|
||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#
|
||||
);
|
||||
{
|
||||
let mut f = std::fs::File::create(&bin).expect("create fake");
|
||||
f.write_all(script.as_bytes()).expect("write fake");
|
||||
f.sync_all().expect("sync fake");
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut p = std::fs::metadata(&bin).unwrap().permissions();
|
||||
p.set_mode(0o755);
|
||||
std::fs::set_permissions(&bin, p).unwrap();
|
||||
}
|
||||
super::conformance::wait_until_executable(&bin);
|
||||
|
||||
let session = CodexExecSession::new(
|
||||
SessionId::new_random(),
|
||||
bin.to_string_lossy().into_owned(),
|
||||
"/",
|
||||
None,
|
||||
Vec::new(),
|
||||
vec![("CODEX_SANDBOX_NETWORK_DISABLED".to_owned(), "0".to_owned())],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let _ = session.send("salut").await.expect("send ok");
|
||||
|
||||
let recorded = std::fs::read_to_string(&env_file).expect("env file");
|
||||
assert_eq!(recorded.trim(), "0");
|
||||
let _ = std::fs::remove_file(&bin);
|
||||
let _ = std::fs::remove_file(&env_file);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_structured_policy_overrides_stale_network_env() {
|
||||
use std::io::Write as _;
|
||||
|
||||
let dir = std::env::current_dir()
|
||||
.expect("cwd")
|
||||
.join("target")
|
||||
.join("test-fakes")
|
||||
.join("session");
|
||||
std::fs::create_dir_all(&dir).expect("fake dir");
|
||||
let bin = dir.join(format!("idea-env-override-cli-{}", std::process::id()));
|
||||
let env_file = dir.join(format!("idea-env-override-rec-{}", std::process::id()));
|
||||
let script = format!(
|
||||
"#!/bin/sh\nprintf '%s\\n' \"$CODEX_SANDBOX_NETWORK_DISABLED\" > '{}'\nprintf '%s\\n' '{}'\n",
|
||||
env_file.display(),
|
||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#
|
||||
);
|
||||
{
|
||||
let mut f = std::fs::File::create(&bin).expect("create fake");
|
||||
f.write_all(script.as_bytes()).expect("write fake");
|
||||
f.sync_all().expect("sync fake");
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut p = std::fs::metadata(&bin).unwrap().permissions();
|
||||
p.set_mode(0o755);
|
||||
std::fs::set_permissions(&bin, p).unwrap();
|
||||
}
|
||||
super::conformance::wait_until_executable(&bin);
|
||||
|
||||
let session = CodexExecSession::new_with_policy(
|
||||
SessionId::new_random(),
|
||||
bin.to_string_lossy().into_owned(),
|
||||
"/",
|
||||
None,
|
||||
"workspace-write",
|
||||
Vec::new(),
|
||||
Some(true),
|
||||
vec![("CODEX_SANDBOX_NETWORK_DISABLED".to_owned(), "1".to_owned())],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let _ = session.send("salut").await.expect("send ok");
|
||||
|
||||
let recorded = std::fs::read_to_string(&env_file).expect("env file");
|
||||
assert_eq!(recorded.trim(), "0");
|
||||
let _ = std::fs::remove_file(&bin);
|
||||
let _ = std::fs::remove_file(&env_file);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// LS2 — adapter Claude niveau 1 (§21) : `parse_reset_ms` (parseur ISO-8601
|
||||
// À LA MAIN + heuristique secondes/ms + days_from_civil) et le mapping
|
||||
|
||||
Reference in New Issue
Block a user