Skip to content

fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM#96031

Merged
sallyom merged 3 commits into
openclaw:mainfrom
Alix-007:fix/bound-nextcloud-talk-send
Jun 28, 2026
Merged

fix(nextcloud-talk): bound external send/reaction response reads to prevent OOM#96031
sallyom merged 3 commits into
openclaw:mainfrom
Alix-007:fix/bound-nextcloud-talk-send

Conversation

@Alix-007

@Alix-007 Alix-007 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The Nextcloud Talk channel sends to self-hosted servers, so its HTTP responses are not under our control and are not trusted to be small. Three external response bodies in send.ts were buffered with no byte cap:

  • success JSON via await response.json() (every successful send/receipt parse)
  • send error text via await response.text() (non-2xx send responses)
  • reaction error text via await response.text() (non-2xx reaction responses)

A hostile or misbehaving Nextcloud endpoint can return a response without a content-length header and stream an unbounded body. With the old code that body was fully buffered into memory, creating memory pressure / a potential hang on the plugin (and the provider/outbound) path — a small reaction error or a bogus 200 body could expand to gigabytes. This is the symmetric counterpart to the #95103 / #95108 response-limit campaign, which is hardening exactly these unbounded response.json() / response.text() reads across channels and providers.

Changes

  • extensions/nextcloud-talk/src/send.ts
    • Success receipt JSON now reads through readResponseWithLimit (cap 16 MiB, the same cap used for provider JSON in #95218), then JSON.parse on the decoded prefix. On overflow the stream is cancelled and a bounded error is thrown.
    • Both send and reaction error bodies now read through the already-public readResponseTextLimited (from openclaw/plugin-sdk/provider-http, cap 8 KiB, cancels the upstream stream once the byte budget is full), then a small local collapseErrorSnippet collapses whitespace and caps the prefix at 200 chars. This is wrapped in readNextcloudTalkErrorSnippet, which returns "" on any read failure (matching the previous .catch(() => "") semantics). No new plugin-SDK surface is added.
    • Preserved fault tolerance: the existing "message was sent but the receipt JSON could not be parsed -> keep the unknown receipt" behavior is intact. An over-limit success body now also flows through that same try/catch, so a hostile success body never throws to the caller — it just yields the unknown receipt.
  • extensions/nextcloud-talk/src/send.cfg-threading.test.ts
    • Added a bounded response reads suite driving the real exported functions against streamed ReadableStream bodies with no content-length: over-limit success JSON keeps the unknown receipt, over-limit send/reaction error bodies stay bounded (error message far below the streamed 17 MiB body), and a normal small body still parses fully.

Real behavior proof

  • Behavior addressed: Nextcloud Talk outbound send success JSON, send error text, and reaction error text are no longer read with unbounded response.json() / response.text() from a self-hosted server. Oversized no-Content-Length streams are bounded, while normal small success JSON still parses.

  • Real environment tested: Local OpenClaw checkout on Linux, running a standalone node --import tsx --input-type=module harness from the repository root. The harness starts a real local node:http server on 127.0.0.1, enables channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork, and drives the real exported sendMessageNextcloudTalk / sendReactionNextcloudTalk functions through the real fetchWithSsrFGuard path.

  • Exact steps or command run after this patch: The harness exercised four paths against live HTTP responses with no Content-Length: a 17 MiB streamed 200 success body, a normal small 200 success JSON body, a 17 MiB streamed 400 send error body, and a 17 MiB streamed 500 reaction error body.

  • Evidence after fix:

    PASS oversized-success-falls-back-unknown {"result":{"messageId":"unknown"},"stats":{"chunks":17,"bytes":17825792,"closed":true}}
    PASS small-success-json-intact {"messageId":"99","timestamp":1700000000}
    PASS oversized-send-error-snippet-bounded {"messageLength":231,"stats":{"chunks":1,"bytes":1048576,"closed":false}}
    PASS oversized-reaction-error-snippet-bounded {"messageLength":237,"stats":{"chunks":1,"bytes":1048576,"closed":false}}
    SUMMARY pass=4 fail=0 port=37819
    
  • Observed result after fix: The over-cap success body no longer throws to the caller and preserves the existing messageId="unknown" fallback; small JSON still returns messageId="99" and the timestamp; send/reaction error messages remain short (231 / 237 chars) instead of embedding streamed multi-MiB bodies.

  • What was not tested: A live production Nextcloud Talk deployment. The proof uses a local HTTP server because it must deterministically stream oversized no-Content-Length bodies; the exercised code path still uses the real exported send/reaction functions and real SSRF-guarded fetch path.

Evidence

$ node scripts/run-vitest.mjs extensions/nextcloud-talk/src/send.cfg-threading.test.ts --run
 Test Files  1 passed (1)
      Tests  passed (bounded response reads suite included)

$ node scripts/plugin-sdk-surface-report.mjs --check    # exit 0 (no new plugin-SDK surface)

$ npx oxlint extensions/nextcloud-talk/src/send.ts src/plugin-sdk/response-limit-runtime.ts
# clean (exit 0)

Label: security


AI-assisted. This is the symmetric continuation of the #95103 / #95108 response-limit campaign, applying the same @openclaw/media-core readResponseWithLimit hardening to the Nextcloud Talk success JSON, and bounding the send/reaction error bodies through the already-public readResponseTextLimited plus a local snippet collapse (no new plugin-SDK surface).

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added channel: nextcloud-talk Channel integration: nextcloud-talk size: S labels Jun 23, 2026
@Alix-007

Copy link
Copy Markdown
Contributor Author

@clawsweeper review

@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Alix-007 added 2 commits June 23, 2026 21:10
…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).
…mited (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.
@Alix-007
Alix-007 force-pushed the fix/bound-nextcloud-talk-send branch from d9fca1c to f9ec561 Compare June 23, 2026 13:10
@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 6:37 AM ET / 10:37 UTC.

Summary
The PR bounds Nextcloud Talk send success JSON and send/reaction error response-body reads and adds streamed no-content-length regression coverage.

PR surface: Source +37, Tests +126. Total +163 across 2 files.

Reproducibility: yes. Current main is source-reproducible because the Nextcloud Talk send path still buffers three untrusted external response bodies with response.text()/response.json(), and the PR body supplies after-fix terminal proof for the bounded path.

Review metrics: 1 noteworthy metric.

  • External response reads: 3 bounded. The diff covers the success receipt JSON reader plus send and reaction error-body readers without adding config or SDK surface.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • No ClawSweeper repair lane is needed because there are no actionable review findings; the remaining work is ordinary maintainer review and merge-gate handling.

Security
Cleared: The diff reduces untrusted HTTP response-body buffering and does not change dependencies, workflows, lockfiles, secrets handling, permissions, or package resolution.

Review details

Best possible solution:

Merge this focused bounded-read fix after ordinary maintainer and CI gates, and track any broader response-read audit separately.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main is source-reproducible because the Nextcloud Talk send path still buffers three untrusted external response bodies with response.text()/response.json(), and the PR body supplies after-fix terminal proof for the bounded path.

Is this the best way to solve the issue?

Yes. The patch is the narrow maintainable fix for this PR scope: it changes the owning Nextcloud Talk send module, uses existing public SDK helpers, preserves the sent-but-unparseable receipt fallback, and avoids new config or SDK surface.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against bfffc77bfc86.

Label changes

Label justifications:

  • P2: This is normal-priority security and availability hardening for one channel's outbound HTTP response handling with limited blast radius.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal proof from a local HTTP-server harness exercising the real exported send/reaction functions through the SSRF-guarded fetch path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal proof from a local HTTP-server harness exercising the real exported send/reaction functions through the SSRF-guarded fetch path.
Evidence reviewed

PR surface:

Source +37, Tests +126. Total +163 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 41 4 +37
Tests 1 126 0 +126
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 167 4 +163

What I checked:

Likely related people:

  • vincentkoc: git blame attributes the current send/reaction implementation to 54c0048, and related merged response-limit work landed in b073d7c and d6cefe2. (role: current send path introducer and adjacent response-limit contributor; confidence: high; commits: 54c0048d6c9c, b073d7cc11dc, d6cefe26f499; files: extensions/nextcloud-talk/src/send.ts, src/gateway/model-pricing-cache.ts, src/agents/anthropic-transport-stream.ts)
  • Alix-007: Alix-007 authored the recent merged provider JSON response-limit PR that used the same helper family now applied to Nextcloud Talk success JSON. (role: adjacent response-limit contributor; confidence: medium; commits: 2592f8a51a4e; files: src/agents/provider-http-errors.ts, src/agents/provider-http-errors.test.ts)
  • dongdong: Recent current-main work touched neighboring Nextcloud Talk monitor behavior, making dongdong a plausible plugin-area context owner though not the send path owner. (role: recent adjacent Nextcloud Talk contributor; confidence: low; commits: 7b9f4aefa281; files: extensions/nextcloud-talk/src/monitor.ts, extensions/nextcloud-talk/src/monitor.replay.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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 keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jun 23, 2026
@Alix-007

Copy link
Copy Markdown
Contributor Author

Updated the PR body with standalone runtime proof using the exact ## Real behavior proof fields. The proof starts a real local HTTP server, runs the real exported Nextcloud Talk send/reaction functions through the real SSRF-guarded fetch path, and shows oversized no-Content-Length success/error/reaction bodies are bounded while small success JSON still parses.\n\n@clawsweeper re-review

@Alix-007

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 24, 2026
…uccess 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).
@sallyom sallyom self-assigned this Jun 28, 2026
@sallyom

sallyom commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Merge-ready for 312700b.

Local and ClawSweeper review clean, CI green, best narrow fix, and follows the established provider-response-read pattern. The change keeps the Nextcloud Talk external send/reaction response handling scoped to successful REST reads and adds the focused oversized-response cancellation coverage needed for the OOM guardrail.

@sallyom
sallyom merged commit d577cb2 into openclaw:main Jun 28, 2026
89 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 29, 2026
…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).
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…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).
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…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).
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 2, 2026
…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)
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…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).
wheakerd pushed a commit to wheakerd/clawdbot that referenced this pull request Jul 15, 2026
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: nextcloud-talk Channel integration: nextcloud-talk P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants