diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index 54adece003e..be626ba18a8 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -95,23 +95,36 @@ describe("security audit", () => { }); it("flags non-loopback bind without auth as critical", async () => { - const cfg: OpenClawConfig = { - gateway: { - bind: "lan", - auth: {}, - }, - }; + // Clear env tokens so resolveGatewayAuth defaults to mode=none + const prevToken = process.env.CLAWDBOT_GATEWAY_TOKEN; + const prevPassword = process.env.CLAWDBOT_GATEWAY_PASSWORD; + delete process.env.CLAWDBOT_GATEWAY_TOKEN; + delete process.env.CLAWDBOT_GATEWAY_PASSWORD; - const res = await runSecurityAudit({ - config: cfg, - env: {}, - includeFilesystem: false, - includeChannelSecurity: false, - }); + try { + const cfg: ClawdbotConfig = { + gateway: { + bind: "lan", + auth: {}, + }, + }; - expect( - res.findings.some((f) => f.checkId === "gateway.bind_no_auth" && f.severity === "critical"), - ).toBe(true); + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect( + res.findings.some((f) => f.checkId === "gateway.bind_no_auth" && f.severity === "critical"), + ).toBe(true); + } finally { + // Restore env + if (prevToken === undefined) delete process.env.CLAWDBOT_GATEWAY_TOKEN; + else process.env.CLAWDBOT_GATEWAY_TOKEN = prevToken; + if (prevPassword === undefined) delete process.env.CLAWDBOT_GATEWAY_PASSWORD; + else process.env.CLAWDBOT_GATEWAY_PASSWORD = prevPassword; + } }); it("warns when non-loopback bind has auth but no auth rate limit", async () => { @@ -593,6 +606,125 @@ describe("security audit", () => { ); }); + it("flags trusted-proxy auth mode as critical warning", async () => { + const cfg: ClawdbotConfig = { + gateway: { + bind: "lan", + trustedProxies: ["10.0.0.1"], + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-forwarded-user", + }, + }, + }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect(res.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + checkId: "gateway.trusted_proxy_auth", + severity: "critical", + }), + ]), + ); + }); + + it("flags trusted-proxy auth without trustedProxies configured", async () => { + const cfg: ClawdbotConfig = { + gateway: { + bind: "lan", + trustedProxies: [], + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-forwarded-user", + }, + }, + }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect(res.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + checkId: "gateway.trusted_proxy_no_proxies", + severity: "critical", + }), + ]), + ); + }); + + it("flags trusted-proxy auth without userHeader configured", async () => { + const cfg: ClawdbotConfig = { + gateway: { + bind: "lan", + trustedProxies: ["10.0.0.1"], + auth: { + mode: "trusted-proxy", + trustedProxy: {} as never, + }, + }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect(res.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + checkId: "gateway.trusted_proxy_no_user_header", + severity: "critical", + }), + ]), + ); + }); + + it("warns when trusted-proxy auth allows all users", async () => { + const cfg: ClawdbotConfig = { + gateway: { + bind: "lan", + trustedProxies: ["10.0.0.1"], + auth: { + mode: "trusted-proxy", + trustedProxy: { + userHeader: "x-forwarded-user", + allowUsers: [], + }, + }, + }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect(res.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + checkId: "gateway.trusted_proxy_no_allowlist", + severity: "warn", + }), + ]), + ); + }); + it("warns when multiple DM senders share the main session", async () => { const cfg: OpenClawConfig = { session: { dmScope: "main" } }; const plugins: ChannelPlugin[] = [