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.