Security: inject channel config-fix plugins in tests

This commit is contained in:
Peter Steinberger
2026-04-07 11:50:39 +08:00
parent 8c1b954c1b
commit b59560c49a
2 changed files with 86 additions and 4 deletions

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import type { ChannelPlugin } from "../channels/plugins/types.js";
import { fixSecurityFootguns } from "./fix.js";
const isWindows = process.platform === "win32";
@@ -30,6 +31,70 @@ describe("security fix", () => {
OPENCLAW_CONFIG_PATH: configPath,
});
const createWhatsAppConfigFixTestPlugin = (storeAllowFrom: string[]): ChannelPlugin => ({
id: "whatsapp",
meta: {
id: "whatsapp",
label: "WhatsApp",
selectionLabel: "WhatsApp",
docsPath: "/docs/testing",
blurb: "test stub",
},
capabilities: {
chatTypes: ["direct", "group"],
},
config: {
listAccountIds: () => ["default"],
inspectAccount: () => ({ accountId: "default", enabled: true, configured: true, config: {} }),
resolveAccount: () => ({ accountId: "default", enabled: true, config: {} }),
isEnabled: () => true,
isConfigured: () => true,
},
security: {
applyConfigFixes: async ({ cfg }) => {
if (storeAllowFrom.length === 0) {
return { config: cfg, changes: [] };
}
const next = structuredClone(cfg ?? {});
const whatsapp = next.channels?.whatsapp as Record<string, unknown> | undefined;
if (!whatsapp || typeof whatsapp !== "object") {
return { config: cfg, changes: [] };
}
const changes: string[] = [];
let changed = false;
const maybeApply = (prefix: string, holder: Record<string, unknown>) => {
if (holder.groupPolicy !== "allowlist") {
return;
}
const allowFrom = Array.isArray(holder.allowFrom) ? holder.allowFrom : [];
const groupAllowFrom = Array.isArray(holder.groupAllowFrom) ? holder.groupAllowFrom : [];
if (allowFrom.length > 0 || groupAllowFrom.length > 0) {
return;
}
holder.groupAllowFrom = [...storeAllowFrom];
changes.push(`${prefix}groupAllowFrom=pairing-store`);
changed = true;
};
maybeApply("channels.whatsapp.", whatsapp);
const accounts = whatsapp.accounts;
if (accounts && typeof accounts === "object") {
for (const [accountId, value] of Object.entries(accounts)) {
if (!value || typeof value !== "object") {
continue;
}
maybeApply(
`channels.whatsapp.accounts.${accountId}.`,
value as Record<string, unknown>,
);
}
}
return { config: changed ? next : cfg, changes };
},
},
});
const writeJsonConfig = async (configPath: string, config: Record<string, unknown>) => {
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
};
@@ -45,9 +110,13 @@ describe("security fix", () => {
const readParsedConfig = async (configPath: string) =>
JSON.parse(await fs.readFile(configPath, "utf-8")) as Record<string, unknown>;
const runFixAndReadChannels = async (stateDir: string, configPath: string) => {
const runFixAndReadChannels = async (
stateDir: string,
configPath: string,
channelPlugins?: ChannelPlugin[],
) => {
const env = createFixEnv(stateDir, configPath);
const res = await fixSecurityFootguns({ env, stateDir, configPath });
const res = await fixSecurityFootguns({ env, stateDir, configPath, channelPlugins });
const parsed = await readParsedConfig(configPath);
return {
res,
@@ -71,7 +140,9 @@ describe("security fix", () => {
}) => {
await writeWhatsAppConfig(params.configPath, params.whatsapp);
await writeWhatsAppAllowFromStore(params.stateDir, params.allowFromStore);
return runFixAndReadChannels(params.stateDir, params.configPath);
return runFixAndReadChannels(params.stateDir, params.configPath, [
createWhatsAppConfigFixTestPlugin(params.allowFromStore),
]);
};
const writeWhatsAppAllowFromStore = async (stateDir: string, allowFrom: string[]) => {
@@ -148,7 +219,12 @@ describe("security fix", () => {
await writeWhatsAppAllowFromStore(stateDir, [" +15551234567 "]);
const env = createFixEnv(stateDir, configPath);
const res = await fixSecurityFootguns({ env, stateDir, configPath });
const res = await fixSecurityFootguns({
env,
stateDir,
configPath,
channelPlugins: [createWhatsAppConfigFixTestPlugin(["+15551234567"])],
});
expect(res.ok).toBe(true);
expect(res.configWritten).toBe(true);
expect(res.changes).toEqual(

View File

@@ -248,10 +248,14 @@ function applyConfigFixes(params: { cfg: OpenClawConfig; env: NodeJS.ProcessEnv
async function collectChannelSecurityConfigFixMutation(params: {
cfg: OpenClawConfig;
env: NodeJS.ProcessEnv;
channelPlugins?: ChannelPlugin[];
}) {
let nextCfg = params.cfg;
const changes: string[] = [];
const collectPlugins = (): ChannelPlugin[] => {
if (params.channelPlugins) {
return params.channelPlugins;
}
try {
const pluginIds = Object.keys(params.cfg.channels ?? {}).filter(Boolean);
if (pluginIds.length === 0) {
@@ -358,6 +362,7 @@ export async function fixSecurityFootguns(opts?: {
configPath?: string;
platform?: NodeJS.Platform;
exec?: ExecFn;
channelPlugins?: ChannelPlugin[];
}): Promise<SecurityFixResult> {
const env = opts?.env ?? process.env;
const platform = opts?.platform ?? process.platform;
@@ -381,6 +386,7 @@ export async function fixSecurityFootguns(opts?: {
const channelFixes = await collectChannelSecurityConfigFixMutation({
cfg: fixed.cfg,
env,
channelPlugins: opts?.channelPlugins,
});
changes = [...fixed.changes, ...channelFixes.changes];