fix(tlon): bound error body read in channel poke to prevent OOM#98526
fix(tlon): bound error body read in channel poke to prevent OOM#98526wings1029 wants to merge 2 commits into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 8:56 AM ET / 12:56 UTC. Summary PR surface: Source +12, Tests +146. Total +158 across 2 files. Reproducibility: yes. Current main and Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land one canonical Tlon bounded error-body hardening path, with an explicit maintainer choice between this strict poke-only fix plus sibling follow-up and the broader all-three-read PR. Do we have a high-confidence way to reproduce the issue? Yes. Current main and Is this the best way to solve the issue? Mostly yes for the stated path. The bounded read belongs at the Tlon HTTP poke boundary and uses an existing SDK helper, but the best repository outcome should coordinate with the broader Tlon PR or explicitly track the sibling channel-ops/SSE reads. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5e4d71686a6a. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +12, Tests +146. Total +158 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
4cba2e4 to
2ddeb67
Compare
2ddeb67 to
bcd83ee
Compare
bcd83ee to
5b1d6d1
Compare
|
Thanks for the focused poke-path fix and the strict overflow rationale. I landed the broader canonical Tlon error-body fix in #98496, which covers this same createHttpPokeApi path plus the sibling channel-ops and SSE non-ok response reads. Closing this as superseded by the merged broader fix: 8abd5d4 |
What Problem This Solves
createHttpPokeApiin the Tlon channel runtime reads a non-ok Urbit ship response body with a bareresponse.text()on the error path — without a byte cap and without a.catch()guard. If a malformed or misconfigured Urbit ship returns a large streaming error response, the unbounded read buffers the entire payload into anErrormessage, which can OOM the gateway process. Unlike the sibling error paths insse-client.tsandchannel-ops.ts(which at least have.catch(() => "")), this call site has no error-recovery guard at all — a stream read failure on the error path itself would crash the poke handler.Urbit ships are self-hosted (user-controlled), so the "upstream" is not a trusted provider with known response characteristics.
Why This Change Was Made
The fix replaces the bare
response.text()withreadResponseWithLimitcapped at 16 KiB, using the same strict pattern applied to the browser control error path (see #98455): overflow cancels the stream, the partial body is discarded, and the error falls back toPoke failed: <status>without the body text. The.catch(() => undefined)guard handles both the overflow case and stream read failures uniformly.readResponseWithLimitwas chosen overreadResponseTextLimitedto follow the canonical strict contract — fail-clean on overflow rather than preserving a truncated body fragment.User Impact
Normal Tlon poke operations are unchanged. On a non-ok response, a small error body (≤ 16 KiB) is still fully included in the error message. A pathological error response exceeding 16 KiB now produces
"Poke failed: 500"instead of buffering the entire body into process memory.Evidence
Real behavior proof
createHttpPokeApi.poke()error path buffers 4 MiB streaming body → OOM; fixed path cancels the stream at 16 KiB./~/loginreturning a cookie so authentication succeeds before the poke), Node 22, Linux.node:httpserver that returns HTTP 204 for/~/loginand a streaming 4 MiB body for channel requests.createHttpPokeApi(url, code, ship)thenapi.poke(...)through the real function path..messageis under 32 KiB and contains"Poke failed: 500"."invalid mark") is fully preserved.urbitFetchSSRF guard path (mocked in test); the siblingsendDm/sendGroupMessagecallers (they exercise the samepokemethod).Validation Evidence
Security & Privacy / Compatibility
No new configuration, no credential changes, no protocol changes. 16 KiB cap matches the established error-body cap used by browser control (#98455) and provider HTTP error helpers. A thin
__testexport is added for focused boundary coverage; it is not part of the public plugin API.AI-assisted.