diff --git a/CHANGELOG.md b/CHANGELOG.md index 668a91823..5fc2690ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Status: unreleased. ### Fixes - Security: harden Tailscale Serve auth by validating identity via local tailscaled before trusting headers. +- Security: harden gateway runtime defaults and security guidance. (#2000) Thanks @rhuanssauro. - Web UI: improve WebChat image paste previews and allow image-only sends. (#1925) Thanks @smartprogrammer93. - Gateway: default auth now fail-closed (token/password required; Tailscale Serve identity remains allowed). diff --git a/README.md b/README.md index 217a4b61c..6c20d6d6d 100644 --- a/README.md +++ b/README.md @@ -479,32 +479,32 @@ Thanks to all clawtributors:

steipete plum-dawg bohdanpodvirnyi iHildy joaohlisboa mneves75 MatthieuBizien MaudeBot Glucksberg rahthakor vrknetha radek-paclt Tobias Bischoff joshp123 czekaj mukhtharcm sebslight maxsumrall xadenryan rodrigouroz - juanpablodlc hsrvc magimetal meaningfool tyler6204 patelhiren NicholasSpisak jonisjongithub zerone0x abhisekbasu1 - jamesgroat claude JustYannicc Hyaxia dantelex SocialNerd42069 daveonkels google-labs-jules[bot] lc0rp mousberg - vignesh07 mteam88 dbhurley Mariano Belinky Eng. Juan Combetto TSavo julianengel bradleypriest benithors rohannagpal - timolins f-trycua benostein nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino - Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras andranik-sahakyan davidguttman sleontenko denysvitali orlyjamie - thewilloftheshadow sircrumpet peschee rafaelreis-r ratulsarna lutr0 danielz1z emanuelst KristijanJovanovski rdev - joshrad-dev kiranjd osolmaz adityashaw2 CashWilliams sheeek artuskg Takhoffman onutc pauloportella - neooriginal manuelhettich minghinmatthewlam myfunc travisirby buddyh connorshea kyleok mcinteerj dependabot[bot] - John-Rood timkrase uos-status gerardward2007 obviyus roshanasingh4 tosh-hamburg azade-c JonUleis bjesuiter - cheeeee Josh Phillips robbyczgw-cla dlauer pookNast Whoaa512 YuriNachos chriseidhof ngutman ysqander - aj47 superman32432432 Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr HeimdallStrategy - imfing jalehman jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk erikpr1994 - Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl chrisrodz - Friederike Seiler gabriel-trigo iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 manmal ogulcancelik pasogott petradonka - rubyrunsstuff siddhantjain suminhthanh svkozak VACInc wes-davis zats 24601 adam91holt ameno- - Chris Taylor Django Navarro evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash pcty-nextgen-service-account rmorse - Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot Clawd ClawdFx dguido EnzeD - erik-agens Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi - longmaba mickahouan mjrussell odnxe p6l-richard philipp-spiess robaxelsen Sash Catanzarite T5-AndyML travisp - VAC william arzt zknicker abhaymundhara alejandro maza andrewting19 anpoirier arthyn Asleep123 bolismauro - conhecendoia dasilva333 Developer Dimitrios Ploutarchos Drake Thomsen fal3 Felix Krause foeken ganghyun kim grrowl - gtsifrikas HazAT hrdwdmrbl hugobarauna Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin kitze levifig - Lloyd loukotal louzhixian martinpucik Matt mini Miles mrdbstn MSch Mustafa Tag Eldeen ndraiman - nexty5870 Noctivoro prathamdby ptn1411 reeltimeapps RLTCmpe Rolf Fredheim Rony Kelner Samrat Jha senoldogann - Seredeep sergical shiv19 shiyuanhai siraht snopoke testingabc321 The Admiral thesash Ubuntu - voidserf Vultr-Clawd Admin Wimmie wstock yazinsai ymat19 Zach Knickerbocker 0xJonHoldsCrypto aaronn Alphonse-arianee - atalovesyou Azade carlulsoe ddyo Erik hougangdev latitudeki5223 Manuel Maly Mourad Boustani odrobnik - pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock + juanpablodlc hsrvc magimetal meaningfool tyler6204 patelhiren NicholasSpisak jonisjongithub abhisekbasu1 zerone0x + jamesgroat claude JustYannicc SocialNerd42069 Hyaxia dantelex daveonkels google-labs-jules[bot] lc0rp mousberg + vignesh07 mteam88 joeynyc Eng. Juan Combetto Mariano Belinky dbhurley TSavo julianengel bradleypriest benithors + rohannagpal timolins benostein f-trycua nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu + nachoiacovino Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras andranik-sahakyan davidguttman sleontenko denysvitali + orlyjamie thewilloftheshadow sircrumpet peschee rafaelreis-r ratulsarna lutr0 danielz1z emanuelst KristijanJovanovski + CashWilliams rdev osolmaz joshrad-dev kiranjd adityashaw2 sheeek artuskg Takhoffman onutc + pauloportella neooriginal manuelhettich minghinmatthewlam myfunc travisirby buddyh connorshea kyleok mcinteerj + dependabot[bot] John-Rood timkrase uos-status gerardward2007 obviyus roshanasingh4 tosh-hamburg azade-c JonUleis + bjesuiter cheeeee Josh Phillips robbyczgw-cla dlauer pookNast Whoaa512 YuriNachos chriseidhof ngutman + ysqander aj47 superman32432432 Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr + HeimdallStrategy imfing jalehman jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk + erikpr1994 Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl + chrisrodz Friederike Seiler gabriel-trigo iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 manmal ogulcancelik pasogott + petradonka rubyrunsstuff siddhantjain suminhthanh svkozak VACInc wes-davis zats 24601 adam91holt + ameno- Chris Taylor Django Navarro evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash pcty-nextgen-service-account + rmorse Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot Clawd ClawdFx dguido + EnzeD erik-agens Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior + jverdi longmaba mickahouan mjrussell odnxe p6l-richard philipp-spiess robaxelsen Sash Catanzarite T5-AndyML + travisp VAC william arzt zknicker abhaymundhara alejandro maza andrewting19 anpoirier arthyn Asleep123 + bolismauro conhecendoia dasilva333 Developer Dimitrios Ploutarchos Drake Thomsen fal3 Felix Krause foeken ganghyun kim + grrowl gtsifrikas HazAT hrdwdmrbl hugobarauna Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin kitze + levifig Lloyd loukotal louzhixian martinpucik Matt mini Miles mrdbstn MSch Mustafa Tag Eldeen + ndraiman nexty5870 Noctivoro prathamdby ptn1411 reeltimeapps RLTCmpe Rolf Fredheim Rony Kelner Samrat Jha + senoldogann Seredeep sergical shiv19 shiyuanhai siraht snopoke testingabc321 The Admiral thesash + Ubuntu voidserf Vultr-Clawd Admin Wimmie wstock yazinsai ymat19 Zach Knickerbocker 0xJonHoldsCrypto aaronn + Alphonse-arianee atalovesyou Azade carlulsoe ddyo Erik hougangdev latitudeki5223 Manuel Maly Mourad Boustani + odrobnik pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock

diff --git a/SECURITY.md b/SECURITY.md index 11aa0b781..773924ccd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -17,10 +17,8 @@ For threat model + hardening guidance (including `clawdbot security audit --deep ### Node.js Version -Clawdbot requires **Node.js 22.12.0 or later** (LTS). This version includes important security patches: - -- CVE-2025-59466: async_hooks DoS vulnerability -- CVE-2026-21636: Permission model bypass vulnerability +Clawdbot requires **Node.js 22.12.0 or later** (LTS). Keep Node updated for +security patches and compatibility fixes. Verify your Node.js version: @@ -33,14 +31,16 @@ node --version # Should be v22.12.0 or later When running Clawdbot in Docker: 1. The official image runs as a non-root user (`node`) for reduced attack surface -2. Use `--read-only` flag when possible for additional filesystem protection +2. Use `--read-only` when possible and provide a writable state volume 3. Limit container capabilities with `--cap-drop=ALL` Example secure Docker run: ```bash docker run --read-only --cap-drop=ALL \ - -v clawdbot-data:/app/data \ + --tmpfs /tmp \ + -e CLAWDBOT_STATE_DIR=/data \ + -v clawdbot-data:/data \ clawdbot/clawdbot:latest ``` diff --git a/docs/gateway/configuration-examples.md b/docs/gateway/configuration-examples.md index ff38859a7..bb8f0b70b 100644 --- a/docs/gateway/configuration-examples.md +++ b/docs/gateway/configuration-examples.md @@ -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" }, diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 97427debe..97148ff47 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -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. diff --git a/docs/gateway/remote.md b/docs/gateway/remote.md index aa3fff7f7..8a5f5fff6 100644 --- a/docs/gateway/remote.md +++ b/docs/gateway/remote.md @@ -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). diff --git a/docs/gateway/security.md b/docs/gateway/security.md index d13d830cf..6ca2df023 100644 --- a/docs/gateway/security.md +++ b/docs/gateway/security.md @@ -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. diff --git a/docs/gateway/tailscale.md b/docs/gateway/tailscale.md index e6477fbfc..41baebc98 100644 --- a/docs/gateway/tailscale.md +++ b/docs/gateway/tailscale.md @@ -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. diff --git a/docs/help/faq.md b/docs/help/faq.md index aadbda9de..1d744047a 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -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:///`. 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:///`, and authenticate with your token/password (keep `gateway.auth.allowTailscale: false`). - **Tailnet bind**: run `clawdbot 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/?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. diff --git a/docs/platforms/digitalocean.md b/docs/platforms/digitalocean.md index 632057c84..04edb34d8 100644 --- a/docs/platforms/digitalocean.md +++ b/docs/platforms/digitalocean.md @@ -122,8 +122,8 @@ clawdbot gateway restart Open: `https:///` 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 diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md index 996ed0fe4..cf2b403cf 100644 --- a/docs/web/control-ui.md +++ b/docs/web/control-ui.md @@ -68,13 +68,9 @@ clawdbot gateway --tailscale serve Open: - `https:///` (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 diff --git a/docs/web/dashboard.md b/docs/web/dashboard.md index 81d0aacc4..ad0a57a6c 100644 --- a/docs/web/dashboard.md +++ b/docs/web/dashboard.md @@ -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 diff --git a/docs/web/index.md b/docs/web/index.md index 0e1fadfa4..3e919db7e 100644 --- a/docs/web/index.md +++ b/docs/web/index.md @@ -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 diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index e9a682855..c70da1395 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -9,6 +9,6 @@ ] }, "peerDependencies": { - "clawdbot": ">=2026.1.24" + "clawdbot": ">=2026.1.25" } }