diff --git a/CHANGELOG.md b/CHANGELOG.md index cae46427d1e..2a8270dd154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Windows/gateway install: bound `schtasks` calls and fall back to the Startup-folder login item when task creation hangs, so native `openclaw gateway install` fails fast instead of wedging forever on broken Scheduled Task setups. +- Windows/gateway auth: stop attaching device identity on local loopback shared-token and password gateway calls, so native Windows agent replies no longer log stale `device signature expired` fallback noise before succeeding. - Telegram/media downloads: thread the same direct or proxy transport policy into SSRF-guarded file fetches so inbound attachments keep working when Telegram falls back between env-proxy and direct networking. (#44639) Thanks @obviyus. - Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv. - Discord/gateway startup: treat plain-text and transient `/gateway/bot` metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman. diff --git a/src/gateway/call.test.ts b/src/gateway/call.test.ts index 87590e58d49..e4d8d28f562 100644 --- a/src/gateway/call.test.ts +++ b/src/gateway/call.test.ts @@ -14,6 +14,7 @@ let lastClientOptions: { password?: string; tlsFingerprint?: string; scopes?: string[]; + deviceIdentity?: unknown; onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise; onClose?: (code: number, reason: string) => void; } | null = null; @@ -197,6 +198,19 @@ describe("callGateway url resolution", () => { expect(lastClientOptions?.token).toBe("explicit-token"); }); + it("does not attach device identity for local loopback shared-token auth", async () => { + setLocalLoopbackGatewayConfig(); + + await callGateway({ + method: "health", + token: "explicit-token", + }); + + expect(lastClientOptions?.url).toBe("ws://127.0.0.1:18789"); + expect(lastClientOptions?.token).toBe("explicit-token"); + expect(lastClientOptions?.deviceIdentity).toBeUndefined(); + }); + it("uses OPENCLAW_GATEWAY_URL env override in remote mode when remote URL is missing", async () => { loadConfig.mockReturnValue({ gateway: { mode: "remote", bind: "loopback", remote: {} }, diff --git a/src/gateway/call.ts b/src/gateway/call.ts index 31d11ac14b9..8e8f449fc59 100644 --- a/src/gateway/call.ts +++ b/src/gateway/call.ts @@ -81,6 +81,22 @@ export type GatewayConnectionDetails = { message: string; }; +function shouldAttachDeviceIdentityForGatewayCall(params: { + url: string; + token?: string; + password?: string; +}): boolean { + if (!(params.token || params.password)) { + return true; + } + try { + const parsed = new URL(params.url); + return !["127.0.0.1", "::1", "localhost"].includes(parsed.hostname); + } catch { + return true; + } +} + export type ExplicitGatewayAuth = { token?: string; password?: string; @@ -818,7 +834,9 @@ async function executeGatewayRequestWithScopes(params: { mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI, role: "operator", scopes, - deviceIdentity: loadOrCreateDeviceIdentity(), + deviceIdentity: shouldAttachDeviceIdentityForGatewayCall({ url, token, password }) + ? loadOrCreateDeviceIdentity() + : undefined, minProtocol: opts.minProtocol ?? PROTOCOL_VERSION, maxProtocol: opts.maxProtocol ?? PROTOCOL_VERSION, onHelloOk: async (hello) => {