fix(sandbox): accès Git minimal pour Codex — writable roots restreints au .git résolu, sans blanket grant project (#195, QA verte)
Codex ne pouvait pas accéder aux métadonnées Git sous sandbox workspace-write. À l'inverse, la factory relayait jusqu'ici le project_root entier en --add-dir, soit un grant large non défendable. Change pour n'accorder en écriture (Codex --add-dir ET SandboxPlan OS) que le .git réellement résolu : - resolve_git_dir gère .git/ directory ET fichier worktree 'gitdir: <path>' (absolu ou relatif au parent du marker), puis canonicalize. - enrich_sandbox_plan_with_codex_git_roots ajoute/déduplique les roots en RW sur le SandboxPlan existant (préserve l'autorité EffectivePermissions). - factory: défaut writable_roots = Vec::new() en l'absence de policy explicite. Invariants conservés : - project_root n'est jamais injecté comme writable root. - aucun élargissement au parent commun (canonicalize + BTreeSet). - permissions MCP/globales inchangées (seul le plan runtime est enrichi). Tests (crates/application) : - standard .git/ dir -> root unique. - projet non-Git -> aucun root. - worktree (.git file gitdir:) -> résolution + canonicalization. - symlink -> dédup, pas de grant parent. - factory: défaut sans policy n'émet plus --add-dir project_root. Réserve QA: launch_conventionfile_injects_assigned_skills_in_order (agent_lifecycle) arbitré hors-scope/non bloquant pour #195.
This commit is contained in:
@ -451,18 +451,18 @@ impl CodexExecSession {
|
||||
///
|
||||
/// Format RÉEL vérifié 2026-06-10 (codex 0.137.0) :
|
||||
/// - Conversation neuve : `codex exec --json --skip-git-repo-check
|
||||
/// --sandbox workspace-write --add-dir <project-root> <prompt>`.
|
||||
/// --sandbox workspace-write [--add-dir <explicit-root>...] <prompt>`.
|
||||
/// - Reprise (id connu) : `codex exec --json
|
||||
/// --skip-git-repo-check --sandbox workspace-write --add-dir <project-root> resume
|
||||
/// <thread_id> <prompt>`.
|
||||
/// --skip-git-repo-check --sandbox workspace-write [--add-dir <explicit-root>...]
|
||||
/// resume <thread_id> <prompt>`.
|
||||
///
|
||||
/// **Autonomie d'écriture (D3)** : `--sandbox workspace-write` autorise l'agent à
|
||||
/// écrire dans son workspace. `codex exec` est déjà non-interactif (aucun prompt
|
||||
/// d'approbation possible), donc on ne passe **pas** `--ask-for-approval` : ce flag
|
||||
/// appartient à la commande interactive `codex`, pas à la sous-commande `exec` qui
|
||||
/// sort sur `error: unexpected argument '--ask-for-approval' found`. Comme l'agent
|
||||
/// tourne depuis son run dir isolé, `--add-dir` expose explicitement le project root
|
||||
/// à la sandbox Codex pour que les écritures Git touchent le vrai workspace.
|
||||
/// tourne depuis son run dir isolé, `--add-dir` expose uniquement les roots
|
||||
/// writable explicitement fournis par la launch policy.
|
||||
fn build_spawn_line(&self, prompt: &str) -> SpawnLine {
|
||||
let mut args = vec!["exec".to_owned()];
|
||||
let conversation_id = self.conversation_id.lock().expect("mutex sain").clone();
|
||||
|
||||
@ -189,11 +189,7 @@ impl AgentSessionFactory for StructuredSessionFactory {
|
||||
writable_roots.clone(),
|
||||
Some(*network_access),
|
||||
),
|
||||
None => (
|
||||
"workspace-write".to_owned(),
|
||||
vec![ctx.project_root.clone()],
|
||||
None,
|
||||
),
|
||||
None => ("workspace-write".to_owned(), Vec::new(), None),
|
||||
};
|
||||
Arc::new(CodexExecSession::new_with_policy_and_overrides(
|
||||
id,
|
||||
|
||||
@ -647,7 +647,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn factory_passes_project_root_to_codex_add_dir() {
|
||||
async fn factory_does_not_pass_project_root_to_codex_add_dir_without_policy() {
|
||||
let factory = StructuredSessionFactory::new();
|
||||
let (cmd, argv) = make_recording_fake(&[
|
||||
r#"{"type":"thread.started","thread_id":"cx-new"}"#,
|
||||
@ -679,8 +679,12 @@ mod tests {
|
||||
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
||||
let args: Vec<&str> = recorded.lines().collect();
|
||||
assert!(
|
||||
args.windows(2).any(|w| w == ["--add-dir", "/project/root"]),
|
||||
"factory must relay PreparedContext.project_root to Codex --add-dir, got: {args:?}"
|
||||
!args.windows(2).any(|w| w == ["--add-dir", "/project/root"]),
|
||||
"factory must not invent --add-dir project root without an explicit policy, got: {args:?}"
|
||||
);
|
||||
assert!(
|
||||
!args.contains(&"--add-dir"),
|
||||
"default Codex adapter launch must not carry any implicit --add-dir, got: {args:?}"
|
||||
);
|
||||
assert!(
|
||||
!args.contains(&"--ask-for-approval"),
|
||||
|
||||
Reference in New Issue
Block a user