feat(permissions): LP4-3 — activation bout-en-bout de la sandbox OS au runtime

Composition root : PortablePtyAdapter::new() injecte désormais default_enforcer()
(Landlock sur Linux / Noop ailleurs) via with_sandbox_enforcer, dans state.rs et
dans LocalHost (remote/mod.rs).

Launch path : LaunchAgent::execute peuple spec.sandbox via le plan pur
compile_sandbox_plan(effective_permissions, SandboxContext{project_root, run_dir}),
en réutilisant l'EffectivePermissions déjà résolu pour la projection advisory LP3.
Invariant produit conservé : eff == None ⇒ plan None ⇒ comportement natif, aucune
projection OS.

Portée : seul le chemin PTY brut est OS-enforcé ; le chemin structuré (Claude/Codex
mode JSON) porte le plan mais ne l'enforce pas encore (lot ultérieur).

Tests : ajout d'un test d'intégration bout-en-bout (pty/mod.rs, sandbox_e2e_tests)
prouvant qu'un enfant spawné sous un plan RW restreint ne peut PAS écrire hors-grant
(écriture bloquée par le kernel) alors que l'écriture dans le grant passe, plus un
test compagnon anti faux-positif (sans plan ⇒ pas de sandbox). Skip propre si le
kernel n'a pas Landlock. domain+infrastructure+application+app-tauri verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 08:08:40 +02:00
parent 17ca65ed0f
commit 7e01ac60cb
4 changed files with 198 additions and 2 deletions

View File

@ -28,6 +28,7 @@ use domain::{
ProjectedFile, ProjectionContext, ProjectorKey, Project, ProjectPath, ProviderSessionStore,
PtySize, SessionId, SessionKind, SessionStatus, Skill, TerminalSession,
};
use domain::sandbox::{compile_sandbox_plan, SandboxContext};
use crate::error::AppError;
use crate::layout::{persist_doc, resolve_doc};
@ -1510,6 +1511,22 @@ impl LaunchAgent {
)
.await?;
// 5d. ── PLAN DE SANDBOX OS (lot LP4-3) ──
// Strictement APRÈS la résolution des permissions (3) et la projection
// advisory (5c), AVANT le split structuré/PTY : `compile_sandbox_plan`
// est **pur** (domaine) — le launch path ne fait qu'orchestrer
// `EffectivePermissions` (déjà lue du store en 3) → `SandboxPlan` et
// remplir `spec.sandbox`. `eff == None` (rien posé) ⇒ `None` : aucun plan,
// comportement natif conservé (invariant produit). L'enforcer concret
// (Landlock/Noop) est injecté côté PTY au composition root.
spec.sandbox = compile_sandbox_plan(
effective_permissions.as_ref(),
&SandboxContext {
project_root: input.project.root.as_str(),
run_dir: run_dir.as_str(),
},
);
// 5b. ── POINT DE ROUTAGE §17.4 : IA structuré vs terminal brut ──
// Le convention file (CLAUDE.md / AGENTS.md) vient d'être écrit dans le
// run dir (étape 5) ; la CLI structurée le lira à chaque tour