From cd08facd7ab8185b210cbb347021f51baef2feee Mon Sep 17 00:00:00 2001 From: Doruk Ardahan <35905596+dorukardahan@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:54:41 +0300 Subject: [PATCH] fix(plugins): keep auto-enabled channels behind allowlists --- src/plugins/config-state.test.ts | 30 ++++++++++++++++++++++++++++++ src/plugins/config-state.ts | 5 +++-- src/plugins/loader.test.ts | 30 ++++++++++++++++++++++++++++++ src/plugins/loader.ts | 13 +++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/plugins/config-state.test.ts b/src/plugins/config-state.test.ts index 0f9d46a91d0..60218aa639e 100644 --- a/src/plugins/config-state.test.ts +++ b/src/plugins/config-state.test.ts @@ -351,6 +351,36 @@ describe("resolveEffectivePluginActivationState", () => { }); }); + it("keeps denylist authoritative over explicit bundled channel activation", () => { + const rawConfig = { + channels: { + telegram: { + enabled: true, + }, + }, + plugins: { + deny: ["telegram"], + }, + }; + + expect( + resolveEffectivePluginActivationState({ + id: "telegram", + origin: "bundled", + config: normalizePluginsConfig(rawConfig.plugins), + rootConfig: rawConfig, + sourceConfig: normalizePluginsConfig(rawConfig.plugins), + sourceRootConfig: rawConfig, + }), + ).toEqual({ + enabled: false, + activated: false, + explicitlyEnabled: true, + source: "disabled", + reason: "blocked by denylist", + }); + }); + it("does not let auto-enable reasons bypass the allowlist", () => { const rawConfig = { plugins: { diff --git a/src/plugins/config-state.ts b/src/plugins/config-state.ts index 590e5fa83d2..66e31195534 100644 --- a/src/plugins/config-state.ts +++ b/src/plugins/config-state.ts @@ -287,8 +287,7 @@ export function resolvePluginActivationState(params: { }); const explicitlyConfiguredBundledChannel = params.origin === "bundled" && - explicitSelection.reason === "channel enabled in config" && - explicitSelection.explicitlyEnabled; + isBundledChannelEnabledByChannelConfig(params.sourceRootConfig ?? params.rootConfig, params.id); if (!params.config.enabled) { return { @@ -451,6 +450,8 @@ export function resolveEffectiveEnableState(params: { config: NormalizedPluginsConfig; rootConfig?: OpenClawConfig; enabledByDefault?: boolean; + sourceConfig?: NormalizedPluginsConfig; + sourceRootConfig?: OpenClawConfig; }): { enabled: boolean; reason?: string } { const state = resolveEffectivePluginActivationState(params); return state.enabled ? { enabled: true } : { enabled: false, reason: state.reason }; diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index 8f95b29998b..67d59571d76 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -1098,6 +1098,36 @@ describe("loadOpenClawPlugins", () => { }); }); + it("keeps auto-enabled bundled channels behind restrictive allowlists", () => { + setupBundledTelegramPlugin(); + const rawConfig = { + channels: { + telegram: { + botToken: "x", + }, + }, + plugins: { + allow: ["browser"], + }, + } satisfies PluginLoadConfig; + const autoEnabled = applyPluginAutoEnable({ + config: rawConfig, + env: {}, + }); + + const registry = loadOpenClawPlugins({ + cache: false, + workspaceDir: cachedBundledTelegramDir, + config: autoEnabled.config, + activationSourceConfig: rawConfig, + autoEnabledReasons: autoEnabled.autoEnabledReasons, + }); + + const telegram = registry.plugins.find((entry) => entry.id === "telegram"); + expect(telegram?.status).toBe("disabled"); + expect(telegram?.error).toBe("not in allowlist"); + }); + it("preserves all auto-enable reasons in activation metadata", () => { setupBundledTelegramPlugin(); const rawConfig = { diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index ebe32819e71..1a421ffcbbe 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -3,6 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { createJiti } from "jiti"; import type { ChannelPlugin } from "../channels/plugins/types.js"; +import { normalizeChatChannelId } from "../channels/registry.js"; import { isChannelConfigured } from "../config/channel-configured.js"; import type { OpenClawConfig } from "../config/config.js"; import type { PluginInstallRecord } from "../config/types.plugins.js"; @@ -1166,6 +1167,12 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi config: normalized, rootConfig: cfg, enabledByDefault: manifestRecord.enabledByDefault, + ...(normalizeChatChannelId(pluginId) + ? { + sourceConfig: activationSourceNormalized, + sourceRootConfig: activationSourceConfig, + } + : {}), }); const entry = normalized.entries[pluginId]; const record = createPluginRecord({ @@ -1717,6 +1724,12 @@ export async function loadOpenClawPluginCliRegistry( config: normalized, rootConfig: cfg, enabledByDefault: manifestRecord.enabledByDefault, + ...(normalizeChatChannelId(pluginId) + ? { + sourceConfig: activationSourceNormalized, + sourceRootConfig: activationSourceConfig, + } + : {}), }); const entry = normalized.entries[pluginId]; const record = createPluginRecord({