* fix(slack): guard relay WebSocket frame JSON.parse against malformed input
Slack Socket Mode relay receives WebSocket frames from external
infrastructure. parseRelayFrame() used bare JSON.parse() which would
throw raw SyntaxError on malformed frames, potentially crashing the
relay connection.
Wrap JSON.parse in try/catch and throw SlackRelayMalformedFrameError
with the original SyntaxError as cause, so callers can distinguish
transport errors from parse errors.
D4 dimension — all competitors have zero coverage.
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* fix(slack): repair relay frame guard tests
---------
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Preserve the Tailscale timeout cleanup from #98134 and the Feishu TDZ fix from #98137 while satisfying the repository lint rules.
Credit: @zhangLei99586 authored both underlying fixes.
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
Co-authored-by: zhangLei99586 <zhang.lei162@xydigit.com>
The finish() closure referenced timer which was declared with const after
finish was defined. If finish(false) ran via the abort signal check at
line 99 or via the abort event listener before the const timer assignment,
accessing timer in the temporal dead zone would throw a ReferenceError.
Hoist timer as a let binding above finish so it is safely undefined when
finish fires early, and the guard in finish handles it.
* fix(matrix): bound raw transport response reads to prevent OOM
* test(matrix): add real node:http server proof for bound transport
Two new integration-style tests drive performMatrixRequest against a real
node:http server (no stubRuntimeFetch mock) using real undici + SSRF
dispatcher and ssrfPolicy.allowPrivateNetwork:
- over-cap: server declares Content-Length > MATRIX_SDK_RESPONSE_MAX_BYTES
with maxBytes omitted → rejects MatrixMediaSizeLimitError (68 ms wall)
- under-cap: server returns small payload with maxBytes omitted → Buffer
returned correctly (12 ms wall)
Addresses ClawSweeper feedback: previous proof showed only Vitest mock
output; these tests exercise the real Matrix runtime fetch path end-to-end.
* test(matrix): satisfy lint for transport proof
* fix(matrix): preserve encrypted media download limits
* test(matrix): add streaming OOM guard proof via real node:http server without Content-Length
Drive readResponseWithLimit directly by omitting Content-Length so that
enforceDeclaredResponseSize is a no-op and the streaming byte cap is the
sole enforcement path. A 20 MiB chunked server response with a 16 MiB
cap confirms the stream is canceled before full buffering (chunksWritten
< TOTAL_CHUNKS). A second case verifies under-cap payloads pass through.
* test(matrix): fix lint errors in real HTTP server proof (curly)
* test(matrix): add under-cap proof to real HTTP server test
An IRC sender mask is nick!user@host where only host is server verified;
nick and user (ident) are client supplied and spoofable. The allowlist
identity classifier treated any entry containing "!" or "@" as a verified
stable identity, so a host-less nick!user entry was classified stable and
matched by the host-less nick!user subject candidate. With
dangerouslyAllowNameMatching at its secure default (off), the mutable
identifier policy only strips entries owned by a dangerous field, so the
host-less entry was never stripped and a remote sender presenting the same
nick and ident was admitted regardless of host.
Require a verified @host component before an entry or subject is classified
stable. Host-less nick and host-less nick!user are now both routed to a
dangerous (mutable) field so they are gated by the same name-matching policy.
The doctor mutable-allowlist detector now also flags host-less nick!user
entries so operators who typed that undocumented shape get a warning. The
documented full nick!user@host mask stays stable and unaffected.
* fix(codex): derive terminal-idle watchdog from effective run timeout
Fixes the early-abort half of #85242. The Codex app-server terminal-idle
watchdog used a hardcoded 30-minute default that was not derived from the
effective run timeout, so a scheduled turn configured with a longer
timeoutSeconds could be aborted early at 30 minutes even with budget left.
resolveCodexTurnTerminalIdleTimeoutMs (now in attempt-timeouts.ts after the
upstream split) accepts the effective run timeout and, with no explicit
override, follows the run budget instead of the 30-minute default:
- explicit override always wins (advanced config / tests)
- otherwise terminal-idle = max(30min floor, run budget), so a longer run is
no longer cut short and existing protection is never shortened
- falls back to the 30min default when no run budget is known
Reuses the existing resolvePositiveIntegerTimeoutMs helper, matching the
neighbouring post-tool resolver. Adds focused unit tests for the derivation.
The diagnostic-wording half of #85242 (naming the terminal-idle watchdog in
the surfaced guidance) is left as a separate follow-up.
* fix(codex): derive terminal-idle watchdog from effective run timeout
* fix(codex): preserve default terminal idle watchdog
---------
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(imessage): add native poll action
Wire the imsg CLI 'poll send' bridge command into the iMessage channel
message-tool action surface, mirroring the existing Discord poll action.
Adds the 'poll' action (gate: polls), a sendPoll runtime, selector-gated
capability advertisement (pollPayloadMessage), config type + zod schema,
regenerated channel metadata, docs, and tests.
* feat(imessage): read inbound polls, vote, and suppress vote echo
Builds on the native poll send action:
- Inbound polls now render to the agent as a readable line (question +
numbered options + tallies) instead of the raw 0xFFFD balloon placeholder,
so a received poll no longer reads as an empty message.
- New `poll-vote` action casts a vote via `imsg poll vote`, resolving a
1-based option index / text / UUID to the poll's option identifier.
- message_tool_only echo guard: the model tends to narrate its choice in a
text reply right after voting ("Blue."), which is redundant since the vote
shows on the poll. A new `poll_vote_echo` suppression reason (alongside
inbound_metadata_echo / internal_runtime_context_echo) drops a send/reply
that exactly restates the just-cast vote, using the option label imsg
returns. Extra content passes through untouched.
* fix(imessage): gate poll-vote on imsg poll.vote rpc capability
Released imsg carries the pollPayloadMessage selector (poll create) but
predates the poll.vote CLI/RPC. Gating both poll and poll-vote on that
selector alone would advertise a vote action the released CLI rejects.
Gate poll-vote additionally on the advertised poll.vote rpc method so this
plugin can ship ahead of the imsg release.
* fix(imessage): enforce poll.vote capability at execution, not just discovery
Codex review flagged the discovery gate as bypassable: a caller that already
knows action=poll-vote skips describeMessageTool and reaches handleAction
directly. Add the same imessageRpcSupportsMethod(status, 'poll.vote') check in
the poll-vote execution path (after assertPrivateApiEnabled), so a direct
dispatch on released imsg fails closed with a clear message instead of an
opaque CLI rejection. Adds a negative handleAction test.
* fix(imessage): harden native poll support
* fix(message): validate targets before channel discovery
* fix(message): validate targets before channel discovery
---------
Co-authored-by: Omar Shahine <lobster@users.noreply.github.com>
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* Route LAN pairing URLs by default route
* Advertise route-aware LAN Control UI links
* Fix route-aware LAN test mocks
* Narrow advertised LAN SDK export
* [AI] fix(feishu): guard partial channelRuntime in monitor startup
When the gateway passes a partial channelRuntime (ChannelRuntimeSurface
with only runtimeContexts but no inbound/debounce), the ?? operator in
monitor.account.ts selects it over the full local runtime, causing
monitor startup to crash on channelRuntime.debounce access.
Mirror the same ?.inbound guard pattern from bot.ts:739 (PR #93466)
to monitor.account.ts:487. When the runtime lacks inbound, fall back
to getFeishuRuntime().channel which always has the full surface.
No test: the guard mirrors an already-merged pattern (#93466) and only
affects the partial-runtime edge case that requires a gateway context.
Related to #92595
* [AI] test(feishu): add channelRuntime guard tests for monitorSingleAccount
Three test cases for the channelRuntime guard at
monitor.account.ts:489:
1. Partial channelRuntime (no inbound) → falls back to local runtime
2. Full channelRuntime with inbound → uses provided runtime
3. No channelRuntime (undefined) → falls back to local runtime
Related to #92595
Preserve structured tool-result replay text across provider transports while keeping provider-facing redaction and Anthropic media ordering intact.
Thanks @snowzlmbot!
* feat(openai): add GPT-5.6 series support
* docs: refresh map for GPT-5.6
* fix(openai): preserve GPT-5.6 thinking metadata
* fix(codex): sync managed app server version
* fix(codex): sync managed app server version
* fix(openai): account for GPT-5.6 cache writes
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
Wrap JSON.parse(text) in MatrixAuthedHttpClient.requestJson with try/catch to prevent a malformed Matrix homeserver response from throwing an unhandled SyntaxError.
On parse failure, throw an Error with statusCode attached (matching the buildHttpError convention) so callers can handle it like any other Matrix API error.
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
Co-authored-by: Claude <noreply@anthropic.com>
Wrap JSON.parse in parseTwilioListPayload and retrieveTwilioMessagingService with try/catch to prevent a malformed Twilio API response from throwing an unhandled SyntaxError.
- parseTwilioListPayload: return [] on parse failure (fail-safe for phone number listing)
- retrieveTwilioMessagingService: throw descriptive Error on parse failure
Note: parseTwilioApiError and parseTwilioSuccessPayload already had try/catch guards (lines 85-97, 104-121).
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
Co-authored-by: Claude <noreply@anthropic.com>
* test(telegram): add regression test for forum topic message_thread_id with streamed reasoning
After thorough code tracing of all delivery paths (draft stream, durable,
non-durable, preview hooks), the current main branch already correctly
passes message_thread_id through all paths for the streaming reasoning +
final answer scenario in forum topics. The bug reported in #89352 may have
been resolved by earlier merged changes (media-path preservation,
draft/progress streaming, etc.).
This commit adds a focused regression test covering the combined
scenario to prevent future regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(telegram): assert forum thread at draft-stream boundary for both lanes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(memory): detect unindexed session transcripts in status mode (fixes#97814)
The status-purpose MemoryIndexManager init skips ensureSessionStartupCatchup(),
so openclaw memory status reports dirty=false while unindexed session files
exist on disk. This is a false-clean state: the operator sees no backlog,
but memory search silently misses unindexed transcripts.
Fix: run markSessionStartupCatchupDirtyFiles() in status mode. It checks
for on-disk session files without corresponding memory_index_sources rows
and sets sessionsDirty=true if found, without triggering a full sync.
The full catchup+sync runs on the next non-transient manager cycle (CLI
sync or normal runtime init).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory): await status dirty detection before status() reads the flag (fixes#97814)
The status-purpose MemoryIndexManager init skips ensureSessionStartupCatchup(),
so openclaw memory status reports dirty=false while unindexed session files
exist on disk. This is a false-clean state: the operator sees no backlog,
but memory search silently misses unindexed transcripts.
Fix: move status-mode markSessionStartupCatchupDirtyFiles() from the
constructor (fire-and-forget via void) into the create() factory method
where it is awaited. This guarantees sessionsDirty is set before any
caller reads manager.status(). The detection does NOT trigger a sync,
so status remains lightweight.
Review: verified manually — unit tests 21/21 pass, compilation 0 errors.
* test(memory): add regression test for status-purpose dirty detection (fixes#97814)
Adds a regression test that exercises the actual purpose:'status' manager
flow: create a session file without index row, create status manager, verify
status().dirty is true. This addresses the ClawSweeper P1 finding requesting
a test that exercises the real status path, not only the protected helper.
Also fixes the timing issue: move dirty detection from constructor (void)
to create() factory (await), guaranteeing sessionsDirty is set before any
caller reads manager.status().
---------
Co-authored-by: Claude <noreply@anthropic.com>
Two-step decode in decodeLiteralEscapes:
- Step 1: regex anchored to high-surrogate range (U+D800–U+DBFF) so a
preceding BMP escape (e.g. \u0041) cannot consume the high-surrogate
half of a valid pair like \uD83D\uDE00 (😀), leaving \uDE00 lone.
- Step 2: decode remaining BMP codepoints; preserve lone surrogates as
six-character literals instead of corrupting them to U+FFFD in the
outbound IRC UTF-8 stream.
* fix(discord): bound happy-path API response reads to prevent OOM
Replace the unbounded res.text() call in requestDiscord's success path with
readResponseTextLimited capped at 4 MiB. Discord channel message lists and
attachment payloads can accumulate to large sizes; without a cap the process
can exhaust available memory. The error path already used readResponseTextLimited
with DISCORD_API_ERROR_BODY_LIMIT_BYTES — this applies the same guard to the
happy path using a separate DISCORD_API_RESPONSE_BODY_LIMIT_BYTES constant
sized appropriately for valid API payloads.
* test(discord): upgrade to real HTTP server proof for bound requestDiscord
* fix(discord): remove unnecessary type assertion in bound test
* [AI] fix(memory-wiki): gracefully handle unparsable YAML frontmatter in vault scans (#96125)
toWikiPageSummary now catches YAML parse errors during vault-wide scans
(compile/lint/query/status) and returns a degraded WikiPageSummary with
frontmatterError set instead of letting the error propagate through
readPageSummaries -> compileMemoryWikiVault, which previously caused one
bad page to crash the entire vault.
Edit paths (apply, chatgpt-import) still throw on bad frontmatter to
prevent silent metadata loss on write.
- markdown.ts: add frontmatterError field to WikiPageSummary, extract
splitWikiFrontmatterBlock, catch YAML.parse in toWikiPageSummary
- lint.ts: add invalid-frontmatter lint code, skip frontmatter-derived
checks when frontmatterError is present
- markdown.test.ts: test degraded parse preserves body, empty frontmatter
- lint.test.ts: test invalid-frontmatter lint on broken page, healthy
page unaffected
Related to #96125
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(memory-wiki): isolate invalid frontmatter scans
* fix(memory-wiki): keep malformed lint reports fail-closed
* fix(memory-wiki): reject non-mapping frontmatter
* fix(memory-wiki): validate generated index targets
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
* fix(imessage): detect MiniMax mm: namespaced reasoning tags in reflection guard
The iMessage reflection guard's THINKING_TAG_RE only matched bare
`<think>/<thought>` tags, so MiniMax's `<mm:think>` namespaced reasoning
tags (and Anthropic's `antml:` prefix) were not recognized as reflected
assistant content. When such reasoning leaked back inbound, the guard
treated it as a normal user message instead of dropping it, allowing
recursive echo amplification.
Accept the known reasoning-tag namespace prefixes `(?:antml:|mm:)?` on
the think/thought alternatives, mirroring the shared reasoning-tag
contract in src/shared/text/reasoning-tags.ts (PR #93767). This is the
remaining channel-monitor complement of #93767 for the iMessage path;
the optional prefix is a pure recognition enhancement with no change to
the bare-tag, code-region exemption, or other reflection patterns.
Verified by driving the real detectReflectedContent export:
<mm:think>secret</mm:think>visible -> isReflection=true (was false)
<think>...</think> -> isReflection=true
<think>secret</think> -> isReflection=true (no regression)
"thinking about minimax algorithms" -> isReflection=false (no false-positive)
`<mm:think>x</mm:think>` in code -> isReflection=false (code exemption kept)
* chore: retrigger CI for real behavior proof check
* refactor(imessage): tighten reflection guard coverage
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
* fix(moonshot): bound video description JSON response reads
The Moonshot video description endpoint used an unbounded await res.json()
to parse the media understanding response. Route through
readProviderJsonResponse (16 MiB cap) to match the bound already in
place for other media understanding providers (xai, openrouter).
AI-assisted.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(moonshot): add bounds and malformed-JSON coverage for video description
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* 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 <noreply@anthropic.com>
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* 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 <liao.shirong@xydigit.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* test(signal): remove committed proof script
---------
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>