LP4-1 : adaptateurs SandboxEnforcer concrets (LandlockSandbox Linux via le LSM Landlock, NoopSandbox ailleurs) + default_enforcer() cfg-sélectionné, prêts à être injectés au composition root (LP4-3). Aucun changement de comportement runtime tant que rien n'est câblé : SpawnSpec.sandbox reste None. LP4-2 : PortablePtyAdapter consomme SpawnSpec.sandbox via spawn_command_sandboxed (thread jetable restreint par enforce() puis fork → le domaine Landlock est hérité par l'enfant à travers execve ; les autres threads d'IdeA ne sont jamais sandboxés). Builder additif with_sandbox_enforcer ; fail-closed si enforce() échoue. Tests : domain + infrastructure verts (dont les tests Landlock réels de fencing read-only/write-only et la confine-au-thread). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
2.9 KiB
Rust
83 lines
2.9 KiB
Rust
//! OS-sandbox adapters (lot LP4-1) — concrete [`domain::sandbox::SandboxEnforcer`]
|
|
//! implementations and the cfg-selected default constructor.
|
|
//!
|
|
//! - [`LandlockSandbox`] (Linux only) enforces a [`domain::sandbox::SandboxPlan`]
|
|
//! via the kernel Landlock LSM.
|
|
//! - [`NoopSandbox`] (everywhere) enforces nothing — the non-Linux / fallback path.
|
|
//!
|
|
//! [`default_enforcer`] returns the right one for the build target as a
|
|
//! `Arc<dyn SandboxEnforcer>`, ready for the composition root (LP4-3) to inject.
|
|
//! Nothing here is wired into the launch path yet (that is LP4-2): until then the
|
|
//! enforcer stays unused and `SpawnSpec.sandbox` stays `None`, so there is zero
|
|
//! runtime behaviour change.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use domain::sandbox::SandboxEnforcer;
|
|
|
|
mod noop;
|
|
pub use noop::NoopSandbox;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod landlock;
|
|
#[cfg(target_os = "linux")]
|
|
pub use landlock::LandlockSandbox;
|
|
|
|
/// The default OS-sandbox enforcer for the current build target: [`LandlockSandbox`]
|
|
/// on Linux, [`NoopSandbox`] everywhere else.
|
|
#[cfg(target_os = "linux")]
|
|
#[must_use]
|
|
pub fn default_enforcer() -> Arc<dyn SandboxEnforcer> {
|
|
Arc::new(LandlockSandbox::new())
|
|
}
|
|
|
|
/// The default OS-sandbox enforcer for the current build target: [`NoopSandbox`]
|
|
/// on non-Linux platforms (no OS sandbox wired yet).
|
|
#[cfg(not(target_os = "linux"))]
|
|
#[must_use]
|
|
pub fn default_enforcer() -> Arc<dyn SandboxEnforcer> {
|
|
Arc::new(NoopSandbox::new())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use domain::sandbox::{SandboxKind, SandboxPlan, SandboxStatus};
|
|
use domain::Posture;
|
|
|
|
#[test]
|
|
fn noop_enforcer_is_unsupported_and_never_errors() {
|
|
use domain::sandbox::PathGrant;
|
|
|
|
let noop = NoopSandbox::new();
|
|
assert_eq!(noop.kind(), SandboxKind::Unsupported);
|
|
|
|
// Across every posture — and even with grants present — the no-op enforces
|
|
// nothing and never fails closed: there is no OS sandbox here to fail closed
|
|
// against (the Deny posture's teeth live only in the advisory LP3 projection).
|
|
for posture in [Posture::Allow, Posture::Ask, Posture::Deny] {
|
|
let plan = SandboxPlan {
|
|
allowed: vec![PathGrant {
|
|
abs_root: "/whatever".to_owned(),
|
|
access: domain::sandbox::PathAccess::RW,
|
|
}],
|
|
default_posture: posture,
|
|
};
|
|
assert_eq!(
|
|
noop.enforce(&plan).unwrap(),
|
|
SandboxStatus::Unsupported,
|
|
"no-op must return Unsupported (never Err) under posture {posture:?}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn default_enforcer_matches_the_build_target() {
|
|
let enforcer = default_enforcer();
|
|
#[cfg(target_os = "linux")]
|
|
assert_eq!(enforcer.kind(), SandboxKind::Landlock);
|
|
#[cfg(not(target_os = "linux"))]
|
|
assert_eq!(enforcer.kind(), SandboxKind::Unsupported);
|
|
}
|
|
}
|