From 27530827d785691bcd6997da51d855ea13682147 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 11 Feb 2026 08:48:06 +0100 Subject: [PATCH] update docs, never disable sandbox --- README.md | 156 ++++++++++++++++++++++++++++++---- internal/backend/tmux/tmux.go | 31 ++++--- internal/sandbox/config.go | 2 - internal/sandbox/wrapper.go | 18 ++-- 4 files changed, 159 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index ab09942..ea286e0 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,10 @@ AnviLLM brings Claude, Kiro, and other LLM chat interfaces directly into your Ac - **Go 1.21+** (for building) - **Plan 9 from User Space** (plan9port) - provides Acme and 9P tools - **tmux** - session isolation and management +- **landrun** - sandboxing tool (optional but recommended) + - Install: `go install github.com/landlock-lsm/landrun@latest` + - Requires Linux kernel 5.13+ with Landlock support + - See Sandboxing section below for kernel version details - **At least one backend CLI**: - [Claude Code CLI](https://github.com/anthropics/claude-code) - Anthropic's official CLI - [kiro-cli](https://github.com/stillmatic/kiro) - Alternative Claude interface @@ -231,17 +235,57 @@ landrun --rw /project --rox /usr --connect-tcp 443 -- claude --dangerously-skip- ### Quick Start -Sandboxing is **enabled by default** with a locked-down policy: -- **Filesystem**: Read/write access to session working directory only +Sandboxing is **ALWAYS ENABLED** and **cannot be disabled**. The default policy is **locked-down**: +- **Filesystem**: Read/write access to session working directory, temp dir, and config dirs only - **System binaries**: Read-only + execute access to `/usr`, `/lib`, `/bin`, `/sbin` -- **Network**: HTTPS (port 443) for API calls -- **Config files**: Access to `~/.claude`, `~/.kiro`, `~/.config/anvillm` +- **System files**: Read-only access to essential files (`/etc/passwd`, `/dev/null`, `/proc/meminfo`, etc.) +- **Network**: Unrestricted (works on all kernel versions; fine-grained control requires kernel 6.10+) +- **Config files**: Access to `~/.claude`, `~/.claude.json`, `~/.kiro`, `~/.config/anvillm` + +**Security philosophy**: +AnviLLM runs backends with `--dangerously-skip-permissions` and `--trust-all-tools`, giving LLMs full access within their sandbox. **The sandbox is the ONLY safety layer.** Therefore, sandboxing cannot be disabled. + +**Important defaults**: +- **Sandboxing: ALWAYS ENABLED** - No config option to disable +- **`best_effort: false`** (strict mode - RECOMMENDED): Sessions **fail** if sandboxing cannot be applied + - Prevents accidentally running with ZERO restrictions when: + - `landrun` is not installed or not in PATH + - Kernel doesn't support Landlock (< 5.13) + - Security-first: Explicit failure is safer than silent degradation to completely unsandboxed mode + - **Do NOT change to `true`** unless you understand the security implications (see Troubleshooting below) +- **`unrestricted: true`** for network: Works on all kernel versions (fine-grained port control requires kernel 6.10+) + +The default policy provides strong filesystem isolation while maintaining compatibility with older kernels. ### Requirements - **landrun**: Install with `go install github.com/landlock-lsm/landrun@latest` -- **Linux Kernel**: 5.13+ with Landlock support -- **Fallback**: Runs unsandboxed if landrun unavailable (best-effort mode) +- **Linux Kernel with Landlock**: + - **Kernel 5.13+** (Landlock ABI v1): Basic filesystem restrictions + - **Kernel 6.7+** (Landlock ABI v4): Improved filesystem control + - **Kernel 6.10+** (Landlock ABI v5): Network restrictions (TCP port control) + +### Modes + +- **Strict mode** (default, `best_effort: false`): + - Sessions **fail** if sandboxing cannot be applied + - Prevents accidentally running unsandboxed + - Security-first: Explicit failure rather than silent degradation +- **Best-effort mode** (`best_effort: true`): + - Gracefully degrades to older Landlock ABI versions if needed + - **CRITICAL**: If `landrun` binary not in PATH → command runs without landrun wrapper (ZERO restrictions) + - **CRITICAL**: If kernel lacks Landlock (< 5.13) → landrun wrapper present but ineffective (ZERO restrictions) + - Prints warning to stderr but no error returned - session starts successfully + - Recommended for kernels < 6.10 when you need ABI v5 features but want graceful degradation + +**Security implications of best-effort mode**: +- Two failure modes that result in **completely unsandboxed** execution: + 1. **`landrun` binary missing**: Backend runs directly without sandbox wrapper + 2. **Kernel lacks Landlock**: Backend runs with landrun wrapper that cannot enforce restrictions +- Either case: No filesystem isolation, no network restrictions - full system access +- Use only when you understand and accept this tradeoff + +**Note**: Default config uses unrestricted network access (works on any kernel). Fine-grained network restrictions (port-specific) require ABI v5 (kernel 6.10+). ### Configuration @@ -301,12 +345,6 @@ filesystem: - "/bin" ``` -#### Disabled (Testing Only) -```yaml -general: - enabled: false -``` - ### Security Model **Default protections**: @@ -321,18 +359,89 @@ general: ### Troubleshooting +**Session fails to launch with "missing kernel Landlock support" or ABI version error?** + +This happens when landrun requires a newer Landlock ABI than your kernel provides. Check your kernel version: +```sh +uname -r +# Kernel 6.8 = Landlock ABI v4 +# Kernel 6.10+ = Landlock ABI v5 +``` + +**Why this happens**: By default, AnviLLM uses **strict mode** (`best_effort: false`). Sessions fail if sandboxing cannot be fully applied, rather than silently falling back to unsandboxed execution. This is intentional for security. + +**Solution 1: Enable best-effort mode** (recommended for kernel < 6.10) + +Edit `~/.config/anvillm/sandbox.yaml`: +```yaml +general: + best_effort: true # Gracefully degrade if ABI too old +``` + +This allows sandboxing to gracefully degrade when network restrictions aren't available (requires ABI v5). Filesystem restrictions will still be enforced on kernels with Landlock support (ABI v1+, kernel 5.13+). + +**CRITICAL SECURITY WARNING**: With `best_effort: true`: +- If `landrun` binary is not in PATH → runs with **ZERO restrictions** (completely unsandboxed) + - AnviLLM omits landrun from the command entirely + - Backend runs directly: `claude --dangerously-skip-permissions` (no sandbox wrapper) + - Warning printed to stderr: "landrun not available, running unsandboxed" +- If `landrun` IS in PATH but kernel lacks Landlock support (< 5.13) → runs with **ZERO restrictions** (completely unsandboxed) + - AnviLLM wraps command: `landrun --best-effort ... -- claude ...` + - landrun executes but internally uses go-landlock library in BestEffort mode + - go-landlock silently succeeds without applying any restrictions (no error from library) + - Backend runs with landrun wrapper present but ineffective +- If ABI version too old (e.g., kernel 6.8 with v4 when v5 needed) → degrades gracefully + - landrun runs with available ABI version (filesystem restrictions still enforced) + - Network restrictions skipped if not supported by kernel + - Partial sandboxing maintained + +Only enable best-effort mode if you understand and accept the possibility of running completely unsandboxed. + +**Solution 2: Upgrade your kernel** + +Install kernel 6.10+ for full Landlock ABI v5 support with network restrictions. + +**Solution 3: Disable network restrictions** (already default) + +The default config uses `unrestricted: true` for network access, which works on older kernels. + **Landrun not available?** - Install: `go install github.com/landlock-lsm/landrun@latest` +- Verify: `which landrun` should show the path (e.g., `/home/user/go/bin/landrun`) - Check status: Click `Sandbox` → `Status` in AnviLLM +- **Default behavior** (`best_effort: false`): Sessions **fail** if landrun binary is missing (secure) + - Error printed to stderr: "sandboxing enabled but landrun not available" + - Session creation aborted - no backend process started +- **With** `best_effort: true`: Backend runs **without landrun wrapper** if binary is missing (DANGEROUS) + - landrun completely omitted from command + - Backend executes directly: `claude --dangerously-skip-permissions` + - No filesystem isolation - full read/write access to entire system + - No network restrictions - unrestricted network access + - Warning printed to stderr but session starts successfully -**Network errors?** -- Ensure `connect_tcp = ["443"]` in config for API calls -- Or set `unrestricted = true` for full network access +**Recommendation**: Keep `best_effort: false` unless you have a specific need for graceful degradation and understand the security implications. + +**Backend fails with permission denied errors?** + +Node.js-based backends (Claude, Kiro) require specific system files. The default config includes these, but if you've modified it, ensure you have: + +```yaml +filesystem: + ro: + - /etc/passwd # Required: Node.js UID→homedir lookup + - /dev/null # Required: null device + - /proc/meminfo # Required: memory info + - /proc/self/cgroup # Required: cgroup info + - /proc/self/maps # Required: process memory maps + - /proc/version # Required: kernel version + rw: + - '{HOME}/.claude.json' # Claude config file +``` **File access denied?** - Add required paths to `rw` or `ro` in config -- Example for SSH keys: `ro = ["{HOME}/.ssh"]` -- Example for NPM: `rw = ["{HOME}/.npm", "{HOME}/.cache"]` +- Example for SSH keys: `ro: ["{HOME}/.ssh"]` +- Example for NPM: `rw: ["{HOME}/.npm", "{HOME}/.cache"]` **Check sandbox status**: ```sh @@ -341,12 +450,25 @@ cat ~/.config/anvillm/sandbox.yaml # Test landrun availability landrun --version + +# Check kernel Landlock ABI version +dmesg | grep -i landlock ``` +**Error messages**: +Launch errors are printed to stderr regardless of debug mode. If a session fails to start, check the error output for specific issues. + For more details, see [landrun documentation](https://github.com/landlock-lsm/landrun). ## Troubleshooting +### Session fails to start + +If sessions fail to launch, error messages are printed to stderr. Common causes: +- **Sandboxing issues**: See Sandboxing → Troubleshooting section above +- **Backend not installed**: Run `which claude` or `which kiro-cli` +- **Landlock ABI mismatch**: Enable `best_effort: true` in sandbox config (see above) + ### "Backend not found" error Make sure the backend CLI is installed and in your PATH: diff --git a/internal/backend/tmux/tmux.go b/internal/backend/tmux/tmux.go index 031e4dd..e6ae7b0 100644 --- a/internal/backend/tmux/tmux.go +++ b/internal/backend/tmux/tmux.go @@ -174,25 +174,24 @@ func (b *Backend) CreateSession(ctx context.Context, cwd string) (backend.Sessio fifoOpenCh <- f }() - // 7. Wrap command with landrun if sandboxing enabled + // 7. Wrap command with landrun (ALWAYS - sandboxing cannot be disabled) command := b.cfg.Command - if sandboxCfg.General.Enabled { - if !sandbox.IsAvailable() { - if sandboxCfg.General.BestEffort { - fmt.Fprintf(os.Stderr, "Warning: landrun not available, running unsandboxed (best-effort mode)\n") - } else { - os.Remove(fifoPath) - killWindow(b.tmuxSession, windowName) - fmt.Fprintf(os.Stderr, "Error: sandboxing enabled but landrun not available\n") - fmt.Fprintf(os.Stderr, "Install landrun or enable best_effort mode in ~/.config/anvillm/sandbox.yaml\n") - return nil, fmt.Errorf("sandboxing enabled but landrun not available") - } + if !sandbox.IsAvailable() { + if sandboxCfg.General.BestEffort { + fmt.Fprintf(os.Stderr, "WARNING: landrun not available, running UNSANDBOXED (best-effort mode)\n") + fmt.Fprintf(os.Stderr, "WARNING: No filesystem or network restrictions will be enforced!\n") + fmt.Fprintf(os.Stderr, "Install landrun: go install github.com/landlock-lsm/landrun@latest\n") + } else { + os.Remove(fifoPath) + killWindow(b.tmuxSession, windowName) + fmt.Fprintf(os.Stderr, "ERROR: landrun not available and sandboxing is required\n") + fmt.Fprintf(os.Stderr, "Install landrun: go install github.com/landlock-lsm/landrun@latest\n") + fmt.Fprintf(os.Stderr, "Or enable best_effort mode in ~/.config/anvillm/sandbox.yaml (NOT RECOMMENDED)\n") + return nil, fmt.Errorf("landrun not available") } - command = sandbox.WrapCommand(sandboxCfg, command, cwd) - debug.Log("[session %s] sandboxed command: %v", id, command) - } else { - debug.Log("[session %s] unsandboxed command: %v", id, command) } + command = sandbox.WrapCommand(sandboxCfg, command, cwd) + debug.Log("[session %s] sandboxed command: %v", id, command) // 8. Build command string for tmux cmdStr := "" diff --git a/internal/sandbox/config.go b/internal/sandbox/config.go index 4ff2cb7..8f0f78a 100644 --- a/internal/sandbox/config.go +++ b/internal/sandbox/config.go @@ -18,7 +18,6 @@ type Config struct { // GeneralConfig contains general sandbox settings type GeneralConfig struct { - Enabled bool `yaml:"enabled"` BestEffort bool `yaml:"best_effort"` LogLevel string `yaml:"log_level"` } @@ -49,7 +48,6 @@ type AdvancedConfig struct { func DefaultConfig() *Config { return &Config{ General: GeneralConfig{ - Enabled: true, BestEffort: false, // Fail-closed: refuse to run if sandboxing unavailable LogLevel: "error", }, diff --git a/internal/sandbox/wrapper.go b/internal/sandbox/wrapper.go index 7dfd01e..51e9ae7 100644 --- a/internal/sandbox/wrapper.go +++ b/internal/sandbox/wrapper.go @@ -7,15 +7,11 @@ import ( ) // WrapCommand wraps a command with landrun based on the configuration. -// Returns the wrapped command args, or the original command if sandboxing is disabled. +// Returns the wrapped command args, or the original command if landrun is unavailable. func WrapCommand(cfg *Config, originalCmd []string, cwd string) []string { - if !cfg.General.Enabled { - return originalCmd - } - if !IsAvailable() { if cfg.General.BestEffort { - // Silently fall back to unsandboxed execution + // Fall back to unsandboxed execution (caller will warn) return originalCmd } // Strict mode: would fail, but caller should check IsAvailable() first @@ -104,16 +100,12 @@ func WrapCommand(cfg *Config, originalCmd []string, cwd string) []string { func BuildSummary(cfg *Config) string { var lines []string - if !cfg.General.Enabled { - return "Sandboxing: DISABLED" - } - - lines = append(lines, "Sandboxing: ENABLED") + lines = append(lines, "Sandboxing: ALWAYS ENABLED (cannot be disabled)") if cfg.General.BestEffort { - lines = append(lines, " Mode: Best-effort (fallback if landrun unavailable)") + lines = append(lines, " Mode: Best-effort (fallback to unsandboxed if landrun unavailable - WARNING)") } else { - lines = append(lines, " Mode: Strict (require landrun)") + lines = append(lines, " Mode: Strict (fail if landrun unavailable - RECOMMENDED)") } // Filesystem