106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
|
|
const note = vi.hoisted(() => vi.fn());
|
|
const pluginRegistry = vi.hoisted(() => ({ list: [] as unknown[] }));
|
|
|
|
vi.mock("../terminal/note.js", () => ({
|
|
note,
|
|
}));
|
|
|
|
vi.mock("../channels/plugins/index.js", () => ({
|
|
listChannelPlugins: () => pluginRegistry.list,
|
|
}));
|
|
|
|
import { noteSecurityWarnings } from "./doctor-security.js";
|
|
|
|
describe("noteSecurityWarnings gateway exposure", () => {
|
|
let prevToken: string | undefined;
|
|
let prevPassword: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
note.mockClear();
|
|
pluginRegistry.list = [];
|
|
prevToken = process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
prevPassword = process.env.OPENCLAW_GATEWAY_PASSWORD;
|
|
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (prevToken === undefined) {
|
|
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
} else {
|
|
process.env.OPENCLAW_GATEWAY_TOKEN = prevToken;
|
|
}
|
|
if (prevPassword === undefined) {
|
|
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
|
} else {
|
|
process.env.OPENCLAW_GATEWAY_PASSWORD = prevPassword;
|
|
}
|
|
});
|
|
|
|
const lastMessage = () => String(note.mock.calls.at(-1)?.[0] ?? "");
|
|
|
|
it("warns when exposed without auth", async () => {
|
|
const cfg = { gateway: { bind: "lan" } } as OpenClawConfig;
|
|
await noteSecurityWarnings(cfg);
|
|
const message = lastMessage();
|
|
expect(message).toContain("CRITICAL");
|
|
expect(message).toContain("without authentication");
|
|
});
|
|
|
|
it("uses env token to avoid critical warning", async () => {
|
|
process.env.OPENCLAW_GATEWAY_TOKEN = "token-123";
|
|
const cfg = { gateway: { bind: "lan" } } as OpenClawConfig;
|
|
await noteSecurityWarnings(cfg);
|
|
const message = lastMessage();
|
|
expect(message).toContain("WARNING");
|
|
expect(message).not.toContain("CRITICAL");
|
|
});
|
|
|
|
it("treats whitespace token as missing", async () => {
|
|
const cfg = {
|
|
gateway: { bind: "lan", auth: { mode: "token", token: " " } },
|
|
} as OpenClawConfig;
|
|
await noteSecurityWarnings(cfg);
|
|
const message = lastMessage();
|
|
expect(message).toContain("CRITICAL");
|
|
});
|
|
|
|
it("skips warning for loopback bind", async () => {
|
|
const cfg = { gateway: { bind: "loopback" } } as OpenClawConfig;
|
|
await noteSecurityWarnings(cfg);
|
|
const message = lastMessage();
|
|
expect(message).toContain("No channel security warnings detected");
|
|
expect(message).not.toContain("Gateway bound");
|
|
});
|
|
|
|
it("shows explicit dmScope config command for multi-user DMs", async () => {
|
|
pluginRegistry.list = [
|
|
{
|
|
id: "whatsapp",
|
|
meta: { label: "WhatsApp" },
|
|
config: {
|
|
listAccountIds: () => ["default"],
|
|
resolveAccount: () => ({}),
|
|
isEnabled: () => true,
|
|
isConfigured: () => true,
|
|
},
|
|
security: {
|
|
resolveDmPolicy: () => ({
|
|
policy: "allowlist",
|
|
allowFrom: ["alice", "bob"],
|
|
allowFromPath: "channels.whatsapp.",
|
|
approveHint: "approve",
|
|
}),
|
|
},
|
|
},
|
|
];
|
|
const cfg = { session: { dmScope: "main" } } as OpenClawConfig;
|
|
await noteSecurityWarnings(cfg);
|
|
const message = lastMessage();
|
|
expect(message).toContain('config set session.dmScope "per-channel-peer"');
|
|
});
|
|
});
|