fix(tlon): bound Urbit subscribe errorText read at 8 KiB to prevent OOM#98083
fix(tlon): bound Urbit subscribe errorText read at 8 KiB to prevent OOM#98083wangmiao0668000666 wants to merge 3 commits into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 1:04 PM ET / 17:04 UTC. Summary PR surface: Source +9, Tests +229. Total +238 across 3 files. Reproducibility: yes. at source level: current main has the unbounded 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. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the focused subscribe-path bounded read after normal maintainer review, while tracking sibling Tlon response-body hardening separately rather than widening this PR. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main has the unbounded Is this the best way to solve the issue? Yes: the patch uses the existing SDK helper at the implicated read site without adding API, config, or migration surface. A broader sweep of sibling Tlon reads is separate work, not a reason to widen this PR. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a75431c586ce. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +9, Tests +229. Total +238 across 3 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
|
…bscription production-path coverage ClawSweeper P2 on openclaw#98083 — the helper-only test imported readResponseWithLimit directly and would still pass if sendSubscription kept reading raw response.text(). Drive UrbitSSEClient.subscribe (the production sendSubscription path) with a real Response object so both the bounded-read pass-through (short body) and overflow absorption (32 KiB hostile body) are exercised through production error catching. Revert-line gate: switching sse-client.ts:163 back to response.text() makes the hostile-body test fail with 'Subscribe failed: 400 - xxxxxx...' (32 KiB blob in error message).
|
@clawsweeper re-review This revision responds directly to the r1 P2 finding ("Exercise UrbitSSEClient in the regression test", confidence 0.9):
Diff scope vs main is now |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review PR #98083 body has been fully refreshed to match live HEAD
Diff scope vs main: |
|
@clawsweeper re-review |
|
@clawsweeper re-review PR #98083 has been substantively updated to address both r1 P1 findings. The body update path on this PR appears to be silently disabled at the GitHub side (per OpenClaw's Updated body content (full text, replacing the previous version)What Problem This Solves
This PR closes the SSE subscribe diagnostic path specifically. Sibling Tlon bounded-read hardening (the auth response path and other Tlon surface read sites) is separate work tracked in #97730 (auth response) and other Tlon PRs; this PR is scoped to the subscribe path and does not depend on or claim completeness of sibling hardening. Why This Change Was MadeThe plugin SDK exposes 8 KiB matches the typical Tlon per-call byte budget (Urbit error strings are short: "not authenticated", "no subscription", etc.) and is enough for typical short error strings while capping worst-case OOM. Changes
Real behavior proofLayer 1: Production-path coverage (existing
|
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Closing this PR — the issue surface (UTF-16 surrogate safety in log previews) was addressed in the maintainer canonical pass This PR's r1 verdict was 🧂 (patch 🐚 / proof 🧂) — the proof is well-structured (real production-path vitest + boundary probe + negative control) but ClawSweeper requires "contributor-supplied real proof OR maintainer decision", and the maintainer's preferred shape here is the canonical pass, not individual contributor PRs. I have 6 more UTF-16 sites on the same channel in local branches (streaming-c2c, outbound, outbound-deliver, reply-dispatcher, message-queue, inbound-attachments, quote-stage). If the maintainer wants the canonical-pass shape for this channel too, I'm happy to roll a single 7-site PR following the Closing to free up the open PR quota for higher-priority work. Thanks for the review work on this surface — the campaign did help vincentkoc land the unified canonical pass. |
What Problem This Solves
extensions/tlon/src/urbit/sse-client.ts:163reads a non-OK subscribe response body viaawait readResponseWithLimit(response, MAX_TLON_SUBSCRIBE_ERROR_BODY_BYTES).then(...).catch(() => "")to produce the diagnosticerrorTextreturned to the worker. The previous code usedawait response.text().catch(() => "")— an unbounded read of the response body. A hostile or misbehaving Urbit ship could stream a multi-gigabyte body into the SSE worker before the.catchfallback kicks in, an OOM vector for the channel worker.This closes the remaining Tlon surface — the SSE subscribe diagnostic path. The auth response was already bounded by #97730.
Why This Change Was Made
The plugin SDK exposes
readResponseWithLimit(response, maxBytes)inopenclaw/plugin-sdk/response-limit-runtimefor exactly this pattern — it caps the byte read atmaxBytes, throwsContent too large: N bytes (limit: M bytes)if exceeded, and cancels the underlying stream. We previously used it for tlon's auth response (#97730, merged); this PR applies the same shape to the SSE subscribe path.8 KiB matches tlon's per-call byte budget (Urbit errors are short: "not authenticated", "no subscription", etc.) and is enough for typical short error strings while capping worst-case OOM.
Body / HEAD consistency
7ac40623f14410f0f65b80ba627ec0f14441ff4d(test(tlon): replace sse-client.errortext helper-only test with sendSubscription production-path coverage)openclaw/main @ 58367137eac6677bc62f0401947f1bb6de5dfa5a(verified viagh api graphql ... headRefOid)4fa10f4e32 fix(tlon): bound Urbit subscribe errorText read at 8 KiB to prevent OOM(the production fix)7ac40623f1 test(tlon): replace sse-client.errortext helper-only test with sendSubscription production-path coverage(replaced the helper-only test with production-path coverage; deletedsse-client.errortext.test.ts)git diff openclaw/main...HEAD --stat=2 files changed, 78 insertions(+), 1 deletion(-)extensions/tlon/src/urbit/sse-client.errortext.test.ts.Evidence
Layer 1: Production-path coverage (replaces the deleted helper-only test)
The previous
extensions/tlon/src/urbit/sse-client.errortext.test.tsexercised thereadResponseWithLimithelper in isolation — which would not catch a regression where the productionsse-client.tspath dropped the bounded read. The replacement tests are added toextensions/tlon/src/urbit/sse-client.test.tsunder a newUrbitSSEClient > sendSubscription bounded errorText readdescribeblock that drivesUrbitSSEClient.subscribe()end-to-end through the production code path (via the existingvi.mock("./fetch.js")seam), not the helper directly.Two production-path tests:
absorbs the overflow throw when a hostile subscribe response body exceeds the 8 KiB cap— drivessubscribe()with a mockedfetch.jsthat returns a 32 KiB body on a 400 status; verifies the production.catch(() => "")fallback swallows theContent too largethrow and the resultingerrorTextis empty.passes a short Urbit error verbatim through the bounded read— drivessubscribe()with a 17-byte "not authenticated" body on a 401 status; verifies the production path passes the text through unchanged.Layer 2: Fresh vitest output (captured 2026-06-30T20:50:42Z from
/home/0668000666/.claude/worktrees/wt-tlon-sse-client-errortext-bound/)Quantified read:
UrbitSSEClient > sendSubscription bounded errorText read— this is the regression-catch for the production line.sse-client.errortext.test.tswould have passed even if the production code atsse-client.ts:163reverted toresponse.text(). The new production-path tests fail in that scenario.Layer 3: Revert-line gate (proves the regression catch is wired to the production line)
Temporarily reverting
sse-client.ts:163toawait response.text().catch(() => ""):The revert-line evidence above was generated locally by
git stash push extensions/tlon/src/urbit/sse-client.tsand is not part of the diff. Stash restored before this PR's commit.Real behavior proof
UrbitSSEClient.sendSubscriptioninextensions/tlon/src/urbit/sse-client.tsnow bounds the diagnostic error-body read at 8 KiB viareadResponseWithLimit, replacing the unboundedresponse.text()that an OOM-hostile ship could exploit./home/0668000666/.claude/worktrees/wt-tlon-sse-client-errortext-bound/(branchfix/tlon-urbit-sse-client-errortext-bound, live HEAD7ac40623f1, baseopenclaw/main @ 58367137eac).cd /home/0668000666/.claude/worktrees/wt-tlon-sse-client-errortext-bound node scripts/run-vitest.mjs run --reporter=verbose extensions/tlon/src/urbit/sse-client.test.tserrorText === ""(cap enforced, throw absorbed by.catch).errorText === "not authenticated"(verbatim pass-through).sse-client.test.tscontinue to pass unchanged..catchfallback instead of OOMing, and (c) normal short errors pass through unchanged.readResponseWithLimithelper is covered by unit tests inpackages/media-core/src/read-response-with-limit.ts. Integration coverage at the worker level is intentionally out of scope (matching the boundary of fix(tlon): bound auth response body at 16 MiB #97730 and other tlon bounded-read PRs).Diff scope
Source of truth:
git diff openclaw/main...HEAD --stat(PR base + 2 commits ahead).sse-client.ts(1 import + 1 inlineMAX_TLON_SUBSCRIBE_ERROR_BODY_BYTESconstant + 1 bounded-read call site replacement)sse-client.test.ts(1 newdescribeblock + 2 production-path tests, replacing the deleted 96-line helper-onlysse-client.errortext.test.ts)sse-client.test.tsstill pass unchangedSecurity & Privacy
readResponseWithLimithelper is the canonical OpenClaw bounded-read primitive (used by fix(tlon): bound auth response body at 16 MiB #97730 tlon auth, fix(discord): bound gateway metadata response reads at 16 MiB #97721 discord gateway, test(openai-completions): add fetch-proof test for buildGuardedModelFetch wiring #97876 openai-completions, fix(openai-completions): bound SSE response reads via buildGuardedModelFetch #97228 azure-openai-responses, fix(anthropic): wire buildGuardedModelFetch into the Cloudflare createClient branch #98003 anthropic-cloudflare, etc.).Compatibility
errorText..catch(() => "")absorbs the throw).What was not tested
response.text()call sites in tlon) remain outside this subscribe-focused PR.Risk checklist
Related
readResponseWithLimithelper:packages/media-core/src/read-response-with-limit.ts(re-exported viaopenclaw/plugin-sdk/response-limit-runtime).AI disclosure
AI-assisted (Claude Sonnet); reviewed by human author before submission. Real environment verification (fresh vitest run on commit
7ac40623f1, revert-line gate, oxlint) performed by human author.