Watchfire
Concepts

Sandboxing

Watchfire sandboxes coding agents using platform-specific backends — Seatbelt on macOS, Landlock or Bubblewrap on Linux — to restrict filesystem access and limit blast radius.

Sandboxing

Watchfire runs coding agents inside platform-specific sandboxes that restrict what the agent process can access. This limits the blast radius of any unintended actions — the agent has full autonomy inside the sandbox, but the sandbox controls what "full autonomy" actually means.

Auto-Detection

The default sandbox setting is "auto". When set, Watchfire automatically selects the best available backend for the current platform:

PlatformAuto-detected backend
macOSSeatbelt (sandbox-exec)
LinuxLandlock (kernel 5.13+) → Bubblewrap fallback → unsandboxed
WindowsUnsandboxed (no sandbox support)

On Linux, Watchfire tries Landlock first. If the kernel doesn't support it (pre-5.13), it falls back to Bubblewrap. If neither is available, the agent runs unsandboxed.

Priority Chain

Sandbox backend resolution follows a strict priority order:

  1. CLI flag--sandbox <backend> or --no-sandbox (highest priority)
  2. Project settingsandbox field in project.yaml
  3. Global defaultdefaults.default_sandbox in settings.yaml

If none are set, the default is "auto".

macOS — Seatbelt

On macOS, Watchfire uses sandbox-exec (Apple's Seatbelt framework) to enforce kernel-level restrictions.

sandbox-exec -f <profile> claude --dangerously-skip-permissions --append-system-prompt "..." [--prompt "..."]

The sandbox profile is generated at runtime from an internal SandboxPolicy struct. It is not user-visible or editable.

Why Seatbelt?

  • Kernel-level enforcement — the agent process cannot bypass the sandbox
  • Child process inheritance — even code the agent writes and executes runs within the same sandbox
  • Regex pattern support — allows fine-grained rules like blocking .env files and .git/hooks by pattern

Allowed Operations

OperationDetails
ReadMost of the filesystem (project files, system libraries, tools)
WriteProject directory, temp dirs, package manager caches (~/.npm, ~/.yarn, ~/.pnpm-store, ~/.cache, ~/Library/Caches/*), dev tool caches (~/.cargo, ~/go, ~/.rustup), Claude config (~/.claude), CLI tool config (~/Library/Application Support)
ExecuteInstalled tools (git, npm, make, etc.)
NetworkFull network access

Blocked Paths

Read and write access is denied to:

  • ~/.ssh — SSH keys and configuration
  • ~/.aws — AWS credentials
  • ~/.gnupg — GPG keys
  • ~/.netrc — Network credentials
  • ~/.npmrc — npm credentials
  • ~/Desktop, ~/Documents, ~/Downloads — Personal directories
  • ~/Music, ~/Movies, ~/Pictures — Media directories
  • .env files — Environment secrets
  • .git/hooks — Git hooks (prevents hook injection)

Projects inside protected directories work correctly. The sandbox profile is generated dynamically — if your project is located inside a protected folder (e.g., ~/Documents/my-project), the deny rule for that folder is automatically skipped so the agent can access the project files. This was fixed in v1.0.0 (#17).

Linux — Landlock

Landlock is the preferred Linux backend. It uses the kernel's Landlock LSM (available since kernel 5.13) to enforce filesystem restrictions with zero external dependencies.

How It Works

The daemon re-invokes itself as a helper process to apply Landlock restrictions before exec-ing the agent:

watchfired --sandbox-exec <config.json>
# → applies Landlock restrictions → exec() claude ...

This approach ensures Landlock rules are applied before the agent process starts, with no way to escape them.

Requirements

  • Linux kernel 5.13 or later
  • No additional packages or tools required
  • Uses go-landlock — a pure Go binding

Allowed and Blocked Paths

Same policy as macOS (derived from the shared SandboxPolicy), except:

  • No regex-based pattern blocking (Landlock operates on file paths, not patterns)
  • Personal directories (~/Desktop, ~/Documents, etc.) are not blocked (these are macOS-specific paths)

Linux — Bubblewrap (Fallback)

If Landlock is not available (kernel older than 5.13), Watchfire falls back to Bubblewrap (bwrap), a namespace-based sandbox.

How It Works

Bubblewrap creates an isolated mount namespace with controlled visibility:

bwrap --ro-bind / / --bind <project> <project> --tmpfs ~/.ssh ... -- claude ...
  • The root filesystem is mounted read-only
  • The project directory is mounted read-write
  • Sensitive directories (like ~/.ssh) are replaced with empty tmpfs mounts, effectively hiding them

Requirements

  • bwrap must be installed on the system
  • Available in most Linux distribution package managers (e.g., apt install bubblewrap, dnf install bubblewrap)
  • Requires user namespace support (enabled by default on most modern distributions)

Limitations

If Bubblewrap is not installed, the agent runs unsandboxed on Linux. A warning is logged when this happens.

Windows

Windows does not currently have sandbox support. Agents run unsandboxed with full access to the filesystem and network.

Implications

  • The agent process has the same permissions as the user running the daemon
  • All filesystem paths are accessible (no credential or personal directory protection)
  • The --dangerously-skip-permissions flag is still used, giving the agent full autonomy without OS-level restrictions

Recommendations

When running Watchfire on Windows:

  • Review agent output carefully, especially for tasks that modify files outside the project directory
  • Consider running the daemon under a restricted user account
  • Use the --no-sandbox flag explicitly to acknowledge the unsandboxed state

Configuration

CLI Flags

The --sandbox and --no-sandbox flags are available on run, chat, plan, generate, and wildfire commands:

# Use a specific backend
watchfire agent start --sandbox landlock

# Disable sandboxing entirely
watchfire agent start --no-sandbox

Valid backend values: auto, seatbelt, landlock, bwrap, none.

Per-Project (project.yaml)

sandbox: "auto"  # "auto" | "seatbelt" | "landlock" | "bwrap" | "none"

Global Default (settings.yaml)

defaults:
  default_sandbox: "auto"

Located at ~/.watchfire/settings.yaml.

Security Model

AspectmacOS (Seatbelt)Linux (Landlock)Linux (Bubblewrap)Windows
EnforcementKernel (App Sandbox)Kernel (LSM)Namespace isolationNone
Profile sourceRuntime SandboxPolicyRuntime SandboxPolicyRuntime SandboxPolicyN/A
Agent permissionsFull within sandboxFull within sandboxFull within sandboxFull (unrestricted)
Claude Code flag--dangerously-skip-permissions--dangerously-skip-permissions--dangerously-skip-permissions--dangerously-skip-permissions
Credential dirs blockedYesYesYes (hidden via tmpfs)No
Personal dirs blockedYesNo (Linux paths differ)No (Linux paths differ)No
.env / .git/hooksBlocked (regex)Not blocked (no regex)Not blockedNot blocked
NetworkFull accessFull accessFull accessFull access
Child process isolationInheritedInheritedInheritedNone
External dependenciesNone (built-in)None (kernel)bwrap packageN/A

Environment Variable Isolation

Across all platforms, the daemon strips internal environment variables (such as CLAUDECODE) from agent subprocess environments. This prevents issues like Claude Code detecting the variable and entering a nested mode. Agents always start with a clean environment, free of daemon-internal state.

Agent Permissions Flow

The --dangerously-skip-permissions flag tells Claude Code to skip its own permission prompts. On macOS and Linux, this is safe because the OS-level sandbox enforces stricter boundaries. On Windows, the agent runs without OS-level restrictions.

On this page