Skip to content

Commit df4a8a8

Browse files
committed
Allow trusted plugin keyed state
1 parent d124c5a commit df4a8a8

2 files changed

Lines changed: 37 additions & 5 deletions

File tree

src/plugin-state/plugin-state-store.runtime.test.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ import type { PluginRuntime } from "../plugins/runtime/types.js";
66
import { withOpenClawTestState } from "../test-utils/openclaw-test-state.js";
77
import { resetPluginStateStoreForTests } from "./plugin-state-store.js";
88

9-
function createPluginRecord(id: string, origin: PluginRecord["origin"] = "bundled"): PluginRecord {
9+
function createPluginRecord(
10+
id: string,
11+
origin: PluginRecord["origin"] = "bundled",
12+
opts: { trustedOfficialInstall?: boolean } = {},
13+
): PluginRecord {
1014
return {
1115
id,
1216
name: id,
1317
source: `/plugins/${id}/index.ts`,
1418
origin,
19+
trustedOfficialInstall: opts.trustedOfficialInstall,
1520
enabled: true,
1621
status: "loaded",
1722
toolNames: [],
@@ -87,6 +92,22 @@ describe("plugin runtime state proxy", () => {
8792
});
8893
});
8994

95+
it("allows trusted official global plugins to use keyed state", async () => {
96+
await withOpenClawTestState({ label: "plugin-state-trusted-global" }, async () => {
97+
const registry = createTestPluginRegistry();
98+
const record = createPluginRecord("slack", "global", { trustedOfficialInstall: true });
99+
registry.registry.plugins.push(record);
100+
const api = registry.createApi(record, { config: {} });
101+
102+
const store = api.runtime.state.openKeyedStore<{ plugin: string }>({
103+
namespace: "runtime",
104+
maxEntries: 10,
105+
});
106+
await expect(store.register("thread", { plugin: "slack" })).resolves.toBeUndefined();
107+
await expect(store.lookup("thread")).resolves.toEqual({ plugin: "slack" });
108+
});
109+
});
110+
90111
it("rejects external plugins in this release", () => {
91112
const registry = createTestPluginRegistry();
92113
const record = createPluginRecord("external-plugin", "workspace");
@@ -95,6 +116,17 @@ describe("plugin runtime state proxy", () => {
95116

96117
expect(() =>
97118
api.runtime.state.openKeyedStore({ namespace: "runtime", maxEntries: 10 }),
98-
).toThrow("openKeyedStore is only available for bundled plugins");
119+
).toThrow("openKeyedStore is only available for trusted plugins");
120+
});
121+
122+
it("rejects untrusted global plugins", () => {
123+
const registry = createTestPluginRegistry();
124+
const record = createPluginRecord("external-plugin", "global");
125+
registry.registry.plugins.push(record);
126+
const api = registry.createApi(record, { config: {} });
127+
128+
expect(() =>
129+
api.runtime.state.openKeyedStore({ namespace: "runtime", maxEntries: 10 }),
130+
).toThrow("openKeyedStore is only available for trusted plugins");
99131
});
100132
});

src/plugins/registry.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import {
5151
import { buildPluginApi } from "./api-builder.js";
5252
import { normalizeRegisteredChannelPlugin } from "./channel-validation.js";
5353
import { CODEX_APP_SERVER_EXTENSION_RUNTIME_ID } from "./codex-app-server-extension-factory.js";
54-
import { getPluginCompatRecord } from "./compat/registry.js";
5554
import type { CodexAppServerExtensionFactory } from "./codex-app-server-extension-types.js";
5655
import {
5756
isReservedCommandName,
@@ -63,6 +62,7 @@ import {
6362
getRegisteredCompactionProvider,
6463
registerCompactionProvider,
6564
} from "./compaction-provider.js";
65+
import { getPluginCompatRecord } from "./compat/registry.js";
6666
import { sendPluginSessionAttachment } from "./host-hook-attachments.js";
6767
import {
6868
clearPluginRunContext,
@@ -2421,9 +2421,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
24212421
const record =
24222422
pluginRuntimeRecordById.get(pluginId) ??
24232423
registry.plugins.find((entry) => entry.id === pluginId);
2424-
if (record?.origin !== "bundled") {
2424+
if (record?.origin !== "bundled" && record?.trustedOfficialInstall !== true) {
24252425
throw new Error(
2426-
"openKeyedStore is only available for bundled plugins in this release.",
2426+
"openKeyedStore is only available for trusted plugins in this release.",
24272427
);
24282428
}
24292429
return createPluginStateKeyedStore<T>(pluginId, options);

0 commit comments

Comments
 (0)