Remote access and API tokens
Opening the daemon beyond loopback: the bind-host opt-in, the API token it turns on, how each client sends it, and the limits of the scheme.
By default the daemon binds loopback only — the Web UI (18788) and the WebSocket gateway (18789) both listen on 127.0.0.1, so no other machine can reach them. Opening it to the LAN is an explicit opt-in, and when you take it, every non-loopback peer must present a token.
This is the other half of a fact that appears throughout the sandbox documentation: the local API is unauthenticated because its trust boundary is the loopback interface. Moving that boundary is what this page is about.
Turning on remote access
SENCLAW_UI_BIND_HOST=0.0.0.0 senclawWhen the bind host is not loopback, the daemon switches token mode on by itself:
- The token is read from
SENCLAW_API_TOKEN; failing that, from~/.senclaw/api_token, which is generated if absent (32 random bytes, hex, mode0600). - The startup log prints the token file's path, never its value.
- Loopback peers are always exempt — the bundled desktop app, a Space App calling back into the daemon, and same-machine tooling all keep working with no configuration at all.
SENCLAW_UI_BIND_HOST is deliberately separate from the SENCLAW_BIND_HOST used by Space Apps: an app has no authentication of its own, so it must not be dragged onto the LAN along with the daemon, or the reverse. Remote access to an app goes through the daemon's /api/space/… proxy, which is already behind the token.
How each client sends the token
| Channel | How |
|---|---|
| REST | Authorization: Bearer <token> or X-SenClaw-Token: <token> |
WebSocket upgrade (18789, /api/ws/terminal) | ?token=<token> or a cookie |
| A browser (Space App iframes, WebSockets) | the senclaw_token cookie, minted by POST /api/auth/login {token} — HttpOnly, SameSite=Lax |
Two endpoints stay open, and they return only a boolean or perform the login: GET /api/auth/status → {authRequired, authorized}, and POST /api/auth/login. GET /api/config gains an authRequired field.
- The Web UI shows a token prompt by itself when
authRequiredis set and the caller is not yet authorized. Every same-originfetchto/api/*is patched to carryX-SenClaw-Token, and a 401 locks the gate again. - The desktop app looks for a token in this order: Settings → General → Connection (stored preferences), then a
SENCLAW_API_TOKENbuild define, then~/.senclaw/api_tokenwhen it runs on the same machine. The header is attached in its API client and its multipart calls; WebSockets use?token=.
Two holes closed in the same change
- CORS. The permissive layer (which answered every origin with
*) is gone. Before that, any web page you had open could read the daemon's configuration endpoints across origins — including provider credentials, in cleartext. Only loopback origins (a Vite dev server, say) may now make cross-origin requests; the real UI is same-origin and needs no CORS at all. Update if you are on a build that predates this. - The WebSocket gateway now rejects at the HTTP upgrade for all three routes (
/,/browser,/browser-mcp). Checking in-band after connecting was not enough, because the dispatcher still ran handlers for an unauthenticated socket.
Known limits
- A LAN is plain HTTP here, so the token crosses the local network in the clear. For anything reachable from the internet, put it behind a TLS reverse proxy (at which point the cookie should also carry
Secure— not yet implemented). - Token comparison is constant-time. At 256 bits of entropy there is nothing for rate limiting to protect.
- Images loaded by the desktop app's network image widget do not carry the header yet. That only affects a desktop app pointed at a remote daemon; the loopback default is unaffected.
Coverage: 15 Rust tests over the middleware, token handling, cookies and CORS (cargo test ui_server::auth), plus a Flutter test for the desktop token order.
Further reading
- Sandbox internals — why the loopback API's lack of authentication mattered so much to sandboxed code, and what stops it now.
- Security model — what the registry enforces, which is a separate question from what the daemon exposes.
In the SenClaw repo
The middleware and token store live under the daemon's UI server auth module, the web gate is web/src/components/TokenGate.tsx with the fetch patch in web/src/lib/auth.ts, and the desktop side is in its API client plus the Connection section of Settings → General.