feat(cowork): re-enable Claude Cowork with preset-only stdio MCP

Restore Cowork feature while closing the RCE vector: drop user-defined
stdio commands so only hard-coded preset plugins (browsermcp) may spawn.
Custom MCP now accepts remote URL only. Routes stay gated to localhost.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
decolua
2026-06-08 15:35:37 +07:00
co-authored by Cursor
parent 7648c3412b
commit f8b73faf5d
7 changed files with 33 additions and 146 deletions
+4 -33
View File
@@ -2,13 +2,8 @@
// broadcasts JSON-RPC frames over SSE, accepts client messages via HTTP POST.
const { spawn } = require("child_process");
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const { LOCAL_STDIO_PLUGINS, ALLOWED_MCP_COMMANDS } = require("@/shared/constants/coworkPlugins");
const { DATA_DIR } = require("@/lib/dataDir");
const CUSTOM_FILE = path.join(DATA_DIR, "mcp", "customPlugins.json");
const { LOCAL_STDIO_PLUGINS } = require("@/shared/constants/coworkPlugins");
const G_KEY = "__9routerMcpBridges";
const MAX_TEXT_CHARS = 50000;
@@ -106,33 +101,9 @@ const getStore = () => {
return globalThis[G_KEY];
};
const getCustomStore = () => {
if (!globalThis.__9routerCustomPlugins) globalThis.__9routerCustomPlugins = new Map();
return globalThis.__9routerCustomPlugins;
};
function isAllowedCommand(cmd) {
const bin = path.basename(String(cmd || ""));
return ALLOWED_MCP_COMMANDS.has(bin);
}
function registerCustomPlugin(def) {
if (!isAllowedCommand(def?.command)) {
throw new Error(`Blocked: command '${def?.command}' not in MCP allowlist`);
}
getCustomStore().set(def.name, def);
}
// Only preset stdio plugins may spawn. No user-defined commands (RCE prevention).
function findPlugin(name) {
const fromMem = getCustomStore().get(name) || LOCAL_STDIO_PLUGINS.find((p) => p.name === name);
if (fromMem) return fromMem;
// Lazy-load custom plugins from disk (survives app restart); re-validate allowlist.
try {
const list = JSON.parse(fs.readFileSync(CUSTOM_FILE, "utf-8"));
const def = Array.isArray(list) ? list.find((p) => p.name === name && p.command) : null;
if (def && isAllowedCommand(def.command)) { getCustomStore().set(def.name, def); return def; }
} catch { /* file missing or invalid */ }
return null;
return LOCAL_STDIO_PLUGINS.find((p) => p.name === name) || null;
}
function getOrSpawn(name) {
@@ -195,4 +166,4 @@ function isRunning(name) {
return !!(entry?.proc && !entry.proc.killed && entry.proc.exitCode === null);
}
module.exports = { getOrSpawn, registerSession, unregisterSession, sendToChild, isRunning, findPlugin, registerCustomPlugin };
module.exports = { getOrSpawn, registerSession, unregisterSession, sendToChild, isRunning, findPlugin };