fix(sandbox-proxy): cap upstream response body size on all proxied routes - #5330
Merged
pedrofrxncx merged 1 commit intoJul 28, 2026
Merged
Conversation
…utes #5316/#5319 capped request bodies clients send into this router (forwardedBodyLimit) because proxyDaemon buffers them fully into memory via c.req.text() before forwarding. The response side had the same gap: every route reads the daemon's (or, for preview-fetch/preview-invoke, the customer's own preview server's) response fully via upstream.text() with no ceiling. A runaway git diff, a chatty exec script, or a huge preview page gets buffered entirely into the Studio API process's memory. Added readBoundedText(), which reads a Response body in chunks and throws UpstreamPayloadTooLargeError once it exceeds the same 10MB cap already used for request bodies, instead of buffering an unbounded amount. Wired it into proxyDaemon, fetchDaemonJson (used by suggest-commit/judge-review), and the preview-fetch/preview-invoke routes — replacing every remaining upstream.text() call in this file.
pedrofrxncx
enabled auto-merge (squash)
July 28, 2026 15:01
decocms Bot
pushed a commit
that referenced
this pull request
Jul 28, 2026
PR: #5330 fix(sandbox-proxy): cap upstream response body size on all proxied routes Bump type: patch - decocms (apps/api/package.json): 4.139.11 -> 4.139.12 Deploy-Scope: server
pedrofrxncx
added a commit
that referenced
this pull request
Jul 28, 2026
apps/api/src/tools/sandbox/{patch-sandbox-operator,resolve-env,sync-git-credentials}.ts
each PUT to the daemon's /_sandbox/config and, on a non-ok response, read the
error body with a plain res.text() with no size cap. sandbox-proxy.ts already
bounds every proxied response to the same endpoint via readBoundedText()
(#5316/#5319/#5330) — these three call sites hit /_sandbox/config directly
(during publish, rebase, and sandbox start) and were missed, so a
compromised or misbehaving daemon can still make the API process buffer an
unbounded error body into memory on that path.
Extracted readBoundedText()/UpstreamPayloadTooLargeError out of
sandbox-proxy.ts into apps/api/src/lib/bounded-text.ts (no behavior change)
so the tools/sandbox call sites can reuse it without an api-routes ->
tools-layer dependency, and switched all three sites to it with the same
10MB cap sandbox-proxy.ts already applies to this endpoint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Source: a hardening follow-up to #5316/#5319, found while auditing the same file (
apps/api/src/api/routes/sandbox-proxy.ts) for this tick's "sandbox-proxy route robustness" focus area.Why: #5316 and #5319 capped every route that reads a client request body fully into memory via
c.req.text()before forwarding it to the daemon (forwardedBodyLimit, 10MB). The response side of the exact same routes has the identical gap:proxyDaemon'sresolveAndFetchreads the daemon's response viaupstream.text()with no size ceiling,fetchDaemonJson(used bysuggest-commit/judge-review) does the same, and thepreview-fetch/preview-invokeroutes read a customer's own preview-server response the same unbounded way. A runawaygit diff, a chatty exec script writing to stdout, or a large preview page gets buffered entirely into the shared Studio API process's memory — the same DoS-by-memory-exhaustion class #5316/#5319 just closed, just on the other side of the same fetch.Fix: added
readBoundedText(response, maxBytes), which reads aResponsebody in chunks via its reader and throwsUpstreamPayloadTooLargeErroronce the running total exceedsmaxBytes, instead of buffering an unbounded amount. Wired it into all four remainingupstream.text()call sites in this file, reusing the existingFORWARDED_BODY_MAX_BYTES(10MB) constant so the response cap matches the already-established request cap.proxyDaemon's catch block now returns a clear502for the new error instead of folding it into the generic "Daemon unreachable" message.Regression test:
readBoundedTextreturns the full body under the cap and throwsUpstreamPayloadTooLargeError(instead of buffering) once a body exceeds it — added tosandbox-proxy.test.ts.How to verify:
bun test apps/api/src/api/routes/sandbox-proxy.test.ts(10 pass, including the 2 new cases);grep -n 'upstream.text()' apps/api/src/api/routes/sandbox-proxy.tsnow returns nothing — every prior unbounded read goes throughreadBoundedText.Checks run locally:
bun run fmtandcd apps/api && bunx tsc --noEmit(both clean), plus the targeted test above. Full CI validates the rest.Summary by cubic
Cap upstream response body size to 10MB across all sandbox-proxy routes to prevent memory exhaustion. Responses are now read in chunks, with clear 502 errors when the cap is exceeded.
readBoundedText(response, maxBytes)that streams and caps response bodies, throwingUpstreamPayloadTooLargeError.upstream.text()calls inapps/api/src/api/routes/sandbox-proxy.ts(proxyDaemon,fetchDaemonJson,preview-fetch,preview-invoke) withreadBoundedText.FORWARDED_BODY_MAX_BYTES(10MB) to match the existing request body cap.502with a specific error instead of the generic "Daemon unreachable".sandbox-proxy.test.ts.Written for commit 1e942fe. Summary will update on new commits.