From 11d71ca35219b27d3604f99b181006d19129461e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 20 Mar 2026 13:27:39 -0700 Subject: [PATCH] pairing: keep setup codes bootstrap-token only (#51259) --- src/cli/qr-cli.test.ts | 29 +++++---------- src/cli/qr-dashboard.integration.test.ts | 7 +--- src/pairing/setup-code.test.ts | 21 ++++------- src/pairing/setup-code.ts | 46 ------------------------ 4 files changed, 17 insertions(+), 86 deletions(-) diff --git a/src/cli/qr-cli.test.ts b/src/cli/qr-cli.test.ts index 3a0490d996f..1bc8a645719 100644 --- a/src/cli/qr-cli.test.ts +++ b/src/cli/qr-cli.test.ts @@ -135,24 +135,16 @@ describe("registerQrCli", () => { }; } - function expectLoggedSetupCode( - url: string, - auth?: { - token?: string; - password?: string; - }, - ) { + function expectLoggedSetupCode(url: string) { const expected = encodePairingSetupCode({ url, bootstrapToken: "bootstrap-123", - ...(auth?.token ? { token: auth.token } : {}), - ...(auth?.password ? { password: auth.password } : {}), }); expect(runtime.log).toHaveBeenCalledWith(expected); } - function expectLoggedLocalSetupCode(auth?: { token?: string; password?: string }) { - expectLoggedSetupCode("ws://gateway.local:18789", auth); + function expectLoggedLocalSetupCode() { + expectLoggedSetupCode("ws://gateway.local:18789"); } function mockTailscaleStatusLookup() { @@ -189,7 +181,6 @@ describe("registerQrCli", () => { const expected = encodePairingSetupCode({ url: "ws://gateway.local:18789", bootstrapToken: "bootstrap-123", - token: "tok", }); expect(runtime.log).toHaveBeenCalledWith(expected); expect(qrGenerate).not.toHaveBeenCalled(); @@ -225,7 +216,7 @@ describe("registerQrCli", () => { await runQr(["--setup-code-only", "--token", "override-token"]); - expectLoggedLocalSetupCode({ token: "override-token" }); + expectLoggedLocalSetupCode(); }); it("skips local password SecretRef resolution when --token override is provided", async () => { @@ -237,7 +228,7 @@ describe("registerQrCli", () => { await runQr(["--setup-code-only", "--token", "override-token"]); - expectLoggedLocalSetupCode({ token: "override-token" }); + expectLoggedLocalSetupCode(); }); it("resolves local gateway auth password SecretRefs before setup code generation", async () => { @@ -250,7 +241,7 @@ describe("registerQrCli", () => { await runQr(["--setup-code-only"]); - expectLoggedLocalSetupCode({ password: "local-password-secret" }); + expectLoggedLocalSetupCode(); expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled(); }); @@ -264,7 +255,7 @@ describe("registerQrCli", () => { await runQr(["--setup-code-only"]); - expectLoggedLocalSetupCode({ password: "password-from-env" }); + expectLoggedLocalSetupCode(); expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled(); }); @@ -279,7 +270,7 @@ describe("registerQrCli", () => { await runQr(["--setup-code-only"]); - expectLoggedLocalSetupCode({ token: "token-123" }); + expectLoggedLocalSetupCode(); expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled(); }); @@ -293,7 +284,7 @@ describe("registerQrCli", () => { await runQr(["--setup-code-only"]); - expectLoggedLocalSetupCode({ password: "inferred-password" }); + expectLoggedLocalSetupCode(); expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled(); }); @@ -342,7 +333,6 @@ describe("registerQrCli", () => { const expected = encodePairingSetupCode({ url: "wss://remote.example.com:444", bootstrapToken: "bootstrap-123", - token: "remote-tok", }); expect(runtime.log).toHaveBeenCalledWith(expected); expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith( @@ -386,7 +376,6 @@ describe("registerQrCli", () => { const expected = encodePairingSetupCode({ url: "wss://remote.example.com:444", bootstrapToken: "bootstrap-123", - token: "remote-tok", }); expect(runtime.log).toHaveBeenCalledWith(expected); }); diff --git a/src/cli/qr-dashboard.integration.test.ts b/src/cli/qr-dashboard.integration.test.ts index 559b9a8fc15..81550c5922a 100644 --- a/src/cli/qr-dashboard.integration.test.ts +++ b/src/cli/qr-dashboard.integration.test.ts @@ -69,8 +69,6 @@ function createGatewayTokenRefFixture() { function decodeSetupCode(setupCode: string): { url?: string; bootstrapToken?: string; - token?: string; - password?: string; } { const padded = setupCode.replace(/-/g, "+").replace(/_/g, "/"); const padLength = (4 - (padded.length % 4)) % 4; @@ -79,8 +77,6 @@ function decodeSetupCode(setupCode: string): { return JSON.parse(json) as { url?: string; bootstrapToken?: string; - token?: string; - password?: string; }; } @@ -119,7 +115,7 @@ describe("cli integration: qr + dashboard token SecretRef", () => { delete process.env.SHARED_GATEWAY_TOKEN; }); - it("uses the same resolved token SecretRef for both qr and dashboard commands", async () => { + it("uses the same resolved token SecretRef for qr auth validation and dashboard commands", async () => { const fixture = createGatewayTokenRefFixture(); process.env.SHARED_GATEWAY_TOKEN = "shared-token-123"; loadConfigMock.mockReturnValue(fixture); @@ -137,7 +133,6 @@ describe("cli integration: qr + dashboard token SecretRef", () => { const payload = decodeSetupCode(setupCode ?? ""); expect(payload.url).toBe("ws://gateway.local:18789"); expect(payload.bootstrapToken).toBeTruthy(); - expect(payload.token).toBe("shared-token-123"); expect(runtimeErrors).toEqual([]); runtimeLogs.length = 0; diff --git a/src/pairing/setup-code.test.ts b/src/pairing/setup-code.test.ts index b1d80a5e50d..6622f6c010f 100644 --- a/src/pairing/setup-code.test.ts +++ b/src/pairing/setup-code.test.ts @@ -45,8 +45,6 @@ describe("pairing setup code", () => { authLabel: string; url?: string; urlSource?: string; - token?: string; - password?: string; }, ) { expect(resolved.ok).toBe(true); @@ -55,8 +53,6 @@ describe("pairing setup code", () => { } expect(resolved.authLabel).toBe(params.authLabel); expect(resolved.payload.bootstrapToken).toBe("bootstrap-123"); - expect(resolved.payload.token).toBe(params.token); - expect(resolved.payload.password).toBe(params.password); if (params.url) { expect(resolved.payload.url).toBe(params.url); } @@ -117,7 +113,6 @@ describe("pairing setup code", () => { payload: { url: "ws://gateway.local:19001", bootstrapToken: "bootstrap-123", - token: "tok_123", }, authLabel: "token", urlSource: "gateway.bind=custom", @@ -144,7 +139,7 @@ describe("pairing setup code", () => { }, ); - expectResolvedSetupOk(resolved, { authLabel: "password", password: "resolved-password" }); + expectResolvedSetupOk(resolved, { authLabel: "password" }); }); it("uses OPENCLAW_GATEWAY_PASSWORD without resolving configured password SecretRef", async () => { @@ -167,7 +162,7 @@ describe("pairing setup code", () => { }, ); - expectResolvedSetupOk(resolved, { authLabel: "password", password: "password-from-env" }); + expectResolvedSetupOk(resolved, { authLabel: "password" }); }); it("does not resolve gateway.auth.password SecretRef in token mode", async () => { @@ -189,7 +184,7 @@ describe("pairing setup code", () => { }, ); - expectResolvedSetupOk(resolved, { authLabel: "token", token: "tok_123" }); + expectResolvedSetupOk(resolved, { authLabel: "token" }); }); it("resolves gateway.auth.token SecretRef for pairing payload", async () => { @@ -212,7 +207,7 @@ describe("pairing setup code", () => { }, ); - expectResolvedSetupOk(resolved, { authLabel: "token", token: "resolved-token" }); + expectResolvedSetupOk(resolved, { authLabel: "token" }); }); it("errors when gateway.auth.token SecretRef is unresolved in token mode", async () => { @@ -261,13 +256,13 @@ describe("pairing setup code", () => { id: "MISSING_GW_TOKEN", }); - expectResolvedSetupOk(resolved, { authLabel: "password", password: "password-from-env" }); + expectResolvedSetupOk(resolved, { authLabel: "password" }); }); it("does not treat env-template token as plaintext in inferred mode", async () => { const resolved = await resolveInferredModeWithPasswordEnv("${MISSING_GW_TOKEN}"); - expectResolvedSetupOk(resolved, { authLabel: "password", password: "password-from-env" }); + expectResolvedSetupOk(resolved, { authLabel: "password" }); }); it("requires explicit auth mode when token and password are both configured", async () => { @@ -333,7 +328,7 @@ describe("pairing setup code", () => { }, ); - expectResolvedSetupOk(resolved, { authLabel: "token", token: "new-token" }); + expectResolvedSetupOk(resolved, { authLabel: "token" }); }); it("errors when gateway is loopback only", async () => { @@ -367,7 +362,6 @@ describe("pairing setup code", () => { payload: { url: "wss://mb-server.tailnet.ts.net", bootstrapToken: "bootstrap-123", - password: "secret", }, authLabel: "password", urlSource: "gateway.tailscale.mode=serve", @@ -396,7 +390,6 @@ describe("pairing setup code", () => { payload: { url: "wss://remote.example.com:444", bootstrapToken: "bootstrap-123", - token: "tok_123", }, authLabel: "token", urlSource: "gateway.remote.url", diff --git a/src/pairing/setup-code.ts b/src/pairing/setup-code.ts index c64ae36077e..6a2c5dd0b39 100644 --- a/src/pairing/setup-code.ts +++ b/src/pairing/setup-code.ts @@ -16,8 +16,6 @@ import { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js"; export type PairingSetupPayload = { url: string; bootstrapToken: string; - token?: string; - password?: string; }; export type PairingSetupCommandResult = { @@ -64,11 +62,6 @@ type ResolveAuthLabelResult = { error?: string; }; -type ResolveSharedAuthResult = { - token?: string; - password?: string; -}; - function normalizeUrl(raw: string, schemeFallback: "ws" | "wss"): string | null { const trimmed = raw.trim(); if (!trimmed) { @@ -213,41 +206,6 @@ function resolvePairingSetupAuthLabel( return { error: "Gateway auth is not configured (no token or password)." }; } -function resolvePairingSetupSharedAuth( - cfg: OpenClawConfig, - env: NodeJS.ProcessEnv, -): ResolveSharedAuthResult { - const defaults = cfg.secrets?.defaults; - const tokenRef = resolveSecretInputRef({ - value: cfg.gateway?.auth?.token, - defaults, - }).ref; - const passwordRef = resolveSecretInputRef({ - value: cfg.gateway?.auth?.password, - defaults, - }).ref; - const token = - resolveGatewayTokenFromEnv(env) || - (tokenRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.token)); - const password = - resolveGatewayPasswordFromEnv(env) || - (passwordRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.password)); - const mode = cfg.gateway?.auth?.mode; - if (mode === "token") { - return { token }; - } - if (mode === "password") { - return { password }; - } - if (token) { - return { token }; - } - if (password) { - return { password }; - } - return {}; -} - async function resolveGatewayTokenSecretRef( cfg: OpenClawConfig, env: NodeJS.ProcessEnv, @@ -417,8 +375,6 @@ export async function resolvePairingSetupFromConfig( if (authLabel.error) { return { ok: false, error: authLabel.error }; } - const sharedAuth = resolvePairingSetupSharedAuth(cfgForAuth, env); - const urlResult = await resolveGatewayUrl(cfgForAuth, { env, publicUrl: options.publicUrl, @@ -445,8 +401,6 @@ export async function resolvePairingSetupFromConfig( baseDir: options.pairingBaseDir, }) ).token, - ...(sharedAuth.token ? { token: sharedAuth.token } : {}), - ...(sharedAuth.password ? { password: sharedAuth.password } : {}), }, authLabel: authLabel.label, urlSource: urlResult.source ?? "unknown",