From f37cce6538a6795080fcf8f21e8d04993a02f13e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 3 Jun 2026 18:29:10 +0200 Subject: [PATCH] fix(plugins): guard setup backend metadata --- src/plugins/setup-registry.runtime.test.ts | 75 ++++++++++++++++++++++ src/plugins/setup-registry.runtime.ts | 48 ++++++++------ 2 files changed, 103 insertions(+), 20 deletions(-) diff --git a/src/plugins/setup-registry.runtime.test.ts b/src/plugins/setup-registry.runtime.test.ts index 945cfdd2352bb3..63596319755f46 100644 --- a/src/plugins/setup-registry.runtime.test.ts +++ b/src/plugins/setup-registry.runtime.test.ts @@ -155,6 +155,81 @@ describe("setup-registry runtime fallback", () => { }); }); + it("skips unreadable bundled setup backend metadata in runtime fallback lookups", async () => { + const poisonedPlugin = Object.defineProperty({}, "origin", { + get() { + throw new Error("setup backend origin exploded"); + }, + }); + loadPluginMetadataSnapshotMock.mockReturnValue({ + index: { + diagnostics: [], + plugins: [ + { + pluginId: "openai", + origin: "bundled", + enabled: true, + }, + ], + }, + plugins: [ + poisonedPlugin, + { + id: "openai", + origin: "bundled", + cliBackends: ["Codex-CLI"], + }, + ], + }); + + const { testing, resolvePluginSetupCliBackendRuntime } = + await import("./setup-registry.runtime.js"); + testing.resetRuntimeState(); + testing.setRuntimeModuleForTest(null); + + expect(resolvePluginSetupCliBackendRuntime({ backend: "codex-cli" })).toEqual({ + pluginId: "openai", + backend: { id: "Codex-CLI" }, + }); + }); + + it("skips unreadable setup backend metadata in descriptor lookups", async () => { + const poisonedPlugin = Object.defineProperty({}, "id", { + get() { + throw new Error("setup backend id exploded"); + }, + }); + loadPluginMetadataSnapshotMock.mockReturnValue({ + index: { + diagnostics: [], + plugins: [ + { + pluginId: "openai", + origin: "bundled", + enabled: true, + }, + ], + }, + plugins: [ + poisonedPlugin, + { + id: "openai", + origin: "bundled", + cliBackends: ["Codex-CLI"], + }, + ], + }); + + const { testing, resolvePluginSetupCliBackendDescriptor } = + await import("./setup-registry.runtime.js"); + testing.resetRuntimeState(); + + expect(resolvePluginSetupCliBackendDescriptor({ backend: "codex-cli" })).toEqual({ + pluginId: "openai", + backend: { id: "Codex-CLI" }, + }); + }); + it("refreshes bundled registry cliBackends when the current metadata snapshot changes", async () => { const { testing, resolvePluginSetupCliBackendRuntime } = await import("./setup-registry.runtime.js"); diff --git a/src/plugins/setup-registry.runtime.ts b/src/plugins/setup-registry.runtime.ts index 3a63a746fad2e3..3206b082592f15 100644 --- a/src/plugins/setup-registry.runtime.ts +++ b/src/plugins/setup-registry.runtime.ts @@ -2,6 +2,7 @@ import { createRequire } from "node:module"; import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isInstalledPluginEnabled } from "./installed-plugin-index.js"; +import type { PluginManifestRecord } from "./manifest-registry.js"; import { resolvePluginMetadataSnapshot, type PluginMetadataSnapshot, @@ -87,16 +88,7 @@ function resolveBundledSetupCliBackends( return cachedBundledSetupCliBackends.entries; } const entries = snapshot.plugins.flatMap((plugin) => { - if (plugin.origin !== "bundled" || !isInstalledPluginEnabled(snapshot.index, plugin.id)) { - return []; - } - return [...plugin.cliBackends, ...(plugin.setup?.cliBackends ?? [])].map( - (backendId) => - ({ - pluginId: plugin.id, - backend: { id: backendId }, - }) satisfies SetupCliBackendRuntimeEntry, - ); + return readSetupCliBackendRuntimeEntries({ plugin, snapshot, bundledOnly: true }); }); if (cacheable && configFingerprint) { cachedBundledSetupCliBackends = { configFingerprint, entries }; @@ -117,16 +109,7 @@ function resolveSetupCliBackendDescriptors( return cachedSetupCliBackendDescriptors.entries; } const entries = snapshot.plugins.flatMap((plugin) => { - if (!isInstalledPluginEnabled(snapshot.index, plugin.id)) { - return []; - } - return [...plugin.cliBackends, ...(plugin.setup?.cliBackends ?? [])].map( - (backendId) => - ({ - pluginId: plugin.id, - backend: { id: backendId }, - }) satisfies SetupCliBackendRuntimeEntry, - ); + return readSetupCliBackendRuntimeEntries({ plugin, snapshot }); }); if (cacheable && configFingerprint) { cachedSetupCliBackendDescriptors = { configFingerprint, entries }; @@ -134,6 +117,31 @@ function resolveSetupCliBackendDescriptors( return entries; } +function readSetupCliBackendRuntimeEntries(params: { + plugin: PluginManifestRecord; + snapshot: PluginMetadataSnapshot; + bundledOnly?: boolean; +}): SetupCliBackendRuntimeEntry[] { + try { + if (params.bundledOnly && params.plugin.origin !== "bundled") { + return []; + } + const pluginId = params.plugin.id; + if (!isInstalledPluginEnabled(params.snapshot.index, pluginId)) { + return []; + } + return [...params.plugin.cliBackends, ...(params.plugin.setup?.cliBackends ?? [])].map( + (backendId) => + ({ + pluginId, + backend: { id: backendId }, + }) satisfies SetupCliBackendRuntimeEntry, + ); + } catch { + return []; + } +} + function loadSetupRegistryRuntime(): SetupRegistryRuntimeModule | null { if (setupRegistryRuntimeModule !== undefined) { return setupRegistryRuntimeModule;