mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:01:01 +00:00
fix: harden read-only channel discovery
This commit is contained in:
@@ -16,12 +16,16 @@ function writeExternalSetupChannelPlugin(
|
||||
pluginDir?: string;
|
||||
pluginId?: string;
|
||||
channelId?: string;
|
||||
manifestChannelIds?: string[];
|
||||
setupChannelId?: string;
|
||||
} = {},
|
||||
) {
|
||||
useNoBundledPlugins();
|
||||
const pluginDir = options.pluginDir ?? makeTempDir();
|
||||
const pluginId = options.pluginId ?? "external-chat";
|
||||
const channelId = options.channelId ?? "external-chat";
|
||||
const manifestChannelIds = options.manifestChannelIds ?? [channelId];
|
||||
const setupChannelId = options.setupChannelId ?? channelId;
|
||||
const fullMarker = path.join(pluginDir, "full-loaded.txt");
|
||||
const setupMarker = path.join(pluginDir, "setup-loaded.txt");
|
||||
const setupEntry = options.setupEntry !== false;
|
||||
@@ -48,7 +52,7 @@ function writeExternalSetupChannelPlugin(
|
||||
{
|
||||
id: pluginId,
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: [channelId],
|
||||
channels: manifestChannelIds,
|
||||
channelEnvVars: {
|
||||
[channelId]: ["EXTERNAL_CHAT_TOKEN"],
|
||||
},
|
||||
@@ -107,12 +111,12 @@ module.exports = {
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(setupMarker)}, "loaded", "utf-8");
|
||||
module.exports = {
|
||||
plugin: {
|
||||
id: ${JSON.stringify(channelId)},
|
||||
id: ${JSON.stringify(setupChannelId)},
|
||||
meta: {
|
||||
id: ${JSON.stringify(channelId)},
|
||||
id: ${JSON.stringify(setupChannelId)},
|
||||
label: "External Chat",
|
||||
selectionLabel: "External Chat",
|
||||
docsPath: ${JSON.stringify(`/channels/${channelId}`)},
|
||||
docsPath: ${JSON.stringify(`/channels/${setupChannelId}`)},
|
||||
blurb: "setup entry",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
@@ -123,11 +127,11 @@ module.exports = {
|
||||
outbound: { deliveryMode: "direct" },
|
||||
secrets: {
|
||||
secretTargetRegistryEntries: [
|
||||
{
|
||||
id: ${JSON.stringify(`channels.${channelId}.token`)},
|
||||
targetType: "channel",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: ${JSON.stringify(`channels.${channelId}.token`)},
|
||||
{
|
||||
id: ${JSON.stringify(`channels.${setupChannelId}.token`)},
|
||||
targetType: "channel",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: ${JSON.stringify(`channels.${setupChannelId}.token`)},
|
||||
secretShape: "secret_input",
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
@@ -272,4 +276,32 @@ describe("listReadOnlyChannelPluginsForConfig", () => {
|
||||
expect(fs.existsSync(setupMarker)).toBe(true);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores external setup plugins that export an unrequested channel id", () => {
|
||||
const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin({
|
||||
pluginId: "external-chat-plugin",
|
||||
channelId: "external-chat",
|
||||
manifestChannelIds: ["external-chat", "spoofed-chat"],
|
||||
setupChannelId: "spoofed-chat",
|
||||
});
|
||||
const plugins = listReadOnlyChannelPluginsForConfig(
|
||||
{
|
||||
channels: {
|
||||
"external-chat": { token: "configured" },
|
||||
},
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["external-chat-plugin"],
|
||||
},
|
||||
} as never,
|
||||
{
|
||||
env: { ...process.env },
|
||||
},
|
||||
);
|
||||
|
||||
expect(plugins.some((entry) => entry.id === "spoofed-chat")).toBe(false);
|
||||
expect(plugins.some((entry) => entry.id === "external-chat")).toBe(false);
|
||||
expect(fs.existsSync(setupMarker)).toBe(true);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,11 +42,22 @@ function resolveReadOnlyChannelPluginOptions(
|
||||
function addChannelPlugins(
|
||||
byId: Map<string, ChannelPlugin>,
|
||||
plugins: Iterable<ChannelPlugin | undefined>,
|
||||
options?: {
|
||||
onlyIds?: ReadonlySet<string>;
|
||||
allowOverwrite?: boolean;
|
||||
},
|
||||
): void {
|
||||
for (const plugin of plugins) {
|
||||
if (plugin) {
|
||||
byId.set(plugin.id, plugin);
|
||||
if (!plugin) {
|
||||
continue;
|
||||
}
|
||||
if (options?.onlyIds && !options.onlyIds.has(plugin.id)) {
|
||||
continue;
|
||||
}
|
||||
if (options?.allowOverwrite === false && byId.has(plugin.id)) {
|
||||
continue;
|
||||
}
|
||||
byId.set(plugin.id, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +146,7 @@ export function listReadOnlyChannelPluginsForConfig(
|
||||
workspaceDir,
|
||||
env,
|
||||
cache: options.cache,
|
||||
includePersistedAuthState: options.includePersistedAuthState ?? false,
|
||||
includePersistedAuthState: options.includePersistedAuthState,
|
||||
manifestRecords: externalManifestRecords,
|
||||
}),
|
||||
),
|
||||
@@ -179,6 +190,10 @@ export function listReadOnlyChannelPluginsForConfig(
|
||||
addChannelPlugins(
|
||||
byId,
|
||||
registry.channelSetups.map((setup) => setup.plugin),
|
||||
{
|
||||
onlyIds: new Set(missingConfiguredChannelIds),
|
||||
allowOverwrite: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user