fix(qqbot): deliver cron auto-TTS voice by trusting OpenClaw temp root#92947
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 15, 2026, 2:53 PM ET / 18:53 UTC. Summary PR surface: Source +62, Tests +70. Total +132 across 4 files. Reproducibility: yes. Source inspection shows current main writes cron TTS audio under the preferred temp root while QQBot's current gates reject that root; the contributor's live proof shows the patched path delivering and an out-of-root control still rejected. Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge
Security Review detailsBest possible solution: Land the scoped QQBot temp-root delivery fix and keep shared empty-identity delivery accounting on the separate accounting PR. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main writes cron TTS audio under the preferred temp root while QQBot's current gates reject that root; the contributor's live proof shows the patched path delivering and an out-of-root control still rejected. Is this the best way to solve the issue? Yes. Extending QQBot's plugin-local sandbox to OpenClaw's already-hardened temp root is narrower than copying media, changing plugin APIs, or relying on the separate accounting fix. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c40db057da33. Label changesLabel justifications:
Evidence reviewedPR surface: Source +62, Tests +70. Total +132 across 4 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
@clawsweeper re-review Updated the Real behavior proof with a live QQ Bot run on the official API (wss://api.sgroup.qq.com), which closes the earlier "no live QQBot run" gap:
This is the delivery fix (message actually arrives) and is complementary to the accounting-only #79811, not a duplicate. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
QQBot is the only channel that root-sandboxes outbound local files. Its three gate sites (resolveOutboundMediaPath, the voice send re-check, and structured-payload validation) only trusted the QQ Bot media storage roots, so framework-generated scratch media written under OpenClaw's hardened temp root (e.g. cron auto-TTS voice files from speech-core) was rejected. The send then returned a no-identity error, the message was silently lost, yet cron still recorded it as delivered. Add one shared resolver (resolveTrustedOutboundMediaPath) that also trusts the preferred OpenClaw temp root — already a sanctioned media root in core (buildMediaLocalRoots) — and route all three gates through it so the trust set agrees everywhere. Fixes openclaw#92816. Co-Authored-By: Claude Opus 4.8 <[email protected]>
5656716 to
782343b
Compare
openclaw#92947) QQBot is the only channel that root-sandboxes outbound local files. Its three gate sites (resolveOutboundMediaPath, the voice send re-check, and structured-payload validation) only trusted the QQ Bot media storage roots, so framework-generated scratch media written under OpenClaw's hardened temp root (e.g. cron auto-TTS voice files from speech-core) was rejected. The send then returned a no-identity error, the message was silently lost, yet cron still recorded it as delivered. Add one shared resolver (resolveTrustedOutboundMediaPath) that also trusts the preferred OpenClaw temp root — already a sanctioned media root in core (buildMediaLocalRoots) — and route all three gates through it so the trust set agrees everywhere. Fixes openclaw#92816. Co-authored-by: zengwen <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]>
openclaw#92947) QQBot is the only channel that root-sandboxes outbound local files. Its three gate sites (resolveOutboundMediaPath, the voice send re-check, and structured-payload validation) only trusted the QQ Bot media storage roots, so framework-generated scratch media written under OpenClaw's hardened temp root (e.g. cron auto-TTS voice files from speech-core) was rejected. The send then returned a no-identity error, the message was silently lost, yet cron still recorded it as delivered. Add one shared resolver (resolveTrustedOutboundMediaPath) that also trusts the preferred OpenClaw temp root — already a sanctioned media root in core (buildMediaLocalRoots) — and route all three gates through it so the trust set agrees everywhere. Fixes openclaw#92816. Co-authored-by: zengwen <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]>
Summary
Fixes #92816. When
messages.tts.auto = "always", cron deliveries to QQBot are silently lost while the run is still recorded as delivered.Root cause: cron auto-TTS (speech-core) writes the generated voice file under OpenClaw's preferred temp root (
resolvePreferredOpenClawTmpDir, e.g./tmp/openclaw/tts-*/voice-*.mp3). QQBot is the only channel that root-sandboxes outbound local files, and its gate only trusted the QQ Bot media storage roots (~/.openclaw/media,~/.openclaw/media/qqbot). So the temp file was rejected,sendMediareturned a no-identity error, the QQ message was lost — but the shared delivery layer still counted it.The trust check runs at three sites that must agree (a file accepted at one is otherwise rejected at the next):
resolveOutboundMediaPath(initial resolve)sendVoiceFromLocalre-check (the gate that actually blocks the voice send)validateStructuredPayloadLocalPath(structured image/video/file payloads)This adds one shared resolver,
resolveTrustedOutboundMediaPath, that also trusts OpenClaw's permission-hardened temp root — which core already treats as a sanctioned media root inbuildMediaLocalRoots(src/media/local-roots.ts) — and routes all three gates through it. The temp root is provisioned0o700, owner-checked, non-symlink, and never world/group-writable (src/infra/tmp-openclaw-dir.ts), so it is the same trust class as~/.openclaw/media;resolveLocalPathFromRootsSyncrealpaths both root and candidate to prevent symlink escape. Out-of-root paths are still rejected.Relation to #79811 (not a duplicate)
This is the delivery fix: it makes the QQ message actually arrive. #79811 (and the earlier, now-closed #92826) only correct accounting — turning a false "delivered" into an honest "failed". The two are complementary: this PR fixes the message loss the user reported; the accounting fix makes a genuine failure visible. Neither has landed on
main, and the media accounting asymmetry there is out of scope here.Real behavior proof
wss://api.sgroup.qq.com), with the OpenClaw gateway built from this branch (pnpm dev gateway), a real C2C recipient,messages.tts.auto = "always", and the keyless Microsoft Edge TTS provider. Also the local resolver unit suite on Node 24.messageId: ROBOT1.0_m-9gIoJjF…(kind: media,conversationId: qqbot:c2c:…). Negative control (untrusted path) was rejected:messageId: "",platformMessageIds: []. The end-to-end cron auto-TTS run wrote a voice file under the temp root (/tmp/openclaw/tts-*/voice-*.mp3) and it arrived on the phone as a QQ voice message (screenshot attached below); no~/.openclaw/media/outboundcopy existed, confirming it was sent straight from the temp root. Unit suite:Test Files 1 passed (1) / Tests 4 passed (4).resolveOutboundMediaPathvoice case) while the out-of-root rejection test still passes; on unpatched code the same temp-root file is rejected,sendMediareturns a no-identity error, and the QQ voice is dropped while cron still records the run as delivered.message send --mediaruns above isolate the gate fix from that flakiness and exercise the exact code this PR changes against the live QQ API.Tests and validation
pnpm test extensions/qqbot— 526 passed (64 files), no regressions from rewiring the three gates.pnpm tsgo:extensionsandpnpm tsgo:test:extensions— clean.pnpm exec oxfmt --check+oxlinton all changed files — clean.message send(delivered vs. rejected) plus an end-to-end cron auto-TTS voice delivered to the device.Risk
User-visible behavior change:
Yes(QQBot now delivers framework temp-root media that was previously dropped). Config/migration:No. Security/auth/secrets:No— trust is limited to OpenClaw's already-hardened temp root, consistent with core's existing media allowlist; realpath containment is preserved, and the live negative control confirms out-of-root media is still rejected.AI-assisted (Claude). Proof above includes a live QQ Bot run on the official API.