fix(discord): bound REST response body to prevent OOM flood#95412
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 25, 2026, 6:44 PM ET / 22:44 UTC. Summary PR surface: Source +19, Tests +72. Total +91 across 2 files. Reproducibility: yes. Current main has an 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. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the focused RequestClient hardening after maintainer acceptance or tuning of the 8 MiB ceiling, and track any broader Discord success-body policy separately. Do we have a high-confidence way to reproduce the issue? Yes. Current main has an unbounded Is this the best way to solve the issue? Yes for the scoped RequestClient path. The PR uses the existing plugin SDK response-limit helper and preserves parsing/status behavior; the maintainer choice is the cap value and whether broader Discord API hardening is separate follow-up. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 95b97e5b0b5e. Label changesLabel justifications:
Evidence reviewedPR surface: Source +19, Tests +72. Total +91 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
|
|
@clawsweeper re-review — Added the |
|
🦞🧹 I asked ClawSweeper to review this item again. |
The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper.
140a4f1 to
172cffd
Compare
|
Merge-ready for 172cffd. Local and ClawSweeper review clean, CI green, best narrow fix, follows established pattern, and cap is acceptable because this Discord REST path reads bounded JSON/control responses rather than media bytes. The relevant Discord REST surfaces are documented as paginated/limited responses, such as channel messages, guild members, and reaction users; file/media content is metadata or separate payload data rather than inline REST response bodies. The 8 MiB ceiling is therefore comfortably above expected successful REST payloads while still preventing unbounded response buffering. |
…#95412) The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper.
…#95412) The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper.
…#95412) The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper.
…#95412) The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper. (cherry picked from commit 2d2a50c)
…#95412) The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper.
…#95412) The Discord REST main response path read the body with an unbounded await response.text() before JSON-parsing it. A controlled or hijacked endpoint could stream an arbitrarily large body and exhaust memory (OOM). Wrap the read in the canonical readResponseWithLimit helper with an 8 MiB cap (well above any legitimate Discord JSON payload) plus an idle timeout tied to the request timeout, so the stream is cancelled at the cap or on stall instead of buffering unbounded. Normal payloads still parse fully. This mirrors PR openclaw#95108 which bounded the analogous Anthropic Messages error-response read with the same helper. (cherry picked from commit 2d2a50c)
What Problem This Solves
This exists to stop Discord REST main responses from buffering attacker-sized bodies into memory before JSON parsing.
RequestClient.executeRequestpreviously read the main response with unboundedawait response.text()(extensions/discord/src/internal/rest.ts:252), so a controlled or hijacked REST endpoint, proxy, or gateway redirection path could stream an arbitrary response and turn the Discord extension into an OOM / DoS target.Changes
readResponseWithLimit, using an 8 MiB cap plus an idle timeout derived from the request timeout.coerceResponseBodyparsing path for normal JSON, empty responses, rate limits, and errors after the bounded read completes.Evidence
The earlier proof for this PR used an in-process stream fixture, so this proof was upgraded to a real terminal
node:httpcheck. The scratchpad proof script is local only and is not committed. It starts a real TCP loopback HTTP server, drives the exportedRequestClientwith its normal fetch path, streams a no-Content-Lengthbody larger than 16 MiB, verifies bounded throw and socket abort, runs a raw-fetch negative control that consumes the full flood, and verifies a small JSON happy path. No in-processReadableStreamfixture is used.Command:
node --import tsx scratchpad/discord-http-proof.mtsFocused regression test:
Command:
node scripts/run-vitest.mjs extensions/discord/src/internal/rest.test.tsSymmetry and Remaining Scope
#95108 bounded the analogous Anthropic Messages error-response read with
readResponseWithLimit/readResponseTextSnippet. This PR is the symmetric fix for the Discord REST main-response path, reusing the same@openclaw/media-corehelper through theresponse-limit-runtimeplugin entry rather than introducing a new abstraction.Sibling read check:
extensions/discord/src/api.ts:194still has an unboundedres.text()on a separate Discord API path and remains appropriate follow-up scope; this PR is scoped toRequestClient.executeRequestinextensions/discord/src/internal/rest.ts.Label: security
AI-assisted: implemented and verified with AI assistance; the proof commands above were run for real against the actual code path.