skate/PLAN.md
Drew Galbraith 7efc6705d3 Use Landlock to restrict bash calls. (#5)
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>
2026-03-02 03:51:46 +00:00

6 KiB

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