diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index 547083886..699d557e4 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -2060,7 +2060,7 @@ See [Plugins](/tools/plugin). - **Auth**: required by default. Non-loopback binds require a shared token/password. Onboarding wizard generates a token by default. - `auth.mode: "none"`: explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts. - `auth.mode: "trusted-proxy"`: delegate auth to an identity-aware reverse proxy and trust identity headers from `gateway.trustedProxies` (see [Trusted Proxy Auth](/gateway/trusted-proxy-auth)). -- `auth.allowTailscale`: when `true`, Tailscale Serve identity headers satisfy auth (verified via `tailscale whois`). Defaults to `true` when `tailscale.mode = "serve"`. +- `auth.allowTailscale`: when `true`, Tailscale Serve identity headers satisfy auth (verified via `tailscale whois`). This tokenless flow assumes the gateway host is trusted. Defaults to `true` when `tailscale.mode = "serve"`. - `auth.rateLimit`: optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return `429` + `Retry-After`. - `auth.rateLimit.exemptLoopback` defaults to `true`; set `false` when you intentionally want localhost traffic rate-limited too (for test setups or strict proxy deployments). - `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth). diff --git a/docs/gateway/remote.md b/docs/gateway/remote.md index fa6a08b42..f95ecd90a 100644 --- a/docs/gateway/remote.md +++ b/docs/gateway/remote.md @@ -123,7 +123,8 @@ Short version: **keep the Gateway loopback-only** unless you’re sure you need - `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. + This tokenless flow assumes the gateway host is trusted. Set it to `false` if you + want tokens/passwords instead. - Treat browser control like operator access: tailnet-only + deliberate node pairing. Deep dive: [Security](/gateway/security). diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md index 4d0ba9005..ee873a0f0 100644 --- a/docs/gateway/security/index.md +++ b/docs/gateway/security/index.md @@ -528,6 +528,11 @@ 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. +**Trust assumption:** tokenless Serve auth assumes the gateway host is trusted. +Do not treat this as protection against hostile same-host processes. If untrusted +local code may run on the gateway host, disable `gateway.auth.allowTailscale` +and require token/password auth. + **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 (or [Trusted Proxy Auth](/gateway/trusted-proxy-auth)) instead. diff --git a/docs/gateway/tailscale.md b/docs/gateway/tailscale.md index 3a12b7fe1..8a195d6d3 100644 --- a/docs/gateway/tailscale.md +++ b/docs/gateway/tailscale.md @@ -33,6 +33,9 @@ daemon (`tailscale whois`) and matching it to the header before accepting it. OpenClaw 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. +This tokenless flow assumes the gateway host is trusted. If untrusted local code +may run on the same host, disable `gateway.auth.allowTailscale` and require +token/password auth instead. To require explicit credentials, set `gateway.auth.allowTailscale: false` or force `gateway.auth.mode: "password"`. diff --git a/docs/help/faq.md b/docs/help/faq.md index 9e61b5957..6958fec32 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -348,7 +348,7 @@ The wizard opens your browser with a clean (non-tokenized) dashboard URL right a **Not on localhost:** -- **Tailscale Serve** (recommended): keep bind loopback, run `openclaw gateway --tailscale serve`, open `https:///`. If `gateway.auth.allowTailscale` is `true`, identity headers satisfy auth (no token). +- **Tailscale Serve** (recommended): keep bind loopback, run `openclaw gateway --tailscale serve`, open `https:///`. If `gateway.auth.allowTailscale` is `true`, identity headers satisfy auth (no token, assumes trusted gateway host). - **Tailnet bind**: run `openclaw gateway --bind tailnet --token ""`, open `http://: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/` and paste the token in Control UI settings. diff --git a/docs/platforms/digitalocean.md b/docs/platforms/digitalocean.md index 17012388b..4cd608770 100644 --- a/docs/platforms/digitalocean.md +++ b/docs/platforms/digitalocean.md @@ -132,7 +132,7 @@ Open: `https:///` Notes: -- Serve keeps the Gateway loopback-only and authenticates via Tailscale identity headers. +- Serve keeps the Gateway loopback-only and authenticates via Tailscale identity headers (tokenless auth assumes trusted gateway host). - To require token/password instead, set `gateway.auth.allowTailscale: false` or use `gateway.auth.mode: "password"`. **Option C: Tailnet bind (no Serve)** diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md index fad37a47a..5050236bc 100644 --- a/docs/web/control-ui.md +++ b/docs/web/control-ui.md @@ -124,6 +124,8 @@ verifies the identity by resolving the `x-forwarded-for` address with 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. +Tokenless Serve auth assumes the gateway host is trusted. If untrusted local +code may run on that host, require token/password auth. ### Bind to tailnet + token diff --git a/docs/web/dashboard.md b/docs/web/dashboard.md index 5c33455f0..b4432a598 100644 --- a/docs/web/dashboard.md +++ b/docs/web/dashboard.md @@ -37,7 +37,7 @@ Prefer localhost, Tailscale Serve, or an SSH tunnel. - **Localhost**: open `http://127.0.0.1:18789/`. - **Token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`); the UI stores a copy in localStorage after you connect. -- **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 (tokenless if `gateway.auth.allowTailscale: true`, assumes trusted gateway host), tailnet bind with a token, or an SSH tunnel. See [Web surfaces](/web). ## If you see “unauthorized” / 1008 diff --git a/docs/web/index.md b/docs/web/index.md index 3ec00abad..6d1b40990 100644 --- a/docs/web/index.md +++ b/docs/web/index.md @@ -104,7 +104,8 @@ Open: - 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](/gateway/tailscale) and [Security](/gateway/security). This + tokenless flow assumes the gateway host is trusted. - `gateway.tailscale.mode: "funnel"` requires `gateway.auth.mode: "password"` (shared password). ## Building the UI