baton

Security model

What Baton exposes, what it refuses, and the exact guards between a hostile web page and your repo.

A daemon with read access to your repo deserves paranoia. Baton's exposure is deliberately narrow, and each guard exists because of a concrete attack — not as a checkbox.

The threat model

Baton assumes your machine is yours and your browser is hostile. It does not try to defend against a malicious process already running as your user — nothing can — but a web page you visit must never be able to read your task list, your file paths, or touch your repo through the daemon.

The guards

Loopback only

The daemon binds 127.0.0.1. Nothing on your network — not your LAN, not your coffee shop — can reach it. There is no flag to bind wider.

Origin guard on every write

Every mutating endpoint rejects requests whose Origin header is not a loopback origin. A malicious page can send a cross-origin request to localhost:7077, but the browser stamps it with the page's real origin, which fails the check — so CSRF against the daemon dies at the door. This is one central gate in front of all writes, not a per-endpoint checklist that can be forgotten on the next route.

Host-header check (DNS rebinding)

The subtle one. A page on evil.com can re-point its DNS at 127.0.0.1; the browser then treats the daemon as same-origin — no cross-origin Origin header, CORS never engages, and every read would sail through. The Host header is what closes it: the browser sets it to the name it actually dialled and script cannot forge it. Any request whose Host is not loopback is refused before routing — no handler, static file, or SSE stream is reachable under a rebound name. curl is unaffected; it sets Host itself.

Writes are opt-in

Mutating endpoints only exist when the daemon is started with baton serve --write. The default daemon is a read-only window. If you just want to watch the dashboard, run it without the flag and the API physically cannot merge, remove, or edit anything.

No shell, no injection surface

Git runs through a single hardened, shell-free exec wrapper — argv arrays, no string interpolation, no sh -c. File paths, branch names, and prompts from agents are data, never command text. Agent launchers neutralize flag-lookalike prompts (a "prompt" of --dangerously-skip-permissions reaches the agent CLI as text, not as a flag).

Zero-dependency by rule

The daemon's HTTP layer is raw node:http with no runtime dependencies. The component with repo access has (almost) no supply chain to compromise.

What leaves your machine

Nothing, unless you export it. .baton/ is local and gitignored by default; the knowledge base is only shared if you run baton kb share or baton kb export yourself. There is no telemetry, no account, no cloud sync.

Found something?

Security reports are welcome — see SECURITY.md in the repo for the disclosure process.

On this page