Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(coderd): enforce Force On MCP server policy on the backend - #27990

Merged
ThomasK33 merged 1 commit into
mainfrom
cdm-policy-5ncb
Aug 11, 2026
Merged

fix(coderd): enforce Force On MCP server policy on the backend#27990
ThomasK33 merged 1 commit into
mainfrom
cdm-policy-5ncb

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

Enforce the Force On MCP server availability policy on the backend for chat creation, message sends, and turn generation, remediating Cure53 finding CDM-02-010 (Linear: CODAGT-787, disclosure: coder/security-disclosures#170).

Problem

MCP server configs with availability = "force_on" are meant to be injected into every conversation. Enforcement lived only in the frontend, which appended the forced IDs to the mcp_server_ids request parameter. A user could intercept POST /api/experimental/chats or POST /api/experimental/chats/{chat}/messages and strip the IDs (for example "mcp_server_ids": []), and the Force On servers were silently omitted from the chat.

Fix

Enforcement now happens server-side at three points:

  • chatd.CreateChat unions the requested IDs with every enabled force_on config before persisting the chat.
  • chatd.SendMessage applies the same union inside the update transaction whenever a caller-provided ID list would overwrite the chat's stored selection (nil still means "no change").
  • prepareGeneration merges force_on configs into the effective config set for each turn, which covers chats persisted before enforcement existed and servers marked force_on after chat creation.

Existing narrowing still applies on top of the merged set: plan-mode turns keep filtering on allow_in_plan_mode, and Explore chats keep their immutable spawn-time MCP snapshot (forced servers reach Explore children through the parent chat's already-enforced ID list). The forced-config lookup fails closed rather than running a turn without the forced set. User MCP tokens are now loaded whenever external MCP servers are connected, instead of only when the chat's stored ID list was non-empty.

Testing

All new tests fail before the fix and pass after it:

  • TestCreateChat_ForceOnMCPServerEnforced (chatd): a stripped create list still persists the forced server and its tools are offered to the LLM.
  • TestSendMessage_ForceOnMCPServerEnforced (chatd): an emptied or tampered mcp_server_ids update cannot remove the forced server.
  • TestGeneration_ForceOnMCPServerEnforcedForExistingChats (chatd): chats stored with an empty ID list before a server was marked force_on still get the forced tools at generation time.
  • TestChats_ForceOnMCPServerEnforced (coderd): endpoint-level walk of the original reproduction steps (admin creates a force_on server, regular member sends tampered requests through both endpoints).

Full ./coderd/x/chatd/... suite, chat handler tests, golangci-lint, and intxcheck pass.


Generated with mux

The Force On MCP server availability policy was only enforced
client-side: the frontend appended force_on server IDs to the
mcp_server_ids request parameter, so a user could strip them from the
request when creating a chat or sending a message and the forced
servers were silently omitted (Cure53 CDM-02-010).

Enforce the policy server-side at three points:

- chatd.CreateChat unions the requested IDs with every enabled
  force_on config before persisting the chat.
- chatd.SendMessage applies the same union inside the update
  transaction whenever a caller-provided ID list would overwrite the
  chat's stored selection.
- prepareGeneration merges force_on configs into the effective config
  set for each turn, covering chats persisted before enforcement
  existed and servers marked force_on after chat creation. Existing
  plan-mode filtering still narrows the merged set, and Explore
  chats keep their immutable spawn-time snapshot.

The forced-config lookup fails closed. User MCP tokens are now
loaded whenever external MCP servers are connected, not only when the
chat's stored ID list is non-empty.

Regression coverage: chatd-level tests for stripped create lists,
emptied update lists, and generation-time enforcement for pre-existing
chats, plus an endpoint-level test walking the original reproduction
steps through POST /api/experimental/chats and
POST /api/experimental/chats/{chat}/messages.

---
_Generated with [`mux`](https://github.com/coder/mux)_
@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

CODAGT-787

@ThomasK33
ThomasK33 marked this pull request as ready for review August 11, 2026 09:51
@ThomasK33
ThomasK33 merged commit 91d3027 into main Aug 11, 2026
63 of 64 checks passed
@ThomasK33
ThomasK33 deleted the cdm-policy-5ncb branch August 11, 2026 10:02
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants