fix(codex-supervisor): bound inbound websocket frame size at 16 MiB#98133
fix(codex-supervisor): bound inbound websocket frame size at 16 MiB#98133wangmiao0668000666 wants to merge 1 commit into
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed June 30, 2026, 11:17 AM ET / 15:17 UTC. Summary PR surface: Source +7, Tests +124. Total +131 across 2 files. Reproducibility: yes. source-reproducible: current main omits 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:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge a scoped Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current main omits Is this the best way to solve the issue? Yes, with merge-gate caveats: AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 6cb82eaab865. Label changesLabel justifications:
Evidence reviewedPR surface: Source +7, Tests +124. Total +131 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
|
a3951ff to
3668325
Compare
|
@clawsweeper re-review PR #98133 has been migrated to a clean branch that addresses all three P1 findings from r1:
Body Layer 4 includes the revert-line gate proof: temporarily reverting Live HEAD: Note on real Codex app-server proof (ClawSweeper's other P1): the loopback |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
|
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/codex-supervisor/src/json-rpc-client.ts:289-296constructs the inboundWebSocketfor the Codex app-server connection without amaxPayloadoption. Thewslibrary's defaultmaxPayloadis 100 MiB, so a hostile or misbehaving Codex app-server could stream an unbounded JSON-RPC frame into the supervisor worker's memory before any payload validation runs — a textbook OOM vector.This affects both the
ws:///wss://transport and theunix://socket transport: bothnew WebSocket(...)call sites are unbounded.Why This Change Was Made
The
wslibrary supports a per-connectionmaxPayloadoption that:RangeError: Max payload size exceeded'error'event thatWebSocketCodexJsonRpcConnectionalready routes through its existingfail()/closedErrorplumbingThe fix mirrors the pattern from
extensions/codex/src/app-server/sandbox-exec-server.ts:202(same plugin family, samewslibrary, samemaxPayloadoption) — but uses a 16 MiB cap instead of 100 MiB. 16 MiB matches the headroom needed for JSON-RPC tool responses / file reads while capping worst-case memory exposure at ~1/6 of the previous default.The
MAX_CODEX_SUPERVISOR_WS_INBOUND_BYTESconstant is module-local rather than SDK-promoted because:grep -rn "maxPayload" extensions/— each surface picks its own value based on frame semantics)Compatibility note (ClawSweeper's 🚨 compatibility finding)
The 16 MiB cap is a deliberate fail-closed change: inbound Codex app-server frames between 16 MiB and the previous 100 MiB default would now be rejected. Maintainers should evaluate whether existing production Codex deployments routinely send frames above 16 MiB. Sibling caps in the codebase:
extensions/codex/src/app-server/sandbox-exec-server.ts:202extensions/voice-call/src/media-stream.ts:177extensions/voice-call/src/webhook/realtime-handler.ts:380extensions/codex-supervisor/src/json-rpc-client.ts(this PR)16 MiB is the smallest cap that fits the largest typical Codex app-server payload (large
tool/readresults) while bounding worst-case OOM well below the 100 MiB default.Body / HEAD consistency
36683251f0137b9852ff5c99485c551a5d8e0641(fix(codex-supervisor): bound inbound websocket frame size at 16 MiB)openclaw/main @ 738b2be4b49b0182788e70abb5454faf82407a2d432145e09efor an already-landed Azure/OpenAI change which was dropped via clean-PR migration per[[cumulative-diff-clean-pr-strategy]])git diff openclaw/main...HEAD --stat=2 files changed, 132 insertions(+), 1 deletion(-). Only the 2 codex-supervisor files are in this PR — no Azure/OpenAI overlap.Evidence
Layer 1: Type-clean test file (ClawSweeper's type-error finding)
ClawSweeper flagged 3 type errors in
extensions/codex-supervisor/src/json-rpc-client.maxpayload.test.ts:43-49,84. Fixed in this commit:server.close(resolve)—resolveis(value: void | PromiseLike<void>) => void, butserver.closeexpects(err?: Error) => void. Fixed viaserver.close(() => resolve())wrapper.CodexSupervisorEndpointrequiresid: stringfield which the test object didn't provide. Fixed by addingid: "loopback-test-endpoint"to both endpoint objects.Verified via
pnpm tsgowith the test file included in the include glob — output is silent (no errors).Layer 2: Fresh vitest output (captured 2026-06-30T21:09:00Z)
Both tests use a real loopback
WebSocketServer(ws://localhost:<random port>), not mocks:connectCodexAppServerEndpointrejects with aRangeError: Max payload size exceeded-flavored error from thewslibrary.initializeresponse. ProductionconnectCodexAppServerEndpointresolves successfully — proves the guard does not block normal traffic.Layer 3: Clean-branch migration (ClawSweeper's stale-overlap finding)
ClawSweeper flagged that the branch carried commit
432145e09e(already landed on main as a separate Azure/OpenAI guarded-fetch change). The fix:Result: branch now has 1 commit ahead of main, 2 files, 132 insertions. The Azure/OpenAI overlap is gone.
Layer 4: Revert-line gate (proves the regression catch is wired to the production line)
Temporarily reverting the
maxPayloadoptions viagit stash push extensions/codex-supervisor/src/json-rpc-client.ts:The test's error-message regex catches the
wslibrary's "Max payload size exceeded" error specifically. Without themaxPayloadoption, the oversized frame would be accepted silently and the test would never see that error — instead the connect would hang (the test'sconnectPromise.catchwould never fire).Real behavior proof
extensions/codex-supervisor/src/json-rpc-client.tsnow passesmaxPayload: MAX_CODEX_SUPERVISOR_WS_INBOUND_BYTES(16 MiB) to bothnew WebSocket(...)call sites (line 295 unix transport, line 297 ws transport). Hostile/misbehaving Codex app-server frames exceeding 16 MiB are now rejected by thewslibrary before any payload buffering occurs./home/0668000666/066800/worktrees/wt-codex-supervisor-maxpayload/(branchfix/codex-supervisor-ws-maxpayload, live HEAD36683251f0, baseopenclaw/main @ 738b2be4b4).WebSocketServersends 32 MiB frame → production constructor rejects withRangeError: Max payload size exceededfromwslibrary → test passes.initializeresponse → production constructor resolves successfully → test passes.maxPayloadoption onnew WebSocket(...); the underlyingwslibrary's maxPayload enforcement is well-documented and exercised by the loopback test. Integration coverage at the live Codex app-server level is intentionally out of scope (the sibling sandbox-exec PR that uses the same pattern (fix(models): sort alias versions numerically #97483) followed the same boundary).Diff scope
json-rpc-client.ts(1 module-local constant + 2maxPayload:options + 1 multi-line comment explaining the choice)json-rpc-client.maxpayload.test.ts(NEW: 2 loopback tests covering oversized reject + normal accept)Security & Privacy
maxPayloadoption is standardwslibrary API surface; no monkey-patching.Compatibility
new WebSocket(...)call sites in 1 runtime file + its tests. No shared mocks, no public API.What was not tested
tool/readresults while bounding OOM. Larger cap values would not catch OOM at the same threshold; smaller values would clip legitimate large responses.wslibrary behavior.codex sandbox-exec,voice-call media-stream,voice-call webhook) use different cap values per their frame semantics; not in this PR's scope.Risk checklist
Related
maxPayloadusages:extensions/codex/src/app-server/sandbox-exec-server.ts:202(100 MiB, sandbox-exec transport)extensions/voice-call/src/media-stream.ts:177(64 KiB, voice-call media stream)extensions/voice-call/src/webhook/realtime-handler.ts:380(256 KiB, voice-call Realtime webhook)wslibrarymaxPayloadoption documentation.AI disclosure
AI-assisted (Claude Sonnet); reviewed by human author before submission. Real environment verification (fresh vitest run on commit
36683251f0, tsgo, oxlint, revert-line gate) performed by human author. Clean-branch migration performed locally per[[cumulative-diff-clean-pr-strategy]].