
























@@ -1,5 +1,6 @@
11import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
22import type { OpenClawConfig } from "../../config/types.openclaw.js";
3+import { isBlockedObjectKey } from "../../infra/prototype-keys.js";
34import {
45hasExplicitChannelConfig,
56listConfiguredChannelIdsForReadOnlyScope,
@@ -10,11 +11,14 @@ import {
1011loadPluginManifestRegistry,
1112type PluginManifestRecord,
1213} from "../../plugins/manifest-registry.js";
13-import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
14+import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
15+import { sanitizeForLog } from "../../terminal/ansi.js";
1416import { getBundledChannelSetupPlugin } from "./bundled.js";
1517import { listChannelPlugins } from "./registry.js";
1618import type { ChannelPlugin } from "./types.plugin.js";
171920+const SAFE_MANIFEST_CHANNEL_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
21+1822type ReadOnlyChannelPluginOptions = {
1923env?: NodeJS.ProcessEnv;
2024workspaceDir?: string;
@@ -28,6 +32,7 @@ type ReadOnlyChannelPluginResolution = {
2832configuredChannelIds: string[];
2933missingConfiguredChannelIds: string[];
3034};
35+type ManifestChannelConfigRecord = NonNullable<PluginManifestRecord["channelConfigs"]>[string];
31363237function addChannelPlugins(
3338byId: Map<string, ChannelPlugin>,
@@ -66,6 +71,21 @@ function rebindChannelScopedString(
6671return value;
6772}
687374+function isSafeManifestChannelId(channelId: string): boolean {
75+return SAFE_MANIFEST_CHANNEL_ID_PATTERN.test(channelId) && !isBlockedObjectKey(channelId);
76+}
77+78+function readOwnRecordValue(record: Record<string, unknown>, key: string): unknown {
79+if (isBlockedObjectKey(key) || !Object.prototype.hasOwnProperty.call(record, key)) {
80+return undefined;
81+}
82+return record[key];
83+}
84+85+function normalizeManifestText(value: string | undefined, fallback: string): string {
86+return sanitizeForLog(value?.trim() || fallback).trim();
87+}
88+6989function rebindChannelConfig(
7090cfg: OpenClawConfig,
7191sourceChannelId: string,
@@ -113,11 +133,14 @@ function restoreReboundChannelConfig(params: {
113133}
114134115135function getChannelConfigRecord(cfg: OpenClawConfig, channelId: string): Record<string, unknown> {
136+if (!isSafeManifestChannelId(channelId)) {
137+return {};
138+}
116139const channels = cfg.channels;
117140if (!channels || typeof channels !== "object" || Array.isArray(channels)) {
118141return {};
119142}
120-const entry = (channels as Record<string, unknown>)[channelId];
143+const entry = readOwnRecordValue(channels as Record<string, unknown>, channelId);
121144return entry && typeof entry === "object" && !Array.isArray(entry)
122145 ? (entry as Record<string, unknown>)
123146 : {};
@@ -127,7 +150,14 @@ function listManifestChannelAccountIds(cfg: OpenClawConfig, channelId: string):
127150const channelConfig = getChannelConfigRecord(cfg, channelId);
128151const accounts = channelConfig.accounts;
129152if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
130-return Object.keys(accounts).toSorted((left, right) => left.localeCompare(right));
153+return [
154+ ...new Set(
155+Object.keys(accounts)
156+.filter((accountId) => !isBlockedObjectKey(accountId))
157+.map((accountId) => normalizeAccountId(accountId))
158+.filter((accountId) => !isBlockedObjectKey(accountId)),
159+),
160+].toSorted((left, right) => left.localeCompare(right));
131161}
132162return hasExplicitChannelConfig({ config: cfg, channelId }) ? [DEFAULT_ACCOUNT_ID] : [];
133163}
@@ -138,10 +168,13 @@ function resolveManifestChannelAccountConfig(params: {
138168accountId?: string | null;
139169}): Record<string, unknown> {
140170const channelConfig = getChannelConfigRecord(params.cfg, params.channelId);
141-const resolvedAccountId = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
171+const resolvedAccountId = normalizeAccountId(params.accountId);
142172const accounts = channelConfig.accounts;
143173if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
144-const accountConfig = (accounts as Record<string, unknown>)[resolvedAccountId];
174+const accountConfig = readOwnRecordValue(
175+accounts as Record<string, unknown>,
176+resolvedAccountId,
177+);
145178if (accountConfig && typeof accountConfig === "object" && !Array.isArray(accountConfig)) {
146179return accountConfig as Record<string, unknown>;
147180}
@@ -153,19 +186,31 @@ function buildManifestChannelPlugin(params: {
153186record: PluginManifestRecord;
154187channelId: string;
155188}): ChannelPlugin | undefined {
156-const channelConfig = params.record.channelConfigs?.[params.channelId];
157-if (!channelConfig) {
189+if (!isSafeManifestChannelId(params.channelId)) {
190+return undefined;
191+}
192+const channelConfigValue = params.record.channelConfigs
193+ ? readOwnRecordValue(params.record.channelConfigs as Record<string, unknown>, params.channelId)
194+ : undefined;
195+if (
196+!channelConfigValue ||
197+typeof channelConfigValue !== "object" ||
198+Array.isArray(channelConfigValue)
199+) {
158200return undefined;
159201}
160-const label = channelConfig.label?.trim() || params.record.name || params.channelId;
161-const blurb = channelConfig.description?.trim() || params.record.description || "";
202+const channelConfig = channelConfigValue as ManifestChannelConfigRecord;
203+const label =
204+normalizeManifestText(channelConfig.label, params.record.name || params.channelId) ||
205+params.channelId;
206+const blurb = normalizeManifestText(channelConfig.description, params.record.description || "");
162207return {
163208id: params.channelId,
164209meta: {
165210id: params.channelId,
166211 label,
167212selectionLabel: label,
168-docsPath: `/channels/${params.channelId}`,
213+docsPath: `/channels/${encodeURIComponent(params.channelId)}`,
169214 blurb,
170215 ...(channelConfig.preferOver?.length ? { preferOver: channelConfig.preferOver } : {}),
171216},
@@ -179,7 +224,7 @@ function buildManifestChannelPlugin(params: {
179224listAccountIds: (cfg) => listManifestChannelAccountIds(cfg, params.channelId),
180225defaultAccountId: () => DEFAULT_ACCOUNT_ID,
181226resolveAccount: (cfg, accountId) => ({
182-accountId: accountId?.trim() || DEFAULT_ACCOUNT_ID,
227+accountId: normalizeAccountId(accountId),
183228config: resolveManifestChannelAccountConfig({
184229 cfg,
185230channelId: params.channelId,
@@ -340,7 +385,9 @@ function addSetupChannelPlugins(
340385},
341386): void {
342387for (const setup of setups) {
343-const ownedMissingChannelIds = options.ownedMissingChannelIdsByPluginId.get(setup.pluginId);
388+const ownedMissingChannelIds = options.ownedMissingChannelIdsByPluginId
389+.get(setup.pluginId)
390+?.filter(isSafeManifestChannelId);
344391if (!ownedMissingChannelIds || ownedMissingChannelIds.length === 0) {
345392continue;
346393}
@@ -361,7 +408,9 @@ function addSetupChannelPlugins(
361408);
362409continue;
363410}
364-const ownedChannelIds = options.ownedChannelIdsByPluginId.get(setup.pluginId) ?? [];
411+const ownedChannelIds = (options.ownedChannelIdsByPluginId.get(setup.pluginId) ?? []).filter(
412+isSafeManifestChannelId,
413+);
365414if (setup.plugin.id !== setup.pluginId && !ownedChannelIds.includes(setup.plugin.id)) {
366415continue;
367416}
@@ -395,6 +444,9 @@ function addManifestChannelPlugins(
395444continue;
396445}
397446for (const channelId of record.channels) {
447+if (!isSafeManifestChannelId(channelId)) {
448+continue;
449+}
398450if (!channelIds.has(channelId)) {
399451continue;
400452}
@@ -487,7 +539,7 @@ export function resolveReadOnlyChannelPluginsForConfig(
487539 manifestRecords,
488540}),
489541),
490-];
542+].filter(isSafeManifestChannelId);
491543const byId = new Map<string, ChannelPlugin>();
492544493545addChannelPlugins(byId, listChannelPlugins());
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。