fix(security): bound response bodies in secureFetchWithPinnedIP by default - #6169
Conversation
…fault secureFetchWithPinnedIP only capped a response body when the caller passed maxResponseBytes; with the option absent the body streamed into an unbounded Buffer.concat. Several tool proxies whose target host is user-supplied (Jupyter, ClickHouse, Grafana, 1Password Connect) and the RSS poller called it without that option, so an attacker-controlled server answering with an endless chunked body could grow the shared process heap until it was OOM-killed. Make the cap fail-safe: default to 100MB (and treat a non-positive value as the default) so there is no unlimited mode, then pass tighter explicit caps at the user-supplied-host sites. Responses that carry no body (HEAD, 204, 304) are exempted from the content-length pre-check — they advertise the resource size as metadata, which would otherwise spuriously fail a HEAD probe of a large file or an RSS conditional-GET 304.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview User-supplied-host callers pass tighter limits: 10MB ( New loopback tests cover explicit cap, default cap, bodyless responses, and streaming rejection; shared test mocks export the new constants. Reviewed by Cursor Bugbot for commit cfe18d4. Configure here. |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cfe18d4. Configure here.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cfe18d4. Configure here.
|
@greptile review |
Greptile SummaryThe PR makes response-size limiting fail-safe in
Confidence Score: 5/5The PR appears safe to merge, with no actionable regressions identified in the changed response-cap behavior. The default and endpoint-specific limits consistently bound buffered bodies, bodyless HTTP responses avoid metadata false positives, and the changed paths retain appropriate stream error and socket cleanup behavior.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/core/security/input-validation.server.ts | Introduces the default 100MB response ceiling, a shared 10MB JSON ceiling, and bodyless-response handling while preserving stream-level enforcement and cleanup. |
| apps/sim/lib/core/security/secure-fetch-response-cap.server.test.ts | Covers chunked overflow, default content-length rejection, successful bounded reads, HEAD metadata, and 304 metadata using a local HTTP server. |
| apps/sim/app/api/tools/onepassword/utils.ts | Defaults 1Password Connect JSON responses to 10MB while allowing file callers to request a larger explicit bound. |
| apps/sim/lib/webhooks/polling/rss.ts | Caps attacker-controlled RSS response bodies at 10MB before XML parsing. |
| apps/sim/app/api/tools/jupyter/proxy/route.ts | Adds a 10MB upstream response limit to the user-configurable Jupyter proxy. |
| apps/sim/app/api/tools/clickhouse/utils.ts | Adds a 10MB limit to ClickHouse response buffering. |
Reviews (1): Last reviewed commit: "fix(security): bound response bodies in ..." | Re-trigger Greptile
Summary
secureFetchWithPinnedIPonly bounded a response body when the caller passedmaxResponseBytes— with the option absent the body streamed into an unboundedBuffer.concat. Tool proxies whose target host is user-supplied (Jupyter, ClickHouse, Grafana, 1Password Connect) and the RSS poller called it without a cap, so an attacker-controlled server answering with an endless chunked body grows the shared process heap until it is OOM-killed. One ~200-byte tool invocation is enough; an RSS trigger repeats it unattended on a schedule.maxResponseBytesnow defaults to 100MB, and a non-positive value falls back to the default — there is no unlimited mode left.MAX_FILE_SIZEfor 1Password file content, 10MB for the RSS poller (which also bounds the XML handed torss-parser, which has no input-size limit of its own).HEADprobe of any file >100MB and the RSS conditional-GET304.Type of Change
Testing
Audited all 47
secureFetchWithPinnedIPcall sites for false positives before defaulting the cap:/api/tools/**route's response is already read back by the executor throughreadToolResponseBody, capped atMAX_TOOL_RESPONSE_BODY_BYTES(10MB) on the internal-fetch branch too — so anything above 10MB already fails today and the new caps cannot block a path that currently works.MAX_FILE_SIZE= 100MB = the new default — identical behavior.New
secure-fetch-response-cap.server.test.tsdrives a real loopback server (explicit cap on a chunked body, default cap via content-length, an under-cap read, HEAD, 304). Verified the tests can fail: revertedinput-validation.server.tsand 3 of the 5 went red.Full
apps/simsuite: 17,294 passing; the only 2 failures are pre-existing and environmental (a test shelling out to a missingrgbinary, and a 30s-timeout flake under parallel load) and reproduce independently of this change.tscclean onapps/simandpackages/testing, biome clean,check:api-validationpassed.Checklist