fix: harden gateway security guidance (#2000) (thanks @rhuanssauro)

This commit is contained in:
Peter Steinberger committed 2026-01-26 13:38:37 +00:00
1 parent 42fe1021ca
commit 09ab5a9b31
14 files changed
+67 -89

No files matched your search

+1 -1
View File
@@ -392,7 +392,7 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
auth: {
mode: "token",
token: "gateway-token",
allowTailscale: true
allowTailscale: false
},
tailscale: { mode: "serve", resetOnExit: false },
remote: { url: "ws://gateway.tailnet:18789", token: "remote-token" },
+4 -9
View File
@@ -2867,22 +2867,17 @@ Notes:
- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).
- OpenAI Chat Completions endpoint: **disabled by default**; enable with `gateway.http.endpoints.chatCompletions.enabled: true`.
- Precedence: `--port` > `CLAWDBOT_GATEWAY_PORT` > `gateway.port` > default `18789`.
- Gateway auth is required by default (token/password or Tailscale Serve identity). Non-loopback binds require a shared token/password.
- Gateway auth is required by default (token/password). Non-loopback binds require a shared token/password.
- The onboarding wizard generates a gateway token by default (even on loopback).
- `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored.
Auth and Tailscale:
- `gateway.auth.mode` sets the handshake requirements (`token` or `password`). When unset, token auth is assumed.
- `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine).
- When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers).
- When `gateway.auth.mode` is set, only that method is accepted.
- `gateway.auth.password` can be set here, or via `CLAWDBOT_GATEWAY_PASSWORD` (recommended).
- `gateway.auth.allowTailscale` allows Tailscale Serve identity headers
(`tailscale-user-login`) to satisfy auth when the request arrives on loopback
with `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`. Clawdbot
verifies the identity by resolving the `x-forwarded-for` address via
`tailscale whois` before accepting it. When `true`, Serve requests do not need
a token/password; set `false` to require explicit credentials. Defaults to
`true` when `tailscale.mode = "serve"` and auth mode is not `password`.
- `gateway.auth.allowTailscale` is a legacy option; do not use it for auth.
Keep it `false` and require token/password even with Tailscale Serve.
- `gateway.tailscale.mode: "serve"` uses Tailscale Serve (tailnet only, loopback bind).
- `gateway.tailscale.mode: "funnel"` exposes the dashboard publicly; requires auth.
- `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown.
+2 -2
View File
@@ -115,8 +115,8 @@ Short version: **keep the Gateway loopback-only** unless you’re sure you need
- **Non-loopback binds** (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) must use auth tokens/passwords.
- `gateway.remote.token` is **only** for remote CLI calls — it does **not** enable local auth.
- `gateway.remote.tlsFingerprint` pins the remote TLS cert when using `wss://`.
- **Tailscale Serve** can authenticate via identity headers when `gateway.auth.allowTailscale: true`.
Set it to `false` if you want tokens/passwords instead.
- **Tailscale Serve** only provides HTTPS routing; it does not replace auth. Use a
token/password and keep `gateway.auth.allowTailscale: false`.
- Treat `browser.controlUrl` like an admin API: tailnet-only + token auth.
Deep dive: [Security](/gateway/security).
+6 -12
View File
@@ -327,19 +327,13 @@ Rotation checklist (token/password):
3. Update any remote clients (`gateway.remote.token` / `.password` on machines that call into the Gateway).
4. Verify you can no longer connect with the old credentials.
### 0.6) Tailscale Serve identity headers
### 0.6) Tailscale Serve auth
When `gateway.auth.allowTailscale` is `true` (default for Serve), Clawdbot
accepts Tailscale Serve identity headers (`tailscale-user-login`) as
authentication. Clawdbot verifies the identity by resolving the
`x-forwarded-for` address through the local Tailscale daemon (`tailscale whois`)
and matching it to the header. This only triggers for requests that hit loopback
and include `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` as
injected by Tailscale.
**Security rule:** do not forward these headers from your own reverse proxy. If
you terminate TLS or proxy in front of the gateway, disable
`gateway.auth.allowTailscale` and use token/password auth instead.
Tailscale Serve/Funnel provide HTTPS and routing; they do **not** replace Gateway
auth. Always require a token/password and keep `gateway.auth.allowTailscale: false`
(legacy option). If you terminate TLS or proxy in front of the gateway,
ensure your proxy overwrites `x-forwarded-*` and is listed in
`gateway.trustedProxies`.
Trusted proxies:
- If you terminate TLS in front of the Gateway, set `gateway.trustedProxies` to your proxy IPs.
+6 -13
View File
@@ -8,7 +8,7 @@ read_when:
Clawdbot can auto-configure Tailscale **Serve** (tailnet) or **Funnel** (public) for the
Gateway dashboard and WebSocket port. This keeps the Gateway bound to loopback while
Tailscale provides HTTPS, routing, and (for Serve) identity headers.
Tailscale provides HTTPS and routing.
## Modes
@@ -23,16 +23,8 @@ Set `gateway.auth.mode` to control the handshake:
- `token` (default when `CLAWDBOT_GATEWAY_TOKEN` is set)
- `password` (shared secret via `CLAWDBOT_GATEWAY_PASSWORD` or config)
When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`,
valid Serve proxy requests can authenticate via Tailscale identity headers
(`tailscale-user-login`) without supplying a token/password. Clawdbot verifies
the identity by resolving the `x-forwarded-for` address via the local Tailscale
daemon (`tailscale whois`) and matching it to the header before accepting it.
Clawdbot only treats a request as Serve when it arrives from loopback with
Tailscale’s `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`
headers.
To require explicit credentials, set `gateway.auth.allowTailscale: false` or
force `gateway.auth.mode: "password"`.
Tailscale Serve/Funnel do **not** replace Gateway auth. Always require a token
or password, and keep `gateway.auth.allowTailscale: false` (legacy option).
## Config examples
@@ -42,7 +34,8 @@ force `gateway.auth.mode: "password"`.
{
gateway: {
bind: "loopback",
tailscale: { mode: "serve" }
tailscale: { mode: "serve" },
auth: { mode: "token", token: "your-token", allowTailscale: false }
}
}
```
@@ -133,7 +126,7 @@ Avoid Funnel for browser control endpoints unless you explicitly want public exp
## Tailscale prerequisites + limits
- Serve requires HTTPS enabled for your tailnet; the CLI prompts if it is missing.
- Serve injects Tailscale identity headers; Funnel does not.
- Serve injects Tailscale identity headers; Clawdbot does not use them for auth.
- Funnel requires Tailscale v1.38.3+, MagicDNS, HTTPS enabled, and a funnel node attribute.
- Funnel only supports ports `443`, `8443`, and `10000` over TLS.
- Funnel on macOS requires the open-source Tailscale app variant.
+2 -2
View File
@@ -327,7 +327,7 @@ The wizard now opens your browser with a tokenized dashboard URL right after onb
- The token is the same value as `gateway.auth.token` (or `CLAWDBOT_GATEWAY_TOKEN`) and is stored by the UI after first load.
**Not on localhost:**
- **Tailscale Serve** (recommended): keep bind loopback, run `clawdbot gateway --tailscale serve`, open `https://<magicdns>/`. If `gateway.auth.allowTailscale` is `true`, identity headers satisfy auth (no token).
- **Tailscale Serve** (recommended): keep bind loopback, run `clawdbot gateway --tailscale serve`, open `https://<magicdns>/`, and authenticate with your token/password (keep `gateway.auth.allowTailscale: false`).
- **Tailnet bind**: run `clawdbot gateway --bind tailnet --token "<token>"`, open `http://<tailscale-ip>:18789/`, paste token in dashboard settings.
- **SSH tunnel**: `ssh -N -L 18789:127.0.0.1:18789 user@host` then open `http://127.0.0.1:18789/?token=...` from `clawdbot dashboard`.
@@ -1434,7 +1434,7 @@ Check the basics:
- Channel health: `clawdbot channels status`
Then verify auth and routing:
- If you use Tailscale Serve, make sure `gateway.auth.allowTailscale` is set correctly.
- If you use Tailscale Serve, confirm you’re using token/password auth and `gateway.auth.allowTailscale: false`.
- If you connect via SSH tunnel, confirm the local tunnel is up and points at the right port.
- Confirm your allowlists (DM or group) include your account.
+2 -2
View File
@@ -122,8 +122,8 @@ clawdbot gateway restart
Open: `https://<magicdns>/`
Notes:
- Serve keeps the Gateway loopback-only and authenticates via Tailscale identity headers.
- To require token/password instead, set `gateway.auth.allowTailscale: false` or use `gateway.auth.mode: "password"`.
- Serve keeps the Gateway loopback-only and provides HTTPS routing.
- You still need gateway auth (token/password). Keep `gateway.auth.allowTailscale: false` and set `gateway.auth.mode` to `token` or `password`.
**Option C: Tailnet bind (no Serve)**
```bash
+3 -7
View File
@@ -68,13 +68,9 @@ clawdbot gateway --tailscale serve
Open:
- `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`)
By default, Serve requests can authenticate via Tailscale identity headers
(`tailscale-user-login`) when `gateway.auth.allowTailscale` is `true`. Clawdbot
verifies the identity by resolving the `x-forwarded-for` address with
`tailscale whois` and matching it to the header, and only accepts these when the
request hits loopback with Tailscale’s `x-forwarded-*` headers. Set
`gateway.auth.allowTailscale: false` (or force `gateway.auth.mode: "password"`)
if you want to require a token/password even for Serve traffic.
Serve does **not** replace Gateway auth. Set a token/password (prefer
`CLAWDBOT_GATEWAY_TOKEN` or `CLAWDBOT_GATEWAY_PASSWORD`) and connect with it.
Keep `gateway.auth.allowTailscale: false` (legacy option).
### Bind to tailnet + token
+1 -1
View File
@@ -29,7 +29,7 @@ Authentication is enforced at the WebSocket handshake via `connect.params.auth`
- **Localhost**: open `http://127.0.0.1:18789/`. If you see “unauthorized,” run `clawdbot dashboard` and use the tokenized link (`?token=...`).
- **Token source**: `gateway.auth.token` (or `CLAWDBOT_GATEWAY_TOKEN`); the UI stores it after first load.
- **Not localhost**: use Tailscale Serve (tokenless if `gateway.auth.allowTailscale: true`), tailnet bind with a token, or an SSH tunnel. See [Web surfaces](/web).
- **Not localhost**: use Tailscale Serve with a token/password, tailnet bind with a token, or an SSH tunnel. See [Web surfaces](/web).
## If you see “unauthorized” / 1008
+4 -5
View File
@@ -91,14 +91,13 @@ Open:
## Security notes
- Gateway auth is required by default (token/password or Tailscale identity headers).
- Gateway auth is required by default (token/password).
- Non-loopback binds still **require** a shared token/password (`gateway.auth` or env).
- The wizard generates a gateway token by default (even on loopback).
- The UI sends `connect.params.auth.token` or `connect.params.auth.password`.
- With Serve, Tailscale identity headers can satisfy auth when
`gateway.auth.allowTailscale` is `true` (no token/password required). Set
`gateway.auth.allowTailscale: false` to require explicit credentials. See
[Tailscale](/gateway/tailscale) and [Security](/gateway/security).
- Tailscale Serve does not bypass auth; use token/password and keep
`gateway.auth.allowTailscale: false`. See [Tailscale](/gateway/tailscale) and
[Security](/gateway/security).
- `gateway.tailscale.mode: "funnel"` requires `gateway.auth.mode: "password"` (shared password).
## Building the UI