Skip to content

fix(sandbox): cap the Go daemon's MCP JSON-RPC response body - #6162

Merged
pedrofrxncx merged 1 commit into
mainfrom
fix/daemon-cap-mcp-rpc-response-body-w3
Aug 18, 2026
Merged

fix(sandbox): cap the Go daemon's MCP JSON-RPC response body#6162
pedrofrxncx merged 1 commit into
mainfrom
fix/daemon-cap-mcp-rpc-response-body-w3

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Bug found while auditing packages/sandbox/daemon-go/internal/toolscatalog/mcp.go for resource-leak / oversized-payload gaps per the file's own stated threat model.

readRPCBody's plain-JSON branch called io.ReadAll(res.Body) with no size limit, while the SSE branch right next to it already caps a frame at 8MB via the scanner buffer (sc.Buffer(make([]byte, 0, 64*1024), 8*1024*1024)). The file already documents this exact threat model for pagination (maxCatalogPages's comment: "a misbehaving or malicious endpoint") — the same endpoint answering application/json instead of SSE could stream an unbounded body straight into the daemon's memory during a tools catalog fetch, with no cap at all.

Fix: cap the plain-JSON path at the same 8MB via io.LimitReader, returning an error when exceeded — matching the SSE branch's existing bound instead of adding a new one.

Failure scenario before the fix: a malicious or buggy downstream MCP connection responding with Content-Type: application/json and an arbitrarily large body could exhaust the daemon pod's memory on every catalog fetch against that connection.

Regression test: TestReadRPCBodyBoundsPlainJSON in mcp_test.go — a test server writes maxRPCResponseBytes+1 bytes as application/json; asserts readRPCBody rejects it with an "exceeded" error instead of buffering it.

Reviewer check: cd packages/sandbox/daemon-go && go test ./internal/toolscatalog/...

Locally ran: go build ./..., go test ./internal/toolscatalog/... (both existing and new tests pass), gofmt -l and go vet on the touched files (clean). Full CI validates the rest of the daemon-go suite.


Summary by cubic

Caps the Go daemon’s MCP JSON‑RPC response body at 8MB to prevent unbounded memory use. Previously, readRPCBody used io.ReadAll for application/json responses with no limit; now it uses io.LimitReader and returns an error when the body exceeds 8MB, matching the SSE branch’s per‑frame cap.

Review notes

  • Limits plain‑JSON responses to 8MB via maxRPCResponseBytes; oversized responses now fail with a “exceeded” error. SSE handling is unchanged.
  • Adds TestReadRPCBodyBoundsPlainJSON to assert rejection of oversized bodies.
  • If any MCP endpoints return >8MB JSON, reduce response size or paginate; these will now fail.
  • To verify: in packages/sandbox/daemon-go, run go test ./internal/toolscatalog/....

Written for commit 469d346. Summary will update on new commits.

Review in cubic

The plain-JSON branch of readRPCBody read a downstream MCP endpoint's
response with unbounded io.ReadAll, while the SSE branch next to it
already caps the frame at 8MB via the scanner buffer. A misbehaving or
malicious MCP connection returning application/json could stream an
arbitrarily large body straight into daemon memory during catalog
fetch. Cap the plain-JSON path at the same 8MB via LimitReader, matching
the file's existing threat model (see maxCatalogPages above it).
@pedrofrxncx
pedrofrxncx enabled auto-merge (squash) August 18, 2026 14:09
@pedrofrxncx
pedrofrxncx merged commit e828a23 into main Aug 18, 2026
25 checks passed
@pedrofrxncx
pedrofrxncx deleted the fix/daemon-cap-mcp-rpc-response-body-w3 branch August 18, 2026 14:10
pedrofrxncx added a commit that referenced this pull request Aug 18, 2026
decodeBody (used by every /fs/* route: read, write, edit, mkdir, unlink, mv,
list, write_from_url, upload_to_url) read the request body with an unbounded
io.ReadAll. A caller sending an oversized body could buffer it entirely into
the daemon's memory and crash it, tearing down the sandbox pod on the next
missed health probe.

Cap it at maxTransferBytes (500MB), the same bound the daemon already uses
for its other file-transfer path, following the same LimitReader pattern
used to cap the JSON-RPC response body in #6162.

Test: TestDecodeBodyRejectsOversizedRequest drives decodeBody with an
infinite reader and asserts it rejects instead of buffering forever.
pedrofrxncx added a commit that referenced this pull request Aug 18, 2026
decodeBody (used by every /fs/* route: read, write, edit, mkdir, unlink, mv,
list, write_from_url, upload_to_url) read the request body with an unbounded
io.ReadAll. A caller sending an oversized body could buffer it entirely into
the daemon's memory and crash it, tearing down the sandbox pod on the next
missed health probe.

Cap it at maxTransferBytes (500MB), the same bound the daemon already uses
for its other file-transfer path, following the same LimitReader pattern
used to cap the JSON-RPC response body in #6162.

Test: TestDecodeBodyRejectsOversizedRequest drives decodeBody with an
infinite reader and asserts it rejects instead of buffering forever.
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.

1 participant