Skip to content

Commit 006bd56

Browse files
committed
fix(plugins): trust reviewed official npm launch packages
1 parent 781c9b7 commit 006bd56

2 files changed

Lines changed: 64 additions & 35 deletions

File tree

src/plugins/install.npm-spec.test.ts

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -260,43 +260,67 @@ describe("installPluginFromNpmSpec", () => {
260260
});
261261
});
262262

263-
it("allows the official Codex npm plugin to spawn its managed app-server", async () => {
264-
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
265-
const warnings: string[] = [];
266-
mockNpmViewAndInstall({
263+
it.each([
264+
{
265+
spec: "@openclaw/acpx",
266+
pluginId: "acpx",
267+
indexJs: `import { spawn } from "node:child_process";\nspawn("codex-acp", []);`,
268+
},
269+
{
267270
spec: "@openclaw/codex",
268-
packageName: "@openclaw/codex",
269-
version: "2026.5.2",
270271
pluginId: "codex",
271-
npmRoot,
272272
indexJs: `import { spawn } from "node:child_process";\nspawn("codex", ["app-server"]);`,
273-
});
274-
275-
const result = await installPluginFromNpmSpec({
276-
spec: "@openclaw/codex",
277-
npmDir: npmRoot,
278-
logger: {
279-
info: () => {},
280-
warn: (msg: string) => warnings.push(msg),
281-
},
282-
});
283-
284-
expect(result.ok).toBe(true);
285-
if (!result.ok) {
286-
return;
287-
}
288-
expect(result.pluginId).toBe("codex");
289-
expect(
290-
warnings.some((warning) =>
291-
warning.includes("allowed because it is an official OpenClaw package"),
292-
),
293-
).toBe(true);
294-
expectNpmInstallIntoRoot({
295-
calls: runCommandWithTimeoutMock.mock.calls,
296-
npmRoot,
297-
spec: "@openclaw/codex",
298-
});
299-
});
273+
},
274+
{
275+
spec: "@openclaw/google-meet",
276+
pluginId: "google-meet",
277+
indexJs: `import { spawnSync } from "node:child_process";\nspawnSync("node", ["bridge.js"]);`,
278+
},
279+
{
280+
spec: "@openclaw/voice-call",
281+
pluginId: "voice-call",
282+
indexJs: `import { spawn } from "node:child_process";\nspawn("ngrok", ["http", "3000"]);`,
283+
},
284+
])(
285+
"allows official npm plugin $spec with reviewed launch code",
286+
async ({ spec, pluginId, indexJs }) => {
287+
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
288+
const warnings: string[] = [];
289+
mockNpmViewAndInstall({
290+
spec,
291+
packageName: spec,
292+
version: "2026.5.2",
293+
pluginId,
294+
npmRoot,
295+
indexJs,
296+
});
297+
298+
const result = await installPluginFromNpmSpec({
299+
spec,
300+
npmDir: npmRoot,
301+
logger: {
302+
info: () => {},
303+
warn: (msg: string) => warnings.push(msg),
304+
},
305+
});
306+
307+
expect(result.ok).toBe(true);
308+
if (!result.ok) {
309+
return;
310+
}
311+
expect(result.pluginId).toBe(pluginId);
312+
expect(
313+
warnings.some((warning) =>
314+
warning.includes("allowed because it is an official OpenClaw package"),
315+
),
316+
).toBe(true);
317+
expectNpmInstallIntoRoot({
318+
calls: runCommandWithTimeoutMock.mock.calls,
319+
npmRoot,
320+
spec,
321+
});
322+
},
323+
);
300324

301325
it("rejects non-registry npm specs", async () => {
302326
const result = await installPluginFromNpmSpec({ spec: "github:evil/evil" });

src/plugins/install.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@ type PluginInstallPolicyRequest = {
114114
};
115115

116116
const defaultLogger: PluginInstallLogger = {};
117-
const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([["@openclaw/codex", "codex"]]);
117+
const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([
118+
["@openclaw/acpx", "acpx"],
119+
["@openclaw/codex", "codex"],
120+
["@openclaw/google-meet", "google-meet"],
121+
["@openclaw/voice-call", "voice-call"],
122+
]);
118123

119124
function ensureOpenClawExtensions(params: { manifest: PackageManifest }):
120125
| {

0 commit comments

Comments
 (0)