fix: Tailscale serve + auth.mode=none exposes gateway to full...#50631
fix: Tailscale serve + auth.mode=none exposes gateway to full...#50631coygeek wants to merge 2 commits into
Conversation
…tive Co-authored-by: Claude <noreply@anthropic.com>
Greptile SummaryThis PR fixes a critical security vulnerability (CVSS 9.3) where combining The fix adds a minimal, targeted startup-time guard at Confidence Score: 5/5
Reviews (2): Last reviewed commit: "fix(gateway): narrow serve+none guard to..." | Re-trigger Greptile |
| if (tailscaleMode !== "off" && authMode === "none") { | ||
| throw new Error( | ||
| "tailscale serve/funnel requires gateway auth — refusing to start with auth.mode=none when tailscale is active", | ||
| ); | ||
| } |
There was a problem hiding this comment.
New guard is dead code for the funnel path
The new guard at line 130–134 is never reached when tailscaleMode === "funnel" + authMode === "none" because the preceding funnel guard at line 125 (tailscaleMode === "funnel" && authMode !== "password") already throws first. In practice this guard only fires for serve + none, even though the error message says "serve/funnel".
This isn't a bug — the existing funnel guard is strictly stronger (it rejects any non-password auth for funnel, not just none) — but the error message "tailscale serve/funnel requires gateway auth..." can be slightly misleading since a user with funnel + none will never see it. Consider narrowing the condition to tailscaleMode === "serve" to match what the guard actually covers:
| if (tailscaleMode !== "off" && authMode === "none") { | |
| throw new Error( | |
| "tailscale serve/funnel requires gateway auth — refusing to start with auth.mode=none when tailscale is active", | |
| ); | |
| } | |
| if (tailscaleMode === "serve" && authMode === "none") { | |
| throw new Error( | |
| "tailscale serve requires gateway auth — refusing to start with auth.mode=none when tailscale serve is active", | |
| ); | |
| } |
Alternatively, keep the broader condition but reorder: place this guard before the funnel guard so it acts as the first line of defence for both modes and the funnel guard then enforces the additional password requirement. Either way, the currently-passing "rejects tailscale funnel with auth.mode=none" test inadvertently verifies the older guard rather than the new one.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server-runtime-config.ts
Line: 130-134
Comment:
**New guard is dead code for the funnel path**
The new guard at line 130–134 is never reached when `tailscaleMode === "funnel"` + `authMode === "none"` because the preceding funnel guard at line 125 (`tailscaleMode === "funnel" && authMode !== "password"`) already throws first. In practice this guard only fires for `serve + none`, even though the error message says "serve/funnel".
This isn't a bug — the existing funnel guard is strictly stronger (it rejects any non-password auth for funnel, not just `none`) — but the error message `"tailscale serve/funnel requires gateway auth..."` can be slightly misleading since a user with `funnel + none` will never see it. Consider narrowing the condition to `tailscaleMode === "serve"` to match what the guard actually covers:
```suggestion
if (tailscaleMode === "serve" && authMode === "none") {
throw new Error(
"tailscale serve requires gateway auth — refusing to start with auth.mode=none when tailscale serve is active",
);
}
```
Alternatively, keep the broader condition but reorder: place this guard *before* the funnel guard so it acts as the first line of defence for both modes and the funnel guard then enforces the additional password requirement. Either way, the currently-passing `"rejects tailscale funnel with auth.mode=none"` test inadvertently verifies the older guard rather than the new one.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33f58e5957
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (tailscaleMode !== "off" && authMode === "none") { | ||
| throw new Error( | ||
| "tailscale serve/funnel requires gateway auth — refusing to start with auth.mode=none when tailscale is active", |
There was a problem hiding this comment.
Reject this serve+none combo before install flows consume it
When an existing config has gateway.auth.mode: "none" with gateway.tailscale.mode: "serve", this new runtime-only throw will make the installed daemon fail on first boot, but the setup/install path still treats that config as valid. resolveGatewayAuth() still sets allowTailscale=true for serve+none (src/gateway/auth.ts:271-273), and resolveGatewayInstallToken() uses that to skip generating or persisting a token (src/commands/gateway-install-token.ts:60-66) before openclaw gateway install writes the service. The install appears to succeed, then the service immediately dies when it reaches this branch, so this combination needs to be rejected or normalized before install/validation rather than only here at startup.
Useful? React with 👍 / 👎.
The new auth.mode=none guard was dead code for the funnel path because the preceding funnel guard (line 125) already rejects funnel+non-password. Narrow the condition from tailscaleMode !== "off" to tailscaleMode === "serve" so the guard matches only the configuration it actually covers, and update the error message and test assertion accordingly. Addresses Greptile review feedback on PR openclaw#50631.
|
@greptile-apps revuew |
|
Codex review: needs real behavior proof before merge. Latest ClawSweeper review: 2026-05-22 10:44 UTC / May 22, 2026, 6:44 AM ET. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. at source level. Current main accepts PR rating Rank-up moves:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. Real behavior proof Risk before merge
Maintainer options:
Next step before merge Security Review findings
Review detailsBest possible solution: Centralize shared validation that rejects explicit no-auth with Tailscale Serve in runtime and install/onboarding preflight, preserve loopback-only no-auth, update focused tests/docs, and require redacted live proof before merge. Do we have a high-confidence way to reproduce the issue? Yes, at source level. Current main accepts Is this the best way to solve the issue? No, not as written. The runtime guard is the right direction, but the maintainable fix should also reject this configuration before install/onboarding writes or accepts it. Label justifications:
Full review comments:
Overall correctness: patch is incorrect Security concerns:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against ff79299d68e3. |
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
Fixes #50630. Replaces stale PR #50631. Behavior: reject gateway auth mode none when Tailscale Serve or Funnel exposes the gateway, across config validation, install-token preflight, and runtime startup. Proof: - node scripts/run-vitest.mjs src/config/config.gateway-tailscale-bind.test.ts src/gateway/server-runtime-config.test.ts src/commands/doctor-gateway-auth-token.test.ts - .agents/skills/autoreview/scripts/autoreview --mode local - node scripts/crabbox-wrapper.mjs run --shell -- "pnpm check:changed" (run_5a999c1e11c0, exit 0) - GitHub PR checks clean on 0b306e8; prior checkout/diff failures were GitHub infrastructure and cleared after rebase.
Fixes openclaw#50630. Replaces stale PR openclaw#50631. Behavior: reject gateway auth mode none when Tailscale Serve or Funnel exposes the gateway, across config validation, install-token preflight, and runtime startup. Proof: - node scripts/run-vitest.mjs src/config/config.gateway-tailscale-bind.test.ts src/gateway/server-runtime-config.test.ts src/commands/doctor-gateway-auth-token.test.ts - .agents/skills/autoreview/scripts/autoreview --mode local - node scripts/crabbox-wrapper.mjs run --shell -- "pnpm check:changed" (run_5a999c1e11c0, exit 0) - GitHub PR checks clean on 0b306e8; prior checkout/diff failures were GitHub infrastructure and cleared after rebase.
Fixes openclaw#50630. Replaces stale PR openclaw#50631. Behavior: reject gateway auth mode none when Tailscale Serve or Funnel exposes the gateway, across config validation, install-token preflight, and runtime startup. Proof: - node scripts/run-vitest.mjs src/config/config.gateway-tailscale-bind.test.ts src/gateway/server-runtime-config.test.ts src/commands/doctor-gateway-auth-token.test.ts - .agents/skills/autoreview/scripts/autoreview --mode local - node scripts/crabbox-wrapper.mjs run --shell -- "pnpm check:changed" (run_5a999c1e11c0, exit 0) - GitHub PR checks clean on 0b306e8; prior checkout/diff failures were GitHub infrastructure and cleared after rebase.
Fixes openclaw#50630. Replaces stale PR openclaw#50631. Behavior: reject gateway auth mode none when Tailscale Serve or Funnel exposes the gateway, across config validation, install-token preflight, and runtime startup. Proof: - node scripts/run-vitest.mjs src/config/config.gateway-tailscale-bind.test.ts src/gateway/server-runtime-config.test.ts src/commands/doctor-gateway-auth-token.test.ts - .agents/skills/autoreview/scripts/autoreview --mode local - node scripts/crabbox-wrapper.mjs run --shell -- "pnpm check:changed" (run_5a999c1e11c0, exit 0) - GitHub PR checks clean on 0b306e8; prior checkout/diff failures were GitHub infrastructure and cleared after rebase.
Fix Summary
When
gateway.auth.modeis set to"none"andgateway.tailscale.modeis set to"serve", the gateway is reachable over the user's entire Tailnet with zero authentication. Any Tailnet peer connecting through the Tailscale serve URL receives{ ok: true, method: "none" }fromauthorizeGatewayConnectwithout presenting any credential, gaining full operator-level access to the gateway control plane.Issue Linkage
Fixes #50630
Security Snapshot
Implementation Details
Files Changed
src/gateway/server-runtime-config.test.ts(+49/-0)src/gateway/server-runtime-config.ts(+5/-0)Technical Analysis
The startup validation chain in
resolveGatewayRuntimeConfighas three relevant guards:assertGatewayAuthConfigured(auth.ts:285) — validates token/password/trusted-proxy preconditions but has no branch formode === "none". Passes silently for any tailscale configuration.server-runtime-config.ts:125) — blocksauthMode !== "password"whentailscaleMode === "funnel". Does not apply totailscaleMode === "serve".server-runtime-config.ts:133) — blocksmode: "none"on LAN/custom binds by requiringhasSharedSecret || authMode === "trusted-proxy". Does not apply here because Tailscale serve enforces loopback bind (server-runtime-config.ts:130), soisLoopbackHost(bindHost)istrue.The combination
tailscaleMode === "serve"+authMode === "none"satisfies all three guards:assertGatewayAuthConfiguredpasses silently formode: "none".resolveGatewayRuntimeConfigsucceeds, the gateway starts, andauthorizeGatewayConnectunconditionally returns{ ok: true, method: "none" }(auth.ts:402-404) for every incoming connection. Tailscale's local proxy relays requests from any Tailnet peer to the loopback gateway port, bypassing the auth check entirely.Additionally,
resolveGatewayAuthatauth.ts:271-273auto-enablesallowTailscalewhentailscaleMode === "serve"andmode !== "password" && mode !== "trusted-proxy"— this setsallowTailscale: trueformode: "none"+ serve, though themode: "none"early return inauthorizeGatewayConnectfires before the Tailscale header auth path is reached.Deterministic repro config:
Steps:
gateway.auth.mode = "none"andgateway.tailscale.mode = "serve"in~/.openclaw/openclaw.json.openclaw gateway run.https://<hostname>.ts.net).authMethod: "none"— no token, password, or device pairing required.Validation Evidence
pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm testRisk and Compatibility
non-breaking; no known regression impact
AI-Assisted Disclosure