From 006bd56dd68616ef13da8c411b6cbf9c8fe57e11 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 18:35:25 -0700 Subject: [PATCH] fix(plugins): trust reviewed official npm launch packages --- src/plugins/install.npm-spec.test.ts | 88 ++++++++++++++++++---------- src/plugins/install.ts | 7 ++- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/src/plugins/install.npm-spec.test.ts b/src/plugins/install.npm-spec.test.ts index cb66176fd3d..04e2afc2d27 100644 --- a/src/plugins/install.npm-spec.test.ts +++ b/src/plugins/install.npm-spec.test.ts @@ -260,43 +260,67 @@ describe("installPluginFromNpmSpec", () => { }); }); - it("allows the official Codex npm plugin to spawn its managed app-server", async () => { - const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm"); - const warnings: string[] = []; - mockNpmViewAndInstall({ + it.each([ + { + spec: "@openclaw/acpx", + pluginId: "acpx", + indexJs: `import { spawn } from "node:child_process";\nspawn("codex-acp", []);`, + }, + { spec: "@openclaw/codex", - packageName: "@openclaw/codex", - version: "2026.5.2", pluginId: "codex", - npmRoot, indexJs: `import { spawn } from "node:child_process";\nspawn("codex", ["app-server"]);`, - }); + }, + { + spec: "@openclaw/google-meet", + pluginId: "google-meet", + indexJs: `import { spawnSync } from "node:child_process";\nspawnSync("node", ["bridge.js"]);`, + }, + { + spec: "@openclaw/voice-call", + pluginId: "voice-call", + indexJs: `import { spawn } from "node:child_process";\nspawn("ngrok", ["http", "3000"]);`, + }, + ])( + "allows official npm plugin $spec with reviewed launch code", + async ({ spec, pluginId, indexJs }) => { + const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm"); + const warnings: string[] = []; + mockNpmViewAndInstall({ + spec, + packageName: spec, + version: "2026.5.2", + pluginId, + npmRoot, + indexJs, + }); - const result = await installPluginFromNpmSpec({ - spec: "@openclaw/codex", - npmDir: npmRoot, - logger: { - info: () => {}, - warn: (msg: string) => warnings.push(msg), - }, - }); + const result = await installPluginFromNpmSpec({ + spec, + npmDir: npmRoot, + logger: { + info: () => {}, + warn: (msg: string) => warnings.push(msg), + }, + }); - expect(result.ok).toBe(true); - if (!result.ok) { - return; - } - expect(result.pluginId).toBe("codex"); - expect( - warnings.some((warning) => - warning.includes("allowed because it is an official OpenClaw package"), - ), - ).toBe(true); - expectNpmInstallIntoRoot({ - calls: runCommandWithTimeoutMock.mock.calls, - npmRoot, - spec: "@openclaw/codex", - }); - }); + expect(result.ok).toBe(true); + if (!result.ok) { + return; + } + expect(result.pluginId).toBe(pluginId); + expect( + warnings.some((warning) => + warning.includes("allowed because it is an official OpenClaw package"), + ), + ).toBe(true); + expectNpmInstallIntoRoot({ + calls: runCommandWithTimeoutMock.mock.calls, + npmRoot, + spec, + }); + }, + ); it("rejects non-registry npm specs", async () => { const result = await installPluginFromNpmSpec({ spec: "github:evil/evil" }); diff --git a/src/plugins/install.ts b/src/plugins/install.ts index f32441ca7d0..b1895dab9dd 100644 --- a/src/plugins/install.ts +++ b/src/plugins/install.ts @@ -114,7 +114,12 @@ type PluginInstallPolicyRequest = { }; const defaultLogger: PluginInstallLogger = {}; -const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([["@openclaw/codex", "codex"]]); +const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([ + ["@openclaw/acpx", "acpx"], + ["@openclaw/codex", "codex"], + ["@openclaw/google-meet", "google-meet"], + ["@openclaw/voice-call", "voice-call"], +]); function ensureOpenClawExtensions(params: { manifest: PackageManifest }): | {