Skip to content

fix: gate MCP server actions behind isMcpEnabled - #6889

Open
aroh3006 wants to merge 1 commit into
ChatGPTNextWeb:mainfrom
aroh3006:fix/mcp-actions-unauthenticated-rce
Open

fix: gate MCP server actions behind isMcpEnabled#6889
aroh3006 wants to merge 1 commit into
ChatGPTNextWeb:mainfrom
aroh3006:fix/mcp-actions-unauthenticated-rce

Conversation

@aroh3006

@aroh3006 aroh3006 commented Sep 3, 2026

Copy link
Copy Markdown

Fixes #6757.

app/mcp/actions.ts has "use server" at the top, which makes every
exported function in the file callable directly over HTTP as a Next.js
Server Action, unauthenticated, using the action id embedded in the
client bundle. isMcpEnabled() already existed as a guard for this,
but nothing called it, so every action ran regardless of the
ENABLE_MCP setting.

addMcpServer writes a caller-supplied {command, args} to
mcp_config.json and spawns a child process with them via
StdioClientTransport. With no auth check and no isMcpEnabled()
check, that's remote code execution reachable on any deployment,
whether or not MCP was ever turned on — exactly as laid out in #6757.

Every exported action in the file now calls isMcpEnabled() first and
rejects if it's off. This matches what the frontend already does —
chat.tsx, home.tsx, mcp-market.tsx, sidebar.tsx and
store/chat.ts all check isMcpEnabled() before ever calling these,
so legitimate MCP usage (ENABLE_MCP=true) is unaffected.

Test. Added test/mcp-actions-require-enabled.test.ts, which calls
every exported action with ENABLE_MCP unset and asserts each one
rejects with "MCP is not enabled" instead of running, plus a couple of
sanity checks for the enabled case. Ran it against the current code
first: addMcpServer actually wrote the malicious config to disk and
spawned the process — a real file was created on my machine by the
touch payload in the test, running against unpatched main. It
fails there and passes with this change.

This only closes the primary path in #6757 (no auth check at all). The
report's other recommendations — command allowlisting, not inheriting
the full process.env into the spawned process, and moving MCP
management to an explicit authenticated route — are broader hardening
for deployments that do have ENABLE_MCP=true, and probably deserve
their own separate PR(s) and maintainer input on the intended design.

Every function in app/mcp/actions.ts is exported from a "use server"
module, which makes each one callable directly over HTTP as a Next.js
Server Action, unauthenticated, using the action id embedded in the
client bundle. isMcpEnabled() already existed for this but nothing
called it, so every action ran regardless of the ENABLE_MCP setting.

addMcpServer takes a command and args and spawns a child process with
them (via StdioClientTransport), so this was remote code execution
reachable on any deployment, whether or not MCP was ever turned on.

Every exported action now calls isMcpEnabled() first and rejects if
it's off, matching how the frontend already gates on it before ever
calling these.

Added a test that calls each action with ENABLE_MCP unset and checks
it rejects instead of running. Confirmed it fails against the current
code: addMcpServer actually wrote the malicious config to disk and
spawned the process (a real /tmp file got created running the test
against the unfixed code), and passes with the fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unauthenticated Remote Code Execution

1 participant