From 8f3b99c51281e8cce9a0476984ea7512ab277a8f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 23 Apr 2026 10:22:04 -0700 Subject: [PATCH] fix(mcp): block owner-only tools in ACPX bridge --- CHANGELOG.md | 1 + src/mcp/openclaw-tools-serve.test.ts | 20 +++++++++++--------- src/mcp/plugin-tools-handlers.ts | 3 ++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0dd3114131..caf36fe92d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- MCP/tools: stop the ACPX OpenClaw tools bridge from listing or invoking owner-only tools such as `cron`, closing a privilege-escalation path for non-owner MCP callers. (#70698) Thanks @vincentkoc. - WhatsApp/security: keep contact/vCard/location structured-object free text out of the inline message body and render it through fenced untrusted metadata JSON, limiting hidden prompt-injection payloads in names, phone fields, and location labels/comments. - Group-chat/security: keep channel-sourced group names and participant labels out of inline group system prompts and render them through fenced untrusted metadata JSON. - Plugins/startup: restore bundled plugin `openclaw/plugin-sdk/*` resolution from packaged installs and external runtime-deps stage roots, so Telegram/Discord no longer crash-loop with `Cannot find package 'openclaw'` after missing dependency repair. diff --git a/src/mcp/openclaw-tools-serve.test.ts b/src/mcp/openclaw-tools-serve.test.ts index 15615c0ce47..62c512e0fc2 100644 --- a/src/mcp/openclaw-tools-serve.test.ts +++ b/src/mcp/openclaw-tools-serve.test.ts @@ -3,18 +3,20 @@ import { resolveOpenClawToolsForMcp } from "./openclaw-tools-serve.js"; import { createPluginToolsMcpHandlers } from "./plugin-tools-handlers.js"; describe("OpenClaw tools MCP server", () => { - it("exposes cron", async () => { + it("does not expose owner-only cron", async () => { const handlers = createPluginToolsMcpHandlers(resolveOpenClawToolsForMcp()); const listed = await handlers.listTools(); - expect(listed).toEqual({ - tools: [ - expect.objectContaining({ - name: "cron", - description: expect.stringContaining("Manage Gateway cron jobs"), - inputSchema: expect.objectContaining({ type: "object" }), - }), - ], + expect(listed.tools.map((tool) => tool.name)).not.toContain("cron"); + }); + + it("blocks owner-only cron invocation", async () => { + const handlers = createPluginToolsMcpHandlers(resolveOpenClawToolsForMcp()); + + const result = await handlers.callTool({ name: "cron", arguments: { action: "status" } }); + expect(result).toEqual({ + content: [{ type: "text", text: "Unknown tool: cron" }], + isError: true, }); }); }); diff --git a/src/mcp/plugin-tools-handlers.ts b/src/mcp/plugin-tools-handlers.ts index 2d7c67e73a5..4378d1944c3 100644 --- a/src/mcp/plugin-tools-handlers.ts +++ b/src/mcp/plugin-tools-handlers.ts @@ -19,7 +19,8 @@ function resolveJsonSchemaForTool(tool: AnyAgentTool): Record { } export function createPluginToolsMcpHandlers(tools: AnyAgentTool[]) { - const wrappedTools = tools.map((tool) => { + const allowedTools = tools.filter((tool) => !tool.ownerOnly); + const wrappedTools = allowedTools.map((tool) => { if (isToolWrappedWithBeforeToolCallHook(tool)) { return tool; }