fix: remove unsupported gateway auth off option
This commit is contained in:
@@ -52,6 +52,7 @@ Status: unreleased.
|
||||
- Web UI: improve WebChat image paste previews and allow image-only sends. (#1925) Thanks @smartprogrammer93.
|
||||
- Security: wrap external hook content by default with a per-hook opt-out. (#1827) Thanks @mertcicekci0.
|
||||
- Gateway: default auth now fail-closed (token/password required; Tailscale Serve identity remains allowed).
|
||||
- Onboarding: remove unsupported gateway auth "off" choice from onboarding/configure flows and CLI flags.
|
||||
|
||||
## 2026.1.24-3
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ Options:
|
||||
- `--opencode-zen-api-key <key>`
|
||||
- `--gateway-port <port>`
|
||||
- `--gateway-bind <loopback|lan|tailnet|auto|custom>`
|
||||
- `--gateway-auth <off|token|password>`
|
||||
- `--gateway-auth <token|password>`
|
||||
- `--gateway-token <token>`
|
||||
- `--gateway-password <password>`
|
||||
- `--remote-url <url>`
|
||||
|
||||
@@ -214,7 +214,7 @@ the Gateway likely refused to bind.
|
||||
- Fix: run `clawdbot doctor` to update it (or `clawdbot gateway install --force` for a full rewrite).
|
||||
|
||||
**If `Last gateway error:` mentions “refusing to bind … without auth”**
|
||||
- You set `gateway.bind` to a non-loopback mode (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) but left auth off.
|
||||
- You set `gateway.bind` to a non-loopback mode (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) but didn’t configure auth.
|
||||
- Fix: set `gateway.auth.mode` + `gateway.auth.token` (or export `CLAWDBOT_GATEWAY_TOKEN`) and restart the service.
|
||||
|
||||
**If `clawdbot gateway status` says `bind=tailnet` but no tailnet interface was found**
|
||||
|
||||
@@ -78,7 +78,7 @@ export function registerOnboardCommand(program: Command) {
|
||||
.option("--opencode-zen-api-key <key>", "OpenCode Zen API key")
|
||||
.option("--gateway-port <port>", "Gateway port")
|
||||
.option("--gateway-bind <mode>", "Gateway bind: loopback|tailnet|lan|auto|custom")
|
||||
.option("--gateway-auth <mode>", "Gateway auth: off|token|password")
|
||||
.option("--gateway-auth <mode>", "Gateway auth: token|password")
|
||||
.option("--gateway-token <token>", "Gateway token (token auth)")
|
||||
.option("--gateway-password <password>", "Gateway password (password auth)")
|
||||
.option("--remote-url <url>", "Remote Gateway WebSocket URL")
|
||||
|
||||
@@ -3,26 +3,18 @@ import { describe, expect, it } from "vitest";
|
||||
import { buildGatewayAuthConfig } from "./configure.js";
|
||||
|
||||
describe("buildGatewayAuthConfig", () => {
|
||||
it("clears token/password when auth is off", () => {
|
||||
const result = buildGatewayAuthConfig({
|
||||
existing: { mode: "token", token: "abc", password: "secret" },
|
||||
mode: "off",
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves allowTailscale when auth is off", () => {
|
||||
it("preserves allowTailscale when switching to token", () => {
|
||||
const result = buildGatewayAuthConfig({
|
||||
existing: {
|
||||
mode: "token",
|
||||
token: "abc",
|
||||
mode: "password",
|
||||
password: "secret",
|
||||
allowTailscale: true,
|
||||
},
|
||||
mode: "off",
|
||||
mode: "token",
|
||||
token: "abc",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowTailscale: true });
|
||||
expect(result).toEqual({ mode: "token", token: "abc", allowTailscale: true });
|
||||
});
|
||||
|
||||
it("drops password when switching to token", () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
promptModelAllowlist,
|
||||
} from "./model-picker.js";
|
||||
|
||||
type GatewayAuthChoice = "off" | "token" | "password";
|
||||
type GatewayAuthChoice = "token" | "password";
|
||||
|
||||
const ANTHROPIC_OAUTH_MODEL_KEYS = [
|
||||
"anthropic/claude-opus-4-5",
|
||||
@@ -30,9 +30,6 @@ export function buildGatewayAuthConfig(params: {
|
||||
const base: GatewayAuthConfig = {};
|
||||
if (typeof allowTailscale === "boolean") base.allowTailscale = allowTailscale;
|
||||
|
||||
if (params.mode === "off") {
|
||||
return Object.keys(base).length > 0 ? base : undefined;
|
||||
}
|
||||
if (params.mode === "token") {
|
||||
return { ...base, mode: "token", token: params.token };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buildGatewayAuthConfig } from "./configure.gateway-auth.js";
|
||||
import { confirm, select, text } from "./configure.shared.js";
|
||||
import { guardCancel, randomToken } from "./onboard-helpers.js";
|
||||
|
||||
type GatewayAuthChoice = "off" | "token" | "password";
|
||||
type GatewayAuthChoice = "token" | "password";
|
||||
|
||||
export async function promptGatewayConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
@@ -91,11 +91,6 @@ export async function promptGatewayConfig(
|
||||
await select({
|
||||
message: "Gateway auth",
|
||||
options: [
|
||||
{
|
||||
value: "off",
|
||||
label: "Off (loopback only)",
|
||||
hint: "Not recommended unless you fully trust local processes",
|
||||
},
|
||||
{ value: "token", label: "Token", hint: "Recommended default" },
|
||||
{ value: "password", label: "Password" },
|
||||
],
|
||||
@@ -165,11 +160,6 @@ export async function promptGatewayConfig(
|
||||
bind = "loopback";
|
||||
}
|
||||
|
||||
if (authMode === "off" && bind !== "loopback") {
|
||||
note("Non-loopback bind requires auth. Switching to token auth.", "Note");
|
||||
authMode = "token";
|
||||
}
|
||||
|
||||
if (tailscaleMode === "funnel" && authMode !== "password") {
|
||||
note("Tailscale funnel requires password auth.", "Note");
|
||||
authMode = "password";
|
||||
|
||||
@@ -210,7 +210,7 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
await fs.rm(stateDir, { recursive: true, force: true });
|
||||
}, 60_000);
|
||||
|
||||
it("auto-enables token auth when binding LAN and persists the token", async () => {
|
||||
it("auto-generates token auth when binding LAN and persists the token", async () => {
|
||||
if (process.platform === "win32") {
|
||||
// Windows runner occasionally drops the temp config write in this flow; skip to keep CI green.
|
||||
return;
|
||||
@@ -242,7 +242,6 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
installDaemon: false,
|
||||
gatewayPort: port,
|
||||
gatewayBind: "lan",
|
||||
gatewayAuth: "off",
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
@@ -28,16 +28,20 @@ export function applyNonInteractiveGatewayConfig(params: {
|
||||
|
||||
const port = hasGatewayPort ? (opts.gatewayPort as number) : params.defaultPort;
|
||||
let bind = opts.gatewayBind ?? "loopback";
|
||||
let authMode = opts.gatewayAuth ?? "token";
|
||||
const authModeRaw = opts.gatewayAuth ?? "token";
|
||||
if (authModeRaw !== "token" && authModeRaw !== "password") {
|
||||
runtime.error("Invalid --gateway-auth (use token|password).");
|
||||
runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
let authMode = authModeRaw;
|
||||
const tailscaleMode = opts.tailscale ?? "off";
|
||||
const tailscaleResetOnExit = Boolean(opts.tailscaleResetOnExit);
|
||||
|
||||
// Tighten config to safe combos:
|
||||
// - If Tailscale is on, force loopback bind (the tunnel handles external access).
|
||||
// - If binding beyond loopback, disallow auth=off.
|
||||
// - If using Tailscale Funnel, require password auth.
|
||||
if (tailscaleMode !== "off" && bind !== "loopback") bind = "loopback";
|
||||
if (authMode === "off" && bind !== "loopback") authMode = "token";
|
||||
if (tailscaleMode === "funnel" && authMode !== "password") authMode = "password";
|
||||
|
||||
let nextConfig = params.nextConfig;
|
||||
|
||||
@@ -32,7 +32,7 @@ export type AuthChoice =
|
||||
| "copilot-proxy"
|
||||
| "qwen-portal"
|
||||
| "skip";
|
||||
export type GatewayAuthChoice = "off" | "token" | "password";
|
||||
export type GatewayAuthChoice = "token" | "password";
|
||||
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
||||
export type GatewayBind = "loopback" | "lan" | "auto" | "custom" | "tailnet";
|
||||
export type TailscaleMode = "off" | "serve" | "funnel";
|
||||
|
||||
@@ -93,11 +93,6 @@ export async function configureGatewayForOnboarding(
|
||||
: ((await prompter.select({
|
||||
message: "Gateway auth",
|
||||
options: [
|
||||
{
|
||||
value: "off",
|
||||
label: "Off (loopback only)",
|
||||
hint: "Not recommended unless you fully trust local processes",
|
||||
},
|
||||
{
|
||||
value: "token",
|
||||
label: "Token",
|
||||
@@ -165,7 +160,6 @@ export async function configureGatewayForOnboarding(
|
||||
|
||||
// Safety + constraints:
|
||||
// - Tailscale wants bind=loopback so we never expose a non-loopback server + tailscale serve/funnel at once.
|
||||
// - Auth off only allowed for bind=loopback.
|
||||
// - Funnel requires password auth.
|
||||
if (tailscaleMode !== "off" && bind !== "loopback") {
|
||||
await prompter.note("Tailscale requires bind=loopback. Adjusting bind to loopback.", "Note");
|
||||
@@ -173,11 +167,6 @@ export async function configureGatewayForOnboarding(
|
||||
customBindHost = undefined;
|
||||
}
|
||||
|
||||
if (authMode === "off" && bind !== "loopback") {
|
||||
await prompter.note("Non-loopback bind requires auth. Switching to token auth.", "Note");
|
||||
authMode = "token";
|
||||
}
|
||||
|
||||
if (tailscaleMode === "funnel" && authMode !== "password") {
|
||||
await prompter.note("Tailscale funnel requires password auth.", "Note");
|
||||
authMode = "password";
|
||||
|
||||
@@ -244,7 +244,6 @@ export async function runOnboardingWizard(
|
||||
return "Auto";
|
||||
};
|
||||
const formatAuth = (value: GatewayAuthChoice) => {
|
||||
if (value === "off") return "Off (loopback only)";
|
||||
if (value === "token") return "Token (default)";
|
||||
return "Password";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user