From 86dd59f91479abe437ae7dd4c44f761205e2d4bc Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 21 Apr 2026 00:32:44 -0400 Subject: [PATCH] fix: accept lowercase channel env triggers --- src/plugins/channel-plugin-ids.test.ts | 62 +++++++++++++++++++++++++- src/plugins/channel-plugin-ids.ts | 3 +- src/secrets/channel-env-var-names.ts | 2 +- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/plugins/channel-plugin-ids.test.ts b/src/plugins/channel-plugin-ids.test.ts index 5ed68b23edb..8eed1735507 100644 --- a/src/plugins/channel-plugin-ids.test.ts +++ b/src/plugins/channel-plugin-ids.test.ts @@ -119,7 +119,7 @@ function createManifestRegistryFixture() { id: "ambient-env-channel-plugin", channels: ["ambient-env-channel"], channelEnvVars: { - "ambient-env-channel": ["HOME", "PATH", "lowercase_token"], + "ambient-env-channel": ["HOME", "PATH"], }, origin: "config", enabledByDefault: undefined, @@ -715,6 +715,66 @@ describe("listConfiguredChannelIdsForReadOnlyScope", () => { ).toEqual([]); }); + it("accepts lowercase or mixed-case manifest env vars as read-only configured channel triggers", () => { + expect( + listConfiguredChannelIdsForReadOnlyScope({ + config: { + plugins: { + allow: ["external-env-channel-plugin"], + }, + } as OpenClawConfig, + workspaceDir: "/tmp", + env: { + external_env_channel_token: "token", + } as NodeJS.ProcessEnv, + includePersistedAuthState: false, + manifestRecords: [ + { + id: "external-env-channel-plugin", + channels: ["external-env-channel"], + channelEnvVars: { + "external-env-channel": ["external_env_channel_token"], + }, + origin: "config", + enabledByDefault: undefined, + providers: [], + cliBackends: [], + } as never, + ], + }), + ).toEqual(["external-env-channel"]); + }); + + it("matches uppercase process env entries for lowercase manifest env var declarations", () => { + expect( + listConfiguredChannelIdsForReadOnlyScope({ + config: { + plugins: { + allow: ["external-env-channel-plugin"], + }, + } as OpenClawConfig, + workspaceDir: "/tmp", + env: { + EXTERNAL_ENV_CHANNEL_TOKEN: "token", + } as NodeJS.ProcessEnv, + includePersistedAuthState: false, + manifestRecords: [ + { + id: "external-env-channel-plugin", + channels: ["external-env-channel"], + channelEnvVars: { + "external-env-channel": ["external_env_channel_token"], + }, + origin: "config", + enabledByDefault: undefined, + providers: [], + cliBackends: [], + } as never, + ], + }), + ).toEqual(["external-env-channel"]); + }); + it("uses manifest env vars for read-only channel presence checks", () => { listPotentialConfiguredChannelIds.mockReturnValue([]); hasPotentialConfiguredChannels.mockReturnValue(false); diff --git a/src/plugins/channel-plugin-ids.ts b/src/plugins/channel-plugin-ids.ts index 4d470917475..2758945a919 100644 --- a/src/plugins/channel-plugin-ids.ts +++ b/src/plugins/channel-plugin-ids.ts @@ -70,7 +70,8 @@ function hasNonEmptyEnvValue(env: NodeJS.ProcessEnv, key: string): boolean { if (!isSafeChannelEnvVarTriggerName(key)) { return false; } - const value = env[key]; + const trimmed = key.trim(); + const value = env[trimmed] ?? env[trimmed.toUpperCase()]; return typeof value === "string" && value.trim().length > 0; } diff --git a/src/secrets/channel-env-var-names.ts b/src/secrets/channel-env-var-names.ts index ac82cdac47e..e4e717d0d8c 100644 --- a/src/secrets/channel-env-var-names.ts +++ b/src/secrets/channel-env-var-names.ts @@ -19,7 +19,7 @@ const UNSAFE_CHANNEL_ENV_VAR_TRIGGER_NAMES = new Set([ ]); export function isSafeChannelEnvVarTriggerName(key: string): boolean { - const normalized = key.trim(); + const normalized = key.trim().toUpperCase(); return ( /^[A-Z][A-Z0-9_]*$/.test(normalized) && !UNSAFE_CHANNEL_ENV_VAR_TRIGGER_NAMES.has(normalized) );