https://docs.kernel.org/userspace-api/landlock.html Reviewed-on: #5 Co-authored-by: Drew Galbraith <drew@tiramisu.one> Co-committed-by: Drew Galbraith <drew@tiramisu.one>
96 lines
6 KiB
Markdown
96 lines
6 KiB
Markdown
# Implementation Plan
|
|
|
|
## Phase 4: Sandboxing
|
|
|
|
### Step 4.1: Create sandbox module with policy types and tracing foundation
|
|
- `SandboxPolicy` struct: read-only paths, read-write paths, network allowed bool
|
|
- `Sandbox` struct holding policy + working dir
|
|
- Add `tracing` spans and events throughout from the start:
|
|
- `#[instrument]` on all public `Sandbox` methods
|
|
- `debug!` on policy construction with path lists
|
|
- `info!` on sandbox creation with full policy summary
|
|
- No enforcement yet, just the type skeleton and module wiring
|
|
- **Files:** new `src/sandbox/mod.rs`, `src/sandbox/policy.rs`
|
|
- **Done when:** compiles, unit tests for policy construction, `RUST_LOG=debug cargo test` shows sandbox trace output
|
|
|
|
### Step 4.2: Landlock policy builder with startup gate and tracing
|
|
- Translate `SandboxPolicy` into Landlock ruleset using `landlock` crate
|
|
- Kernel requirements:
|
|
- **ABI v4 (kernel 6.7+):** minimum required -- provides both filesystem and network sandboxing
|
|
- ABI 1-3 have filesystem only, no network restriction -- tools could exfiltrate data freely
|
|
- Startup behavior -- on launch, check Landlock ABI version:
|
|
- ABI >= 4: proceed normally (full filesystem + network sandboxing)
|
|
- ABI < 4 (including unsupported): **refuse to start** with clear error: "Landlock ABI v4+ required (kernel 6.7+). Use --yolo to run without sandboxing."
|
|
- `--yolo` flag: skip all Landlock enforcement, log `warn!` at startup, show "UNSANDBOXED" in status bar permanently
|
|
- Landlock applied per-child-process via `pre_exec`, NOT to the main process
|
|
- Main process needs unrestricted network (Claude API) and filesystem (provider)
|
|
- Each `exec_command` child gets the current policy at spawn time
|
|
- `:net on/off` takes effect on the next spawned command
|
|
- Tracing:
|
|
- `info!` on kernel ABI version detected
|
|
- `debug!` for each rule added to ruleset (path, access flags)
|
|
- `warn!` on `--yolo` mode ("running without kernel sandboxing")
|
|
- `error!` if ruleset creation fails unexpectedly
|
|
- **Files:** `src/sandbox/landlock.rs`, add `landlock` dep to `Cargo.toml`, update CLI args in `src/app/`
|
|
- **Done when:** unit test constructs ruleset without panic; `--yolo` flag works on unsupported kernel; startup refuses without flag on unsupported kernel
|
|
|
|
### Step 4.3: Sandbox file I/O API with operation tracing
|
|
- `Sandbox::read_file`, `Sandbox::write_file`, `Sandbox::list_directory`
|
|
- Move `validate_path` from `src/tools/mod.rs` into sandbox
|
|
- Tracing:
|
|
- `debug!` on every file operation: requested path, canonical path, allowed/denied
|
|
- `trace!` for path validation steps (join, canonicalize, starts_with check)
|
|
- `warn!` on path escape attempts (log the attempted path for debugging)
|
|
- `debug!` on successful operations with bytes read/written
|
|
- **Files:** `src/sandbox/mod.rs`
|
|
- **Done when:** unit tests in tempdir pass; path traversal rejected; `RUST_LOG=trace` shows full path resolution chain
|
|
|
|
### Step 4.4: Sandbox command execution with process tracing
|
|
- `Sandbox::exec_command(cmd, args, working_dir)` spawns child process with Landlock applied
|
|
- Captures stdout/stderr, enforces timeout
|
|
- Tracing:
|
|
- `info!` on command spawn: command, args, working_dir, timeout
|
|
- `debug!` on command completion: exit code, stdout/stderr byte lengths, duration
|
|
- `warn!` on non-zero exit codes
|
|
- `error!` on timeout or spawn failure with full context
|
|
- `trace!` for Landlock application to child process thread
|
|
- **Files:** `src/sandbox/mod.rs` or `src/sandbox/exec.rs`
|
|
- **Done when:** unit test runs `echo hello` in tempdir; write outside sandbox fails (on supported kernels)
|
|
|
|
### Step 4.5: Wire tools through Sandbox
|
|
- Change `Tool::execute` signature to accept `&Sandbox` instead of (or in addition to) `&Path`
|
|
- Update all 4 built-in tools to call `Sandbox` methods instead of `std::fs`/`std::process::Command`
|
|
- Remove direct `std::fs` usage from tool implementations
|
|
- Update `ToolRegistry` and orchestrator to pass `Sandbox`
|
|
- Tracing: tools now inherit sandbox spans automatically via `#[instrument]`
|
|
- **Files:** `src/tools/*.rs`, `src/tools/mod.rs`, `src/core/orchestrator.rs`
|
|
- **Done when:** all existing tool tests pass through Sandbox; no direct `std::fs` in tool files; `RUST_LOG=debug cargo run` shows sandbox operations during tool execution
|
|
|
|
### Step 4.6: Network toggle
|
|
- `network_allowed: bool` in `SandboxPolicy`
|
|
- `:net on/off` TUI command parsed in input handler, sent as `UserAction::SetNetworkPolicy(bool)`
|
|
- Orchestrator updates `Sandbox` policy. Status bar shows network state.
|
|
- Only available when Landlock ABI >= 4 (kernel 6.7+); command hidden otherwise
|
|
- Status bar shows: network state when available, "UNSANDBOXED" in `--yolo` mode
|
|
- Tracing: `info!` on network policy change
|
|
- **Files:** `src/tui/input.rs`, `src/tui/render.rs`, `src/core/types.rs`, `src/core/orchestrator.rs`, `src/sandbox/mod.rs`
|
|
- **Done when:** toggling `:net` updates status bar; Landlock network restriction applied on ABI >= 4
|
|
|
|
### Step 4.7: Integration tests
|
|
- Tools + Sandbox in tempdir: write confinement, path traversal rejection, shell command confinement
|
|
- Skip Landlock-specific assertions on ABI < 4
|
|
- Test `--yolo` mode: sandbox constructed but no kernel enforcement
|
|
- Test startup gate: verify error on ABI < 4 without `--yolo`
|
|
- Tests should assert tracing output where relevant (use `tracing-test` crate or `tracing_subscriber::fmt::TestWriter`)
|
|
- **Files:** `tests/sandbox.rs`
|
|
- **Done when:** `cargo test --test sandbox` passes
|
|
|
|
### Phase 4 verification (end-to-end)
|
|
1. `cargo test` -- all tests pass
|
|
2. `cargo clippy -- -D warnings` -- zero warnings
|
|
3. `RUST_LOG=debug cargo run -- --project-dir .` -- ask Claude to read a file, observe sandbox trace logs showing path validation and Landlock policy
|
|
4. Ask Claude to write a file outside project dir -- sandbox denies with `warn!` log
|
|
5. Ask Claude to run a shell command -- observe command spawn/completion trace
|
|
6. `:net off` then ask for network access -- verify blocked
|
|
7. Without `--yolo` on ABI < 4: verify startup refuses with clear error
|
|
8. With `--yolo`: verify startup succeeds, "UNSANDBOXED" in status bar, `warn!` in logs
|