fix(agents): ne relaye plus le plan de sandbox OS aux providers structurés — #227
Les grants Landlock RO/RW empêchaient la custom CLI de démarrer côté web téléphone avant d'atteindre le project root. Les providers structurés/headless gèrent leur propre sandbox compatible CLI.
This commit is contained in:
@ -2439,7 +2439,7 @@ impl LaunchAgent {
|
|||||||
input.node_id,
|
input.node_id,
|
||||||
size,
|
size,
|
||||||
&spec.env,
|
&spec.env,
|
||||||
spec.sandbox.as_ref(),
|
None,
|
||||||
structured_policy.as_ref(),
|
structured_policy.as_ref(),
|
||||||
launch_managed_temp,
|
launch_managed_temp,
|
||||||
local_model_server_guard,
|
local_model_server_guard,
|
||||||
@ -2547,8 +2547,9 @@ impl LaunchAgent {
|
|||||||
launch_managed_temp: Option<LaunchManagedTemp>,
|
launch_managed_temp: Option<LaunchManagedTemp>,
|
||||||
model_server_guard: Option<ModelServerUseGuard>,
|
model_server_guard: Option<ModelServerUseGuard>,
|
||||||
) -> Result<LaunchAgentOutput, AppError> {
|
) -> Result<LaunchAgentOutput, AppError> {
|
||||||
// Relaie le plan de sandbox OS (lot LP4-4) à la fabrique : `spec.sandbox`,
|
// Structured/headless providers manage their own CLI-compatible sandbox
|
||||||
// déjà compilé (pur, domaine) en step 5d. `None` ⇒ exécution native inchangée.
|
// policy. Do not relay the OS SandboxPlan here: RO/RW Landlock grants can
|
||||||
|
// make custom CLIs fail before they reach the project root.
|
||||||
let session = match factory
|
let session = match factory
|
||||||
.start(
|
.start(
|
||||||
profile,
|
profile,
|
||||||
|
|||||||
@ -4277,6 +4277,11 @@ async fn codex_structured_policy_carries_exec_safe_sandbox_roots_and_network() {
|
|||||||
})],
|
})],
|
||||||
"write-denied Codex structured policy must be exec-safe and omit writable roots"
|
"write-denied Codex structured policy must be exec-safe and omit writable roots"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
factory.sandboxes(),
|
||||||
|
vec![None],
|
||||||
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn launch_codex_structured_for_project(
|
async fn launch_codex_structured_for_project(
|
||||||
@ -4335,23 +4340,10 @@ fn assert_codex_writable_roots(
|
|||||||
"Codex writable_roots must mirror resolved write permission roots"
|
"Codex writable_roots must mirror resolved write permission roots"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(sandboxes.len(), 1, "exactly one structured sandbox plan");
|
|
||||||
let sandbox = sandboxes.into_iter().next().unwrap().expect("sandbox plan");
|
|
||||||
let sandbox_roots = sandbox
|
|
||||||
.allowed
|
|
||||||
.iter()
|
|
||||||
.map(|grant| grant.abs_root.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sandbox_roots, writable_roots,
|
sandboxes,
|
||||||
"OS sandbox write roots must match Codex writable_roots"
|
vec![None],
|
||||||
);
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
assert!(
|
|
||||||
sandbox
|
|
||||||
.allowed
|
|
||||||
.iter()
|
|
||||||
.all(|grant| grant.access == domain::sandbox::PathAccess::RW),
|
|
||||||
"Codex writable roots must be added as write grants"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4530,10 +4522,10 @@ async fn codex_structured_broad_write_with_subpath_deny_does_not_grant_project_r
|
|||||||
})],
|
})],
|
||||||
"Codex must not receive project-root write when a subpath write deny cannot be carved out"
|
"Codex must not receive project-root write when a subpath write deny cannot be carved out"
|
||||||
);
|
);
|
||||||
let sandbox = factory.sandboxes().remove(0).expect("sandbox plan");
|
assert_eq!(
|
||||||
assert!(
|
factory.sandboxes(),
|
||||||
sandbox.allowed.is_empty(),
|
vec![None],
|
||||||
"OS sandbox must not grant a broad root that would include the denied subpath"
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
);
|
);
|
||||||
let _ = std::fs::remove_dir_all(&root);
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
}
|
}
|
||||||
@ -4628,7 +4620,7 @@ async fn codex_structured_disjoint_write_allow_survives_unrelated_subpath_deny()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn managed_temp_exposure_enriches_codex_policy_and_os_sandbox_from_same_resolution() {
|
async fn managed_temp_exposure_enriches_codex_policy_without_structured_os_sandbox() {
|
||||||
let root = fresh_temp_dir("managed-temp-exposure");
|
let root = fresh_temp_dir("managed-temp-exposure");
|
||||||
let managed_root = root.join("tmp").join("agent-output");
|
let managed_root = root.join("tmp").join("agent-output");
|
||||||
std::fs::create_dir_all(&managed_root).unwrap();
|
std::fs::create_dir_all(&managed_root).unwrap();
|
||||||
@ -4689,20 +4681,10 @@ async fn managed_temp_exposure_enriches_codex_policy_and_os_sandbox_from_same_re
|
|||||||
})],
|
})],
|
||||||
"Codex writable_roots must include permission roots plus the resolved managed-temp path"
|
"Codex writable_roots must include permission roots plus the resolved managed-temp path"
|
||||||
);
|
);
|
||||||
let sandbox = factory.sandboxes().remove(0).expect("sandbox plan");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sandbox.allowed,
|
factory.sandboxes(),
|
||||||
vec![
|
vec![None],
|
||||||
domain::sandbox::PathGrant {
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
abs_root: root.to_string_lossy().into_owned(),
|
|
||||||
access: domain::sandbox::PathAccess::RW,
|
|
||||||
},
|
|
||||||
domain::sandbox::PathGrant {
|
|
||||||
abs_root: managed_root,
|
|
||||||
access: domain::sandbox::PathAccess::RW,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"OS sandbox grants must match the Codex writable roots"
|
|
||||||
);
|
);
|
||||||
let _ = std::fs::remove_dir_all(&root);
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
}
|
}
|
||||||
@ -4776,7 +4758,7 @@ async fn launch_pty_allocates_managed_tmp_env_and_sandbox_from_same_path() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn launch_structured_non_codex_uses_managed_tmp_for_env_and_sandbox_without_writable_roots() {
|
async fn launch_structured_non_codex_uses_managed_tmp_for_env_without_os_sandbox() {
|
||||||
let root = fresh_temp_dir("launch-tmp-structured-non-codex");
|
let root = fresh_temp_dir("launch-tmp-structured-non-codex");
|
||||||
let tmp = root.join("managed-tmp-main").to_string_lossy().into_owned();
|
let tmp = root.join("managed-tmp-main").to_string_lossy().into_owned();
|
||||||
let store = Arc::new(FakeManagedTempStore::default());
|
let store = Arc::new(FakeManagedTempStore::default());
|
||||||
@ -4815,13 +4797,16 @@ async fn launch_structured_non_codex_uses_managed_tmp_for_env_and_sandbox_withou
|
|||||||
assert_eq!(env.get("TMP"), Some(&tmp));
|
assert_eq!(env.get("TMP"), Some(&tmp));
|
||||||
assert_eq!(env.get("TEMP"), Some(&tmp));
|
assert_eq!(env.get("TEMP"), Some(&tmp));
|
||||||
assert_eq!(factory.policies(), vec![None]);
|
assert_eq!(factory.policies(), vec![None]);
|
||||||
let sandbox = factory.sandboxes().remove(0).expect("sandbox plan");
|
assert_eq!(
|
||||||
assert!(sandbox_has_rw_root(&sandbox, &tmp));
|
factory.sandboxes(),
|
||||||
|
vec![None],
|
||||||
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
|
);
|
||||||
let _ = std::fs::remove_dir_all(&root);
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn launch_structured_codex_uses_same_managed_tmp_for_env_sandbox_and_writable_roots() {
|
async fn launch_structured_codex_uses_managed_tmp_for_env_and_writable_roots_without_os_sandbox() {
|
||||||
let root = fresh_temp_dir("launch-tmp-structured");
|
let root = fresh_temp_dir("launch-tmp-structured");
|
||||||
let tmp = root.join("managed-tmp-main").to_string_lossy().into_owned();
|
let tmp = root.join("managed-tmp-main").to_string_lossy().into_owned();
|
||||||
let store = Arc::new(FakeManagedTempStore::default());
|
let store = Arc::new(FakeManagedTempStore::default());
|
||||||
@ -4862,8 +4847,11 @@ async fn launch_structured_codex_uses_same_managed_tmp_for_env_sandbox_and_writa
|
|||||||
panic!("expected Codex policy");
|
panic!("expected Codex policy");
|
||||||
};
|
};
|
||||||
assert!(writable_roots.contains(&tmp));
|
assert!(writable_roots.contains(&tmp));
|
||||||
let sandbox = factory.sandboxes().remove(0).expect("sandbox plan");
|
assert_eq!(
|
||||||
assert!(sandbox_has_rw_root(&sandbox, &tmp));
|
factory.sandboxes(),
|
||||||
|
vec![None],
|
||||||
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
|
);
|
||||||
let _ = std::fs::remove_dir_all(&root);
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4983,15 +4971,10 @@ async fn launch_managed_tmp_and_exposure_roots_are_union_without_duplicates() {
|
|||||||
vec![root.to_string_lossy().into_owned(), managed_root.clone()],
|
vec![root.to_string_lossy().into_owned(), managed_root.clone()],
|
||||||
"main launch temp and additive exposure path must be deduped"
|
"main launch temp and additive exposure path must be deduped"
|
||||||
);
|
);
|
||||||
let sandbox = factory.sandboxes().remove(0).expect("sandbox plan");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sandbox
|
factory.sandboxes(),
|
||||||
.allowed
|
vec![None],
|
||||||
.iter()
|
"structured launch must not pass an OS SandboxPlan to the provider"
|
||||||
.filter(|grant| grant.abs_root == managed_root)
|
|
||||||
.count(),
|
|
||||||
1,
|
|
||||||
"OS sandbox must also dedupe the shared path"
|
|
||||||
);
|
);
|
||||||
let _ = std::fs::remove_dir_all(&root);
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user