Compare commits

...

3 Commits

Author SHA1 Message Date
Peter Steinberger
7146326c56 fix: use local auth for gateway probe (#1011) (thanks @ivanrvpereira) 2026-01-16 19:04:47 +00:00
Ivan Pereira
1a99be7450 test(security): add coverage for gateway probe auth selection 2026-01-16 19:02:58 +00:00
Ivan Pereira
05c2256324 fix(security): resolve local auth for gateway probe 2026-01-16 19:02:58 +00:00
3 changed files with 146 additions and 1 deletions

View File

@ -71,6 +71,7 @@
- Fix: allow local Tailscale Serve hostnames without treating tailnet clients as direct. (#885) — thanks @oswalpalash.
- Fix: reset sessions after role-ordering conflicts to recover from consecutive user turns. (#998)
- Fix: keep background exec aborts from killing backgrounded sessions while honoring timeouts.
- Fix: use local auth for gateway security probe unless remote mode has a URL. (#1011) — thanks @ivanrvpereira.
- Discord: truncate skill command descriptions for slash command limits. (#1018) — thanks @evalexpr.
## 2026.1.14-1

View File

@ -401,4 +401,148 @@ describe("security audit", () => {
]),
);
});
describe("maybeProbeGateway auth selection", () => {
it("uses local auth when gateway.mode is local", async () => {
let capturedAuth: { token?: string; password?: string } | undefined;
const cfg: ClawdbotConfig = {
gateway: {
mode: "local",
auth: { token: "local-token-abc123" },
},
};
await runSecurityAudit({
config: cfg,
deep: true,
deepTimeoutMs: 50,
includeFilesystem: false,
includeChannelSecurity: false,
probeGatewayFn: async (opts) => {
capturedAuth = opts.auth;
return {
ok: true,
url: opts.url,
connectLatencyMs: 10,
error: null,
close: null,
health: null,
status: null,
presence: null,
configSnapshot: null,
};
},
});
expect(capturedAuth?.token).toBe("local-token-abc123");
});
it("uses local auth when gateway.mode is undefined (default)", async () => {
let capturedAuth: { token?: string; password?: string } | undefined;
const cfg: ClawdbotConfig = {
gateway: {
auth: { token: "default-local-token" },
},
};
await runSecurityAudit({
config: cfg,
deep: true,
deepTimeoutMs: 50,
includeFilesystem: false,
includeChannelSecurity: false,
probeGatewayFn: async (opts) => {
capturedAuth = opts.auth;
return {
ok: true,
url: opts.url,
connectLatencyMs: 10,
error: null,
close: null,
health: null,
status: null,
presence: null,
configSnapshot: null,
};
},
});
expect(capturedAuth?.token).toBe("default-local-token");
});
it("uses remote auth when gateway.mode is remote with URL", async () => {
let capturedAuth: { token?: string; password?: string } | undefined;
const cfg: ClawdbotConfig = {
gateway: {
mode: "remote",
auth: { token: "local-token-should-not-use" },
remote: {
url: "ws://remote.example.com:18789",
token: "remote-token-xyz789",
},
},
};
await runSecurityAudit({
config: cfg,
deep: true,
deepTimeoutMs: 50,
includeFilesystem: false,
includeChannelSecurity: false,
probeGatewayFn: async (opts) => {
capturedAuth = opts.auth;
return {
ok: true,
url: opts.url,
connectLatencyMs: 10,
error: null,
close: null,
health: null,
status: null,
presence: null,
configSnapshot: null,
};
},
});
expect(capturedAuth?.token).toBe("remote-token-xyz789");
});
it("falls back to local auth when gateway.mode is remote but URL is missing", async () => {
let capturedAuth: { token?: string; password?: string } | undefined;
const cfg: ClawdbotConfig = {
gateway: {
mode: "remote",
auth: { token: "fallback-local-token" },
remote: {
token: "remote-token-should-not-use",
},
},
};
await runSecurityAudit({
config: cfg,
deep: true,
deepTimeoutMs: 50,
includeFilesystem: false,
includeChannelSecurity: false,
probeGatewayFn: async (opts) => {
capturedAuth = opts.auth;
return {
ok: true,
url: opts.url,
connectLatencyMs: 10,
error: null,
close: null,
health: null,
status: null,
presence: null,
configSnapshot: null,
};
},
});
expect(capturedAuth?.token).toBe("fallback-local-token");
});
});
});

View File

@ -538,7 +538,7 @@ async function maybeProbeGateway(params: {
return { token, password };
};
const auth = remoteUrlMissing ? resolveAuth("local") : resolveAuth("remote");
const auth = !isRemoteMode || remoteUrlMissing ? resolveAuth("local") : resolveAuth("remote");
const res = await params.probe({ url, auth, timeoutMs: params.timeoutMs }).catch((err) => ({
ok: false,
url,