fix(tlon): bound auth response body at 16 MiB#97730
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 11:51 AM ET / 15:51 UTC. Summary PR surface: Source +9, Tests +124. Total +133 across 2 files. Reproducibility: yes. Current main has a clear source-reproducible path: 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 the shared capped auth drain once maintainers accept the 16 MiB login-body cutoff; keep other Tlon response-read hardening tracked in the narrower related PRs. Do we have a high-confidence way to reproduce the issue? Yes. Current main has a clear source-reproducible path: Is this the best way to solve the issue? Yes. Reusing AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f8e1e0c5bfbc. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +9, Tests +124. Total +133 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
|
|
Thanks for the precise review — already addressed the only concrete P3 finding.
Acceptance commands run after the fix: The P1 maintainer-side note about Urbit auth endpoints that rely on oversized bodies for cookie finalization: my read of
🤖 Generated with Claude Code |
|
@clawsweeper re-review The curly lint failure in extensions/tlon/src/urbit/auth.bounded.test.ts was fixed in 6c54f42; CI check-lint is now green. Please re-run the review on the current HEAD. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Closing as superseded by the 2026-06-29 16 MiB batch. Decision: This PR replaces the Tlon Why: 10 days open after the batch landed; no maintainer movement. A 6/29-vintage PR in a now-closed initiative has low expected review velocity. Supported alternative: If a follow-up is desired, a one-line PR that imports Evidence that would change the decision: a concrete user-reported OOM or hang on tlon auth flows at >16 MiB body sizes, or a maintainer explicitly asking for this branch. 🤖 Generated with Claude Code |
What Problem This Solves
extensions/tlon/src/urbit/auth.ts:40callsawait response.text().catch(() => "")to drain the body of the Urbit/~/loginresponse — necessary because "some Urbit setups require the response body to be read before cookie headers finalize". The call is unbounded: a hostile or broken Urbit endpoint (or any man-in-the-middle behind the SSRF-allowlisted host) can return an arbitrarily large body and exhaust OpenClaw memory before headers land.This is the same flavor of bug as the closed cluster:
readResponseWithLimitis already deployed in #96989 (provider-transport-fetch), #96993 (agents), #97687 (tlon SSE upstream sibling), and 20+ other extensions; the/~/loginauth path is the lasturbitFetchconsumer in the tlon extension that still calls.text()without a cap.Changes
extensions/tlon/src/urbit/auth.tsreadProviderTextResponsefromopenclaw/plugin-sdk/provider-http(re-export of the SDK default 16 MiB cap).await response.text().catch(() => "");withawait readProviderTextResponse(response, "Tlon Urbit auth");wrapped intry/catchso the original "discard body read outcome, let cookie headers parse anyway" semantic is preserved.AGENTS.mdinline-comment shape rule: short, why-not-what, cites the contract.extensions/tlon/src/urbit/auth.bounded.test.ts(new, 122 LoC)http.createServer:.catchdiscards overflow, cookie header parse still runs).lookupFnpattern fromextensions/tlon/src/urbit/auth.ssrf.test.tsso the loopback server is reachable without network egress.Net diff:
132 +9. Same shape as the #97687 (tlon SSE) and #97721 (discord gateway-metadata) precedents.Real behavior proof
This PR adds inline loopback proof (no mocking) per
loopback-proof-required-bounded-read.mdand the Alix-007 #96772 inline test pattern. The new test exercises the productionauthenticate→urbitFetch→fetchWithSsrFGuard→readProviderTextResponsechain over a real local HTTP server:Before / After diff at the body-read site:
extensions/tlon/src/urbit/auth.tsonopenclaw/main)await response.text()buffers all 18 MiB into a string before the loop ends; OOM-able76659f2081)readProviderTextResponsecaps at 16 MiB; the read error is caught and the cookie header parse still runsNegative control: normal-size body returns the expected cookie through the bounded path unchanged.
Evidence
/~/loginauth response body at 16 MiB so an oversized/broken Urbit endpoint cannot exhaust OpenClaw memory before set-cookie headers finalize.extensions/tlon/src/urbit/auth.bounded.test.tsagainst a realhttp.createServerloopback, run vianode scripts/test-extension.mjs tlon -t "bounded-read real wire proof".node scripts/test-extension.mjs tlon -t "bounded-read real wire proof" --reporter=verbose→Test Files 1 passed | 15 skipped | Tests 2 passed | 135 skipped(the new 2 tests pass, no unrelated test was touched)pnpm tsgo:extensions→ exit 0 (extensions lane typecheck clean)[tlon urbit auth bounded-read proof] oversized path: cap=16777216 bytes; cookie preserved server_total=18874368[tlon urbit auth bounded-read proof] normal path: cookie present=true.catchdiscards the overflow, and the cookie header parse still runs to completion. Normal-size bodies parse identically through the new bounded path.readProviderTextResponseSDK helper as 20+ other extensions, so behavior is byte-identical.Pre-existing failure note
extensions/tlon/src/core.test.ts:159(the "configures ship, auth, and discovery settings" setup-wizard test) is failing onopenclaw/mainwithError: Unexpected prompt: Ship 名称. This PR does not cause it — I verified bygit stash-ing my change and re-runningnode scripts/test-extension.mjs tlonagainstopenclaw/maindirectly: same 1 failed / 15 passed / 136 vs 137 tests. This is an unrelated wizard-prompter test that mocks against a translated prompt and fails on localized runs; outside the scope of this bounded-read PR.Tests and validation
extensions/tlon/src/urbit/auth.bounded.test.ts— 2 inline tests via real loopbackhttp.createServer:does not OOM and discards body when an oversized body arrives on real wirereturns parsed cookie for normal-size body on real wirenode scripts/test-extension.mjs tlon -t "bounded-read real wire proof"→ 2/2 pass, no unrelated test touched (135 skipped).pnpm tsgo:extensions→ exit 0.Risk checklist
auth.ts), 1 file test added. Total+133 / -2LoC (S).response.text()would have on a small body.release()lifecycle unchanged — read happens inside the existingtry/finally, soawait release()still runs.openclaw/plugin-sdk/provider-http; the SDK surface already exposesreadProviderTextResponsefor this exact case).urbitFetchreturns the standard undiciResponsefromfetchWithSsrFGuard(same as the msteams / inworld / discord precedents). ThegetReader()path is taken, thearrayBuffer()fallback is unreachable. No additional body==null check needed.extensions/tlon/src/urbit/sse-client.ts; this PR coversextensions/tlon/src/urbit/auth.ts. Non-overlapping sub-surfaces.🤖 Generated with Claude Code