fix(plugins): guard setup backend metadata

This commit is contained in:
Vincent Koc
2026-06-03 18:29:10 +02:00
parent 344e04b5d5
commit f37cce6538
2 changed files with 103 additions and 20 deletions
@@ -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");
+28 -20
View File
@@ -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;