fix(gateway): bound pricing catalog streams#95103
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 19, 2026, 5:41 PM ET / 21:41 UTC. Summary PR surface: Source 0, Tests +65. Total +65 across 2 files. Reproducibility: yes. from source inspection, though I did not execute a failing local repro: current main reads pricing bodies with Review metrics: none identified. Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest possible solution: Land the focused stream-cap fix after normal maintainer review and required checks, keeping the shared response-limit helper as the single bounded-read path. Do we have a high-confidence way to reproduce the issue? Yes from source inspection, though I did not execute a failing local repro: current main reads pricing bodies with Is this the best way to solve the issue? Yes; replacing the local full-body read with the existing shared bounded response helper is the narrowest maintainable fix, and it preserves declared Content-Length rejection, malformed JSON handling, and source-failure fallback behavior. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against d97574aae6f2. Label changesLabel justifications:
Evidence reviewedPR surface: Source 0, Tests +65. Total +65 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
|
|
Maintainer proof for 9c7a05c:
Local focused Vitest was attempted with |
The runtime OpenRouter model-capability detector fetched the full /models catalog with an unbounded `await response.json()`, so a compromised or misbehaving endpoint could stream an arbitrarily large body and force the process to buffer the whole payload before parsing. Read the body through the shared bounded reader instead, capping it at 16 MiB (matching the sibling pricing-cache endpoint hardened in openclaw#95103) and cancelling the stream on overflow. This mirrors the symmetric bound-stream fixes in openclaw#95103 and openclaw#95108. Adds coverage that an oversized streamed catalog is cancelled instead of buffered and that an under-cap chunked body still reassembles, parses, and round-trips through the SQLite cache on a fresh import.
The OpenRouter /models catalog read in fetchOpenRouterModels hardened only the error/early-return path (dbd5689 cancels the body when res.bodyUsed is false), but the success branch still buffered the whole body with an unbounded `await res.json()`. The response is a provider-controlled, runtime-fetched body, so a faulty or hostile provider can stream an effectively unbounded JSON document and exhaust process memory before the parse completes; the finally-cancel is a no-op once .json() has drained. Read the success body through the canonical byte-cap reader (readResponseWithLimit) under a 4 MiB ceiling before JSON.parse, cancelling the stream on overflow and bounding idle stalls with the call's existing timeout. This is the symmetric success-path counterpart to the bounded-stream hardening landed in openclaw#95103 (pricing catalog) and openclaw#95108 (Anthropic error streams), reusing the same helper rather than a new abstraction.
* fix(agents): bound OpenRouter model catalog response reads The runtime OpenRouter model-capability detector fetched the full /models catalog with an unbounded `await response.json()`, so a compromised or misbehaving endpoint could stream an arbitrarily large body and force the process to buffer the whole payload before parsing. Read the body through the shared bounded reader instead, capping it at 16 MiB (matching the sibling pricing-cache endpoint hardened in #95103) and cancelling the stream on overflow. This mirrors the symmetric bound-stream fixes in #95103 and #95108. Adds coverage that an oversized streamed catalog is cancelled instead of buffered and that an under-cap chunked body still reassembles, parses, and round-trips through the SQLite cache on a fresh import. * fix(agents): avoid OpenRouter refetch after capped catalog miss --------- Co-authored-by: sallyom <[email protected]>
The OpenRouter /models catalog read in fetchOpenRouterModels hardened only the error/early-return path (dbd5689 cancels the body when res.bodyUsed is false), but the success branch still buffered the whole body with an unbounded `await res.json()`. The response is a provider-controlled, runtime-fetched body, so a faulty or hostile provider can stream an effectively unbounded JSON document and exhaust process memory before the parse completes; the finally-cancel is a no-op once .json() has drained. Read the success body through the canonical byte-cap reader (readResponseWithLimit) under a 4 MiB ceiling before JSON.parse, cancelling the stream on overflow and bounding idle stalls with the call's existing timeout. This is the symmetric success-path counterpart to the bounded-stream hardening landed in #95103 (pricing catalog) and #95108 (Anthropic error streams), reusing the same helper rather than a new abstraction.
…95420) * fix(agents): bound OpenRouter model catalog response reads The runtime OpenRouter model-capability detector fetched the full /models catalog with an unbounded `await response.json()`, so a compromised or misbehaving endpoint could stream an arbitrarily large body and force the process to buffer the whole payload before parsing. Read the body through the shared bounded reader instead, capping it at 16 MiB (matching the sibling pricing-cache endpoint hardened in openclaw#95103) and cancelling the stream on overflow. This mirrors the symmetric bound-stream fixes in openclaw#95103 and openclaw#95108. Adds coverage that an oversized streamed catalog is cancelled instead of buffered and that an under-cap chunked body still reassembles, parses, and round-trips through the SQLite cache on a fresh import. * fix(agents): avoid OpenRouter refetch after capped catalog miss --------- Co-authored-by: sallyom <[email protected]>
…aw#95418) The OpenRouter /models catalog read in fetchOpenRouterModels hardened only the error/early-return path (3efa98b cancels the body when res.bodyUsed is false), but the success branch still buffered the whole body with an unbounded `await res.json()`. The response is a provider-controlled, runtime-fetched body, so a faulty or hostile provider can stream an effectively unbounded JSON document and exhaust process memory before the parse completes; the finally-cancel is a no-op once .json() has drained. Read the success body through the canonical byte-cap reader (readResponseWithLimit) under a 4 MiB ceiling before JSON.parse, cancelling the stream on overflow and bounding idle stalls with the call's existing timeout. This is the symmetric success-path counterpart to the bounded-stream hardening landed in openclaw#95103 (pricing catalog) and openclaw#95108 (Anthropic error streams), reusing the same helper rather than a new abstraction.
…revent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers).
The /api/tags and /api/show discovery reads in extensions/ollama/src/provider-models.ts
parsed their HTTP responses with an unbounded await response.json(). Ollama base URLs
are user-supplied and can point at remote/cloud endpoints, so a hostile or buggy server
(or one reachable via SSRF) could stream an unbounded or never-ending JSON body and drive
model discovery into OOM.
Route both reads through the shared @openclaw/media-core byte-bounded reader
(readResponseWithLimit, re-exported via openclaw/plugin-sdk/response-limit-runtime) under
a single 16 MiB cap before JSON.parse, cancelling the stream on overflow. Overflow throws a
bounded error that the existing fail-soft handlers swallow, so a capped endpoint degrades
gracefully: /api/tags returns { reachable: false, models: [] } and /api/show returns {}.
Symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign.
AI-assisted.
* fix(parallel): bound successful web-search JSON response reads The Parallel web_search provider parsed its /v1/search success body with an unbounded await res.json(). The body comes from an external web-search upstream, so a hostile or malfunctioning endpoint streaming an unbounded JSON payload could force the runtime to buffer the whole response before parsing, creating memory pressure or a hang on the provider path. Read the success body through the shared readProviderJsonResponse helper with a 16 MiB cap (matching the provider JSON cap from #95218); on overflow the stream is cancelled and a bounded error is thrown. The error-body path was already bounded (readResponseTextLimited, 8 KiB). Symmetric follow-up to the #95103/#95108 response-limit campaign. * docs(parallel): drop upstream PR ref from response-cap comment Replace the PR-specific '#95218' annotation with a neutral description of the shared provider JSON cap so the comment stays accurate independent of upstream PR numbering.
Exa search success responses were read via an unbounded `await response.json()`, so a misbehaving or hostile endpoint could stream an arbitrarily large body into memory before parsing. Read the success body through the shared bounded reader (16 MiB cap, the same limit other bundled providers use) and cancel the stream on overflow. This mirrors the error-body bound already in place and the #95103/#95108 response -limit campaign on the success-JSON side. AI-assisted.
* fix(ollama): bound model-discovery JSON response reads
The /api/tags and /api/show discovery reads in extensions/ollama/src/provider-models.ts
parsed their HTTP responses with an unbounded await response.json(). Ollama base URLs
are user-supplied and can point at remote/cloud endpoints, so a hostile or buggy server
(or one reachable via SSRF) could stream an unbounded or never-ending JSON body and drive
model discovery into OOM.
Route both reads through the shared @openclaw/media-core byte-bounded reader
(readResponseWithLimit, re-exported via openclaw/plugin-sdk/response-limit-runtime) under
a single 16 MiB cap before JSON.parse, cancelling the stream on overflow. Overflow throws a
bounded error that the existing fail-soft handlers swallow, so a capped endpoint degrades
gracefully: /api/tags returns { reachable: false, models: [] } and /api/show returns {}.
Symmetric counterpart to the #95103/#95108 response-limit campaign.
AI-assisted.
* fix(ollama): reuse shared bounded JSON reader for model discovery
Replace the local readOllamaDiscoveryJson helper with the shared
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http), which
already enforces the 16 MiB cap, cancels the stream on overflow, and wraps
malformed JSON with the caller label. The /api/tags and /api/show discovery
reads now go through it directly while keeping the existing fail-soft
handlers ({ reachable: false, models: [] } and {}).
Add a focused regression test: when a discovery stream exceeds the JSON byte
cap, fetchOllamaModels returns { reachable: false, models: [] },
queryOllamaModelShowInfo returns {}, and the bounded reader cancels the body
mid-flight so less than the full advertised stream is read.
…law#96035) * fix(parallel): bound successful web-search JSON response reads The Parallel web_search provider parsed its /v1/search success body with an unbounded await res.json(). The body comes from an external web-search upstream, so a hostile or malfunctioning endpoint streaming an unbounded JSON payload could force the runtime to buffer the whole response before parsing, creating memory pressure or a hang on the provider path. Read the success body through the shared readProviderJsonResponse helper with a 16 MiB cap (matching the provider JSON cap from openclaw#95218); on overflow the stream is cancelled and a bounded error is thrown. The error-body path was already bounded (readResponseTextLimited, 8 KiB). Symmetric follow-up to the openclaw#95103/openclaw#95108 response-limit campaign. * docs(parallel): drop upstream PR ref from response-cap comment Replace the PR-specific 'openclaw#95218' annotation with a neutral description of the shared provider JSON cap so the comment stays accurate independent of upstream PR numbering.
Exa search success responses were read via an unbounded `await response.json()`, so a misbehaving or hostile endpoint could stream an arbitrarily large body into memory before parsing. Read the success body through the shared bounded reader (16 MiB cap, the same limit other bundled providers use) and cancel the stream on overflow. This mirrors the error-body bound already in place and the openclaw#95103/openclaw#95108 response -limit campaign on the success-JSON side. AI-assisted.
* fix(ollama): bound model-discovery JSON response reads
The /api/tags and /api/show discovery reads in extensions/ollama/src/provider-models.ts
parsed their HTTP responses with an unbounded await response.json(). Ollama base URLs
are user-supplied and can point at remote/cloud endpoints, so a hostile or buggy server
(or one reachable via SSRF) could stream an unbounded or never-ending JSON body and drive
model discovery into OOM.
Route both reads through the shared @openclaw/media-core byte-bounded reader
(readResponseWithLimit, re-exported via openclaw/plugin-sdk/response-limit-runtime) under
a single 16 MiB cap before JSON.parse, cancelling the stream on overflow. Overflow throws a
bounded error that the existing fail-soft handlers swallow, so a capped endpoint degrades
gracefully: /api/tags returns { reachable: false, models: [] } and /api/show returns {}.
Symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign.
AI-assisted.
* fix(ollama): reuse shared bounded JSON reader for model discovery
Replace the local readOllamaDiscoveryJson helper with the shared
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http), which
already enforces the 16 MiB cap, cancels the stream on overflow, and wraps
malformed JSON with the caller label. The /api/tags and /api/show discovery
reads now go through it directly while keeping the existing fail-soft
handlers ({ reachable: false, models: [] } and {}).
Add a focused regression test: when a discovery stream exceeds the JSON byte
cap, fetchOllamaModels returns { reachable: false, models: [] },
queryOllamaModelShowInfo returns {}, and the bounded reader cancels the body
mid-flight so less than the full advertised stream is read.
…revent OOM (#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the #95103/#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON).
* fix(mattermost): bound successful REST JSON/text response reads The Mattermost REST client already bounds error bodies (readResponseTextLimited) and streams guarded responses without buffering, but the success path still called `await res.json()` / `await res.text()`, reading the whole body into memory before parsing. A self-hosted or compromised Mattermost server can return an arbitrarily large (or never-terminating, content-length-less) JSON/text body and force the plugin to buffer it unbounded. Read successful JSON through the shared readProviderJsonResponse (16 MiB cap, cancels the stream and throws a bounded error on overflow, same as the provider HTTP path) and cap non-JSON success bodies with readResponseTextLimited. uploadMattermostFile's file-info JSON is bounded the same way. Symmetric follow-up to the #95103 / #95108 response-limit campaign. AI-assisted. * fix(mattermost): bound probe success JSON reads * fix(mattermost): reject oversized success text bodies
…revent OOM (openclaw#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON).
…claw#96033) * fix(mattermost): bound successful REST JSON/text response reads The Mattermost REST client already bounds error bodies (readResponseTextLimited) and streams guarded responses without buffering, but the success path still called `await res.json()` / `await res.text()`, reading the whole body into memory before parsing. A self-hosted or compromised Mattermost server can return an arbitrarily large (or never-terminating, content-length-less) JSON/text body and force the plugin to buffer it unbounded. Read successful JSON through the shared readProviderJsonResponse (16 MiB cap, cancels the stream and throws a bounded error on overflow, same as the provider HTTP path) and cap non-JSON success bodies with readResponseTextLimited. uploadMattermostFile's file-info JSON is bounded the same way. Symmetric follow-up to the openclaw#95103 / openclaw#95108 response-limit campaign. AI-assisted. * fix(mattermost): bound probe success JSON reads * fix(mattermost): reject oversized success text bodies
…law#96035) * fix(parallel): bound successful web-search JSON response reads The Parallel web_search provider parsed its /v1/search success body with an unbounded await res.json(). The body comes from an external web-search upstream, so a hostile or malfunctioning endpoint streaming an unbounded JSON payload could force the runtime to buffer the whole response before parsing, creating memory pressure or a hang on the provider path. Read the success body through the shared readProviderJsonResponse helper with a 16 MiB cap (matching the provider JSON cap from openclaw#95218); on overflow the stream is cancelled and a bounded error is thrown. The error-body path was already bounded (readResponseTextLimited, 8 KiB). Symmetric follow-up to the openclaw#95103/openclaw#95108 response-limit campaign. * docs(parallel): drop upstream PR ref from response-cap comment Replace the PR-specific 'openclaw#95218' annotation with a neutral description of the shared provider JSON cap so the comment stays accurate independent of upstream PR numbering.
Exa search success responses were read via an unbounded `await response.json()`, so a misbehaving or hostile endpoint could stream an arbitrarily large body into memory before parsing. Read the success body through the shared bounded reader (16 MiB cap, the same limit other bundled providers use) and cancel the stream on overflow. This mirrors the error-body bound already in place and the openclaw#95103/openclaw#95108 response -limit campaign on the success-JSON side. AI-assisted.
* fix(ollama): bound model-discovery JSON response reads
The /api/tags and /api/show discovery reads in extensions/ollama/src/provider-models.ts
parsed their HTTP responses with an unbounded await response.json(). Ollama base URLs
are user-supplied and can point at remote/cloud endpoints, so a hostile or buggy server
(or one reachable via SSRF) could stream an unbounded or never-ending JSON body and drive
model discovery into OOM.
Route both reads through the shared @openclaw/media-core byte-bounded reader
(readResponseWithLimit, re-exported via openclaw/plugin-sdk/response-limit-runtime) under
a single 16 MiB cap before JSON.parse, cancelling the stream on overflow. Overflow throws a
bounded error that the existing fail-soft handlers swallow, so a capped endpoint degrades
gracefully: /api/tags returns { reachable: false, models: [] } and /api/show returns {}.
Symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign.
AI-assisted.
* fix(ollama): reuse shared bounded JSON reader for model discovery
Replace the local readOllamaDiscoveryJson helper with the shared
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http), which
already enforces the 16 MiB cap, cancels the stream on overflow, and wraps
malformed JSON with the caller label. The /api/tags and /api/show discovery
reads now go through it directly while keeping the existing fail-soft
handlers ({ reachable: false, models: [] } and {}).
Add a focused regression test: when a discovery stream exceeds the JSON byte
cap, fetchOllamaModels returns { reachable: false, models: [] },
queryOllamaModelShowInfo returns {}, and the bounded reader cancels the body
mid-flight so less than the full advertised stream is read.
…revent OOM (openclaw#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON).
…claw#96033) * fix(mattermost): bound successful REST JSON/text response reads The Mattermost REST client already bounds error bodies (readResponseTextLimited) and streams guarded responses without buffering, but the success path still called `await res.json()` / `await res.text()`, reading the whole body into memory before parsing. A self-hosted or compromised Mattermost server can return an arbitrarily large (or never-terminating, content-length-less) JSON/text body and force the plugin to buffer it unbounded. Read successful JSON through the shared readProviderJsonResponse (16 MiB cap, cancels the stream and throws a bounded error on overflow, same as the provider HTTP path) and cap non-JSON success bodies with readResponseTextLimited. uploadMattermostFile's file-info JSON is bounded the same way. Symmetric follow-up to the openclaw#95103 / openclaw#95108 response-limit campaign. AI-assisted. * fix(mattermost): bound probe success JSON reads * fix(mattermost): reject oversized success text bodies
…revent OOM (openclaw#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON).
…claw#96033) * fix(mattermost): bound successful REST JSON/text response reads The Mattermost REST client already bounds error bodies (readResponseTextLimited) and streams guarded responses without buffering, but the success path still called `await res.json()` / `await res.text()`, reading the whole body into memory before parsing. A self-hosted or compromised Mattermost server can return an arbitrarily large (or never-terminating, content-length-less) JSON/text body and force the plugin to buffer it unbounded. Read successful JSON through the shared readProviderJsonResponse (16 MiB cap, cancels the stream and throws a bounded error on overflow, same as the provider HTTP path) and cap non-JSON success bodies with readResponseTextLimited. uploadMattermostFile's file-info JSON is bounded the same way. Symmetric follow-up to the openclaw#95103 / openclaw#95108 response-limit campaign. AI-assisted. * fix(mattermost): bound probe success JSON reads * fix(mattermost): reject oversized success text bodies
…revent OOM (openclaw#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON). (cherry picked from commit d577cb2)
…claw#96033) * fix(mattermost): bound successful REST JSON/text response reads The Mattermost REST client already bounds error bodies (readResponseTextLimited) and streams guarded responses without buffering, but the success path still called `await res.json()` / `await res.text()`, reading the whole body into memory before parsing. A self-hosted or compromised Mattermost server can return an arbitrarily large (or never-terminating, content-length-less) JSON/text body and force the plugin to buffer it unbounded. Read successful JSON through the shared readProviderJsonResponse (16 MiB cap, cancels the stream and throws a bounded error on overflow, same as the provider HTTP path) and cap non-JSON success bodies with readResponseTextLimited. uploadMattermostFile's file-info JSON is bounded the same way. Symmetric follow-up to the openclaw#95103 / openclaw#95108 response-limit campaign. AI-assisted. * fix(mattermost): bound probe success JSON reads * fix(mattermost): reject oversized success text bodies (cherry picked from commit 9241b97)
…revent OOM (openclaw#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON).
…claw#96033) * fix(mattermost): bound successful REST JSON/text response reads The Mattermost REST client already bounds error bodies (readResponseTextLimited) and streams guarded responses without buffering, but the success path still called `await res.json()` / `await res.text()`, reading the whole body into memory before parsing. A self-hosted or compromised Mattermost server can return an arbitrarily large (or never-terminating, content-length-less) JSON/text body and force the plugin to buffer it unbounded. Read successful JSON through the shared readProviderJsonResponse (16 MiB cap, cancels the stream and throws a bounded error on overflow, same as the provider HTTP path) and cap non-JSON success bodies with readResponseTextLimited. uploadMattermostFile's file-info JSON is bounded the same way. Symmetric follow-up to the openclaw#95103 / openclaw#95108 response-limit campaign. AI-assisted. * fix(mattermost): bound probe success JSON reads * fix(mattermost): reject oversized success text bodies
…revent OOM (openclaw#96031) * fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM Nextcloud Talk talks to self-hosted servers whose HTTP responses are not trusted to be small. The send and reaction paths buffered three external bodies without any byte cap: - success JSON via await response.json() - send error text via await response.text() - reaction error text via await response.text() A hostile or misbehaving Nextcloud endpoint could stream an unbounded body (no content-length) into memory, pressuring or hanging the plugin/provider path. Cap success JSON at 16 MiB via readResponseWithLimit and collapse error bodies to an 8 KiB readResponseTextSnippet, cancelling the stream on overflow. The 'message sent but receipt JSON unreadable -> unknown' fallback is preserved (an over-limit body now also routes through the existing catch). This is the symmetric counterpart to the openclaw#95103/openclaw#95108 response-limit campaign, reusing the shared @openclaw/media-core helpers (newly re-exported from plugin-sdk/response-limit-runtime for plugin consumers). * fix(nextcloud-talk): bound error bodies via public readResponseTextLimited (no new plugin-SDK surface) Re-exporting readResponseTextSnippet from plugin-sdk/response-limit-runtime pushed the public plugin-SDK export count past its surface budget, failing plugin-sdk-surface-report.test.ts. Drop that re-export and instead bound the Nextcloud Talk send/reaction error bodies through the already-public readResponseTextLimited (openclaw/plugin-sdk/provider-http), collapsing the bounded 8 KiB prefix to a short, log-safe snippet locally. Behavior is unchanged for callers; no new plugin-SDK surface is introduced. Success JSON still reads through readResponseWithLimit (16 MiB cap). The committed bounded-response-reads Vitest suite continues to prove the caps hold against 17 MiB streamed bodies with no content-length. * fix(nextcloud-talk): reuse shared readProviderJsonResponse for send success JSON The send success receipt parsed JSON by hand via readResponseWithLimit + a local NEXTCLOUD_TALK_JSON_MAX_BYTES cap + JSON.parse(TextDecoder.decode(...)), duplicating the shared provider-http helper that the sibling room-info.ts and bot-preflight.ts already use. extensions/AGENTS.md forbids re-implementing shared helpers locally. Swap the hand-rolled block for the one-stop readProviderJsonResponse<{ ocs?: ... }>(response, "Nextcloud Talk send"), which reads through the same bounded reader and throws on overflow/malformed JSON, so the outer try/catch still keeps the "unknown" receipt and behavior is equivalent. The error path keeps readResponseTextLimited (text, not JSON). (cherry picked from commit d577cb2)
Summary
Verification
Proof gaps