update docs, never disable sandbox
This commit is contained in:
parent
528a482fe8
commit
27530827d7
156
README.md
156
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:
|
||||
|
|
|
|||
|
|
@ -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 := ""
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Reference in New Issue