Boof Runner v0.2.0

Boof Runner security model

How Boof Runner protects its API: host checks, API keys, scopes, trust levels, audit log and safe remote access.

Security model

  • The server binds 127.0.0.1 by default and rejects requests whose Host header is not a loopback name or listed in security.allowedHosts. This blocks DNS-rebinding attacks.
  • Anyone who can reach the API can create a task, and every task runs coding agents with permission prompts disabled inside your repository. Treat API access as equivalent to running code as your user.
  • Ids are validated as kebab-case before they touch the filesystem. Request bodies are schema-validated and size-limited (1 MB JSON, 25 MB attachments).
  • Agents work in a git worktree on a boof-runner/<id> branch. Nothing is merged or pushed for you.
  • Do not expose the port to the public internet without keys and TLS.

Remote access

A request from a loopback peer with no Authorization header is the trusted local principal (while security.auth.mode is "local-trust", the default). Anything else needs an API key.

One command prepares the server for a remote AI harness:

bash
boof-runner remote setup --hostname boof.example.com --allowed-root ~/codes

It sets security.auth.mode = "required", adds the host name to security.allowedHosts and the directories to security.allowedProjectRoots, creates a harness API client (the key is printed once; default scopes are the full harness set, trust review; change with --name, --scopes, --trust, --expires, or skip with --no-key), restricts the data root to your user (chmod 700) and prints the next steps: serve with TLS (boof-runner serve --host 0.0.0.0 --tls-cert cert.pem --tls-key key.pem) or keep --host 127.0.0.1 behind a tunnel or reverse proxy that forwards https://<hostname> to http://127.0.0.1:4319 with the original Host header (for example Caddy reverse_proxy 127.0.0.1:4319 or a Cloudflare named tunnel routed to that host name; a quick cloudflared tunnel --url gets a random *.trycloudflare.com name, which you add with remote setup --no-key --hostname <name>). Then give the agent BOOF_URL=https://<hostname>, the key and https://nodets.com/boof/harness.txt. Running it again does not duplicate entries; a taken client name makes it stop without changes (use auth rotate <id>).

boof-runner remote status shows the current state and whether a non-loopback bind is allowed; boof-runner remote disable sets the mode back to local-trust (hosts, roots and clients stay).

The same by hand:

bash
# 1. require keys everywhere: set in settings.json
#    "security": { "auth": { "mode": "required" }, "allowedHosts": ["boof.example.com"],
#                  "allowedProjectRoots": ["~/codes"] }
# 2. create a client (the key is printed once; scopes limit what it can do)
boof-runner auth add --name ci-bot --scopes tasks:read,tasks:write --expires 30d
# 3. bind beyond loopback; refused unless mode is "required" and a client exists
boof-runner --host 0.0.0.0 --tls-cert cert.pem --tls-key key.pem
  • Clients may be restricted to some projects (--projects a,b): other projects answer 403 forbidden_project. A client with projects:write may register a project only when its real path is under security.allowedProjectRoots, is a git work tree, is not inside the data root and is not / or the home directory.
  • Call the API with Authorization: Bearer boof_<client>_<secret>. The browser UI asks for the key once per tab. The desktop app manages its own key.
  • boof-runner auth list | revoke <id> | rotate <id> manage clients. Revoked and expired keys stop working within 5 seconds.
  • Keys are root-equivalent. Scope them narrowly, set --expires, and keep the default review trust for anything you do not fully control: its tasks and plans land as drafts that a human approves in the UI. Use --trust trusted only for clients you would give your own shell.
  • Without TLS, keys travel in clear text; the server warns when you bind a non-loopback address without --tls-cert/--tls-key. Tailscale or a reverse proxy are fine alternatives. Requests carrying X-Forwarded-For never get local trust.
  • Every /api and /mcp call is appended to <data>/audit/<day>.jsonl (principal, action, status, timing, IP; never bodies, prompts or keys). Requests are rate limited per principal (security.rateLimit, default 600/min).

The full REST reference, error codes and webhooks are in docs/api.md.

Edit this page on GitHub Last updated