fix(signal): guard containerRestRequest JSON.parse against malformed responses#98073
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 1:41 PM ET / 17:41 UTC. Summary PR surface: Source +4, Tests 0. Total +4 across 2 files. Reproducibility: yes. Source inspection shows current main and v2026.6.11 pass non-empty Signal REST success text to bare Review metrics: none identified. Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. 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 narrow Signal container REST parse normalization after ordinary maintainer review, keeping the fix local to Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main and v2026.6.11 pass non-empty Signal REST success text to bare Is this the best way to solve the issue? Yes. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5c4e478df4ef. Label changesLabel justifications:
Evidence reviewedPR surface: Source +4, Tests 0. Total +4 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
|
1631545 to
bc3f4a2
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
bc3f4a2 to
066c2dd
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
50ed313 to
066c2dd
Compare
|
@clawsweeper re-review |
1 similar comment
|
@clawsweeper re-review |
b8cfbf8 to
fbfd2aa
Compare
|
Merged via squash.
|
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 / telegram-ingress openclaw#98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]>
…responses (openclaw#98073) * fix(signal): guard containerRestRequest JSON.parse against malformed responses Wrap JSON.parse(text) in containerRestRequest with try/catch to prevent a malformed Signal REST container response from throwing an unhandled SyntaxError. On parse failure, throw a descriptive Error. The success body is already bounded by readProviderTextResponse (16 MiB cap, D1 protection). Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * fix(signal): add real HTTP server proof for malformed JSON guard Starts a local node:http server returning malformed JSON, then calls containerRestRequest against it. This exercises the actual changed try/catch code path through the real fetch stack (no mock override). Proof output: PASS malformed JSON: throws Error :: type=Error PASS malformed JSON: message describes malformed JSON PASS malformed JSON: NOT raw SyntaxError Signed-off-by: lsr911 <[email protected]> Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * test(signal): remove committed proof script --------- Signed-off-by: lsr911 <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 / telegram-ingress openclaw#98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]>
…responses (openclaw#98073) * fix(signal): guard containerRestRequest JSON.parse against malformed responses Wrap JSON.parse(text) in containerRestRequest with try/catch to prevent a malformed Signal REST container response from throwing an unhandled SyntaxError. On parse failure, throw a descriptive Error. The success body is already bounded by readProviderTextResponse (16 MiB cap, D1 protection). Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * fix(signal): add real HTTP server proof for malformed JSON guard Starts a local node:http server returning malformed JSON, then calls containerRestRequest against it. This exercises the actual changed try/catch code path through the real fetch stack (no mock override). Proof output: PASS malformed JSON: throws Error :: type=Error PASS malformed JSON: message describes malformed JSON PASS malformed JSON: NOT raw SyntaxError Signed-off-by: lsr911 <[email protected]> Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * test(signal): remove committed proof script --------- Signed-off-by: lsr911 <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…responses (openclaw#98073) * fix(signal): guard containerRestRequest JSON.parse against malformed responses Wrap JSON.parse(text) in containerRestRequest with try/catch to prevent a malformed Signal REST container response from throwing an unhandled SyntaxError. On parse failure, throw a descriptive Error. The success body is already bounded by readProviderTextResponse (16 MiB cap, D1 protection). Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * fix(signal): add real HTTP server proof for malformed JSON guard Starts a local node:http server returning malformed JSON, then calls containerRestRequest against it. This exercises the actual changed try/catch code path through the real fetch stack (no mock override). Proof output: PASS malformed JSON: throws Error :: type=Error PASS malformed JSON: message describes malformed JSON PASS malformed JSON: NOT raw SyntaxError Signed-off-by: lsr911 <[email protected]> Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * test(signal): remove committed proof script --------- Signed-off-by: lsr911 <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…API bodies fetchJson in telegram-ingress-worker.runtime.ts parsed the Bot API response body with JSON.parse but did not catch SyntaxError. A non-JSON body (HTML error page, empty, or truncated from a CDN, reverse proxy, or TLS MITM) made the SyntaxError bubble to the long-poll loop, where isRecoverableTelegramNetworkError judged it non-recoverable (SyntaxError is not in the recoverable code/name/message sets), so the loop re-threw and the isolated ingress worker exited — silently dropping the Telegram channel until manual restart. Wrap JSON.parse in try/catch and throw a descriptive Error carrying statusCode (matching the matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 pattern). Add "malformed json" to RECOVERABLE_MESSAGE_SNIPPETS so the polling/webhook contexts back off and retry instead of exiting; send context stays conservative (allowMessageMatch defaults false) to avoid duplicate-message retries. Co-Authored-By: Claude <[email protected]>
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 / telegram-ingress openclaw#98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]>
…API bodies fetchJson in telegram-ingress-worker.runtime.ts parsed the Bot API response body with JSON.parse but did not catch SyntaxError. A non-JSON body (HTML error page, empty, or truncated from a CDN, reverse proxy, or TLS MITM) made the SyntaxError bubble to the long-poll loop, where isRecoverableTelegramNetworkError judged it non-recoverable (SyntaxError is not in the recoverable code/name/message sets), so the loop re-threw and the isolated ingress worker exited — silently dropping the Telegram channel until manual restart. Wrap JSON.parse in try/catch and throw a descriptive Error carrying statusCode (matching the matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 pattern). Add "malformed json" to RECOVERABLE_MESSAGE_SNIPPETS so the polling/webhook contexts back off and retry instead of exiting; send context stays conservative (allowMessageMatch defaults false) to avoid duplicate-message retries. Co-Authored-By: Claude <[email protected]>
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 / telegram-ingress openclaw#98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]>
…responses (openclaw#98073) * fix(signal): guard containerRestRequest JSON.parse against malformed responses Wrap JSON.parse(text) in containerRestRequest with try/catch to prevent a malformed Signal REST container response from throwing an unhandled SyntaxError. On parse failure, throw a descriptive Error. The success body is already bounded by readProviderTextResponse (16 MiB cap, D1 protection). Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * fix(signal): add real HTTP server proof for malformed JSON guard Starts a local node:http server returning malformed JSON, then calls containerRestRequest against it. This exercises the actual changed try/catch code path through the real fetch stack (no mock override). Proof output: PASS malformed JSON: throws Error :: type=Error PASS malformed JSON: message describes malformed JSON PASS malformed JSON: NOT raw SyntaxError Signed-off-by: lsr911 <[email protected]> Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * test(signal): remove committed proof script --------- Signed-off-by: lsr911 <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…ut (#98381) * fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix #97973 / sms #97999 / signal #98073 / telegram-ingress #98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]> * fix(memory-core): preserve cause in qmd mcporter JSON.parse guard Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error lint rule; the original SyntaxError is now chained, improving diagnosability. Co-Authored-By: Claude <[email protected]> * fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary) ClawSweeper flagged that the previous error message exposed raw mcporter stdout (first 200 chars) before session visibility filtering, which could leak sensitive content. Drop the stdout preview from the thrown message; keep the original SyntaxError as `cause` for diagnostics so the parse-failure reason is still reachable without surfacing unfiltered subprocess output. Co-Authored-By: Claude <[email protected]> * fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak) The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its message (e.g. Unexpected token '<', then the raw bytes). Including that SyntaxError message in the thrown Error would surface unfiltered mcporter stdout before session visibility filtering. Drop the parse-error message from the thrown Error; keep the original SyntaxError as cause for developer diagnostics only. Co-Authored-By: Claude <[email protected]> * fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage) formatErrorMessage walks the .cause chain into the user-visible path. Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw stdout snippet through formatErrorMessage even with a generic message. Give the cause a generic message too; the raw snippet no longer reaches the user. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
…ut (openclaw#98381) * fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 / telegram-ingress openclaw#98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]> * fix(memory-core): preserve cause in qmd mcporter JSON.parse guard Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error lint rule; the original SyntaxError is now chained, improving diagnosability. Co-Authored-By: Claude <[email protected]> * fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary) ClawSweeper flagged that the previous error message exposed raw mcporter stdout (first 200 chars) before session visibility filtering, which could leak sensitive content. Drop the stdout preview from the thrown message; keep the original SyntaxError as `cause` for diagnostics so the parse-failure reason is still reachable without surfacing unfiltered subprocess output. Co-Authored-By: Claude <[email protected]> * fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak) The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its message (e.g. Unexpected token '<', then the raw bytes). Including that SyntaxError message in the thrown Error would surface unfiltered mcporter stdout before session visibility filtering. Drop the parse-error message from the thrown Error; keep the original SyntaxError as cause for developer diagnostics only. Co-Authored-By: Claude <[email protected]> * fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage) formatErrorMessage walks the .cause chain into the user-visible path. Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw stdout snippet through formatErrorMessage even with a generic message. Give the cause a generic message too; the raw snippet no longer reaches the user. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
…responses (openclaw#98073) * fix(signal): guard containerRestRequest JSON.parse against malformed responses Wrap JSON.parse(text) in containerRestRequest with try/catch to prevent a malformed Signal REST container response from throwing an unhandled SyntaxError. On parse failure, throw a descriptive Error. The success body is already bounded by readProviderTextResponse (16 MiB cap, D1 protection). Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * fix(signal): add real HTTP server proof for malformed JSON guard Starts a local node:http server returning malformed JSON, then calls containerRestRequest against it. This exercises the actual changed try/catch code path through the real fetch stack (no mock override). Proof output: PASS malformed JSON: throws Error :: type=Error PASS malformed JSON: message describes malformed JSON PASS malformed JSON: NOT raw SyntaxError Signed-off-by: lsr911 <[email protected]> Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * test(signal): remove committed proof script --------- Signed-off-by: lsr911 <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 7d98ad2)
…API bodies fetchJson in telegram-ingress-worker.runtime.ts parsed the Bot API response body with JSON.parse but did not catch SyntaxError. A non-JSON body (HTML error page, empty, or truncated from a CDN, reverse proxy, or TLS MITM) made the SyntaxError bubble to the long-poll loop, where isRecoverableTelegramNetworkError judged it non-recoverable (SyntaxError is not in the recoverable code/name/message sets), so the loop re-threw and the isolated ingress worker exited — silently dropping the Telegram channel until manual restart. Wrap JSON.parse in try/catch and throw a descriptive Error carrying statusCode (matching the matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 pattern). Add "malformed json" to RECOVERABLE_MESSAGE_SNIPPETS so the polling/webhook contexts back off and retry instead of exiting; send context stays conservative (allowMessageMatch defaults false) to avoid duplicate-message retries. Co-Authored-By: Claude <[email protected]>
…responses (openclaw#98073) * fix(signal): guard containerRestRequest JSON.parse against malformed responses Wrap JSON.parse(text) in containerRestRequest with try/catch to prevent a malformed Signal REST container response from throwing an unhandled SyntaxError. On parse failure, throw a descriptive Error. The success body is already bounded by readProviderTextResponse (16 MiB cap, D1 protection). Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * fix(signal): add real HTTP server proof for malformed JSON guard Starts a local node:http server returning malformed JSON, then calls containerRestRequest against it. This exercises the actual changed try/catch code path through the real fetch stack (no mock override). Proof output: PASS malformed JSON: throws Error :: type=Error PASS malformed JSON: message describes malformed JSON PASS malformed JSON: NOT raw SyntaxError Signed-off-by: lsr911 <[email protected]> Co-Authored-By: Claude <[email protected]> Signed-off-by: lsr911 <[email protected]> * test(signal): remove committed proof script --------- Signed-off-by: lsr911 <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 7d98ad2)
…ut (openclaw#98381) * fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout (daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI killed early, or flag mismatch) threw a raw SyntaxError that propagated uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a context-free SyntaxError with no hint of the actual mcporter failure. Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet (matching the guard pattern already used in parseListedCollections in this file, and the recent matrix openclaw#97973 / sms openclaw#97999 / signal openclaw#98073 / telegram-ingress openclaw#98372 JSON.parse guard series). Co-Authored-By: Claude <[email protected]> * fix(memory-core): preserve cause in qmd mcporter JSON.parse guard Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error lint rule; the original SyntaxError is now chained, improving diagnosability. Co-Authored-By: Claude <[email protected]> * fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary) ClawSweeper flagged that the previous error message exposed raw mcporter stdout (first 200 chars) before session visibility filtering, which could leak sensitive content. Drop the stdout preview from the thrown message; keep the original SyntaxError as `cause` for diagnostics so the parse-failure reason is still reachable without surfacing unfiltered subprocess output. Co-Authored-By: Claude <[email protected]> * fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak) The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its message (e.g. Unexpected token '<', then the raw bytes). Including that SyntaxError message in the thrown Error would surface unfiltered mcporter stdout before session visibility filtering. Drop the parse-error message from the thrown Error; keep the original SyntaxError as cause for developer diagnostics only. Co-Authored-By: Claude <[email protected]> * fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage) formatErrorMessage walks the .cause chain into the user-visible path. Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw stdout snippet through formatErrorMessage even with a generic message. Give the cause a generic message too; the raw snippet no longer reaches the user. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
What Problem This Solves
extensions/signal/src/client-container.ts:262callsJSON.parse(text)on the Signal REST container response body without try/catch. The body is already bounded byreadProviderTextResponse(16 MiB shared provider cap), but a malformed JSON body within that cap throws unhandledSyntaxError.This is error normalization: wrapping the parse in try/catch produces a stable
Error("Signal REST returned malformed JSON")instead of a rawSyntaxError, giving callers consistent error handling.Changes
extensions/signal/src/client-container.ts: wrapJSON.parse(text)in try/catch →Error("Signal REST returned malformed JSON")extensions/signal/src/client-container.test.ts: tighten existing assertion to the specific error messagetest/_proof_signal_json_guard.mts: real HTTP server proof (L2)Label: bugfix | merge-risk: 🟢 minimal | AI-assisted
Evidence
Real HTTP server proof — local node:http server returns malformed JSON (L2)
Starts a local
node:httpserver on127.0.0.1:0, has it returnNOT JSON {{{withcontent-type: application/json, then callscontainerRestRequestagainst it through the real fetch stack. No mock, no global override.Unit test