fix(qqbot): allow scoped sandbox media sends#92872
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 3, 2026, 2:48 AM ET / 06:48 UTC. Summary PR surface: Source +698, Tests +1998, Docs +3. Total +2699 across 16 files. Reproducibility: yes. Source inspection of current main and v2026.6.11 shows QQBot outbound paths do not propagate scoped Review metrics: 1 noteworthy metric.
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:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the scoped QQBot fix after channel/security boundary signoff, while keeping the WebChat attachment-rendering overlap on its separate issue and PR track. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection of current main and v2026.6.11 shows QQBot outbound paths do not propagate scoped Is this the best way to solve the issue? Yes, with maintainer boundary signoff. Threading the existing scoped mediaAccess/localRoots/readFile contract through QQBot-owned outbound paths is narrower than adding broad roots, mirroring files, or adding new configuration. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 768da09ceb15. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +698, Tests +1998, Docs +3. Total +2699 across 16 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
|
58f33c1 to
2a447ab
Compare
|
I think the runtime direction here is mostly right, but the QQBot media skill text needs a pre-merge fix. The important runtime contract is: QQBot outbound media should preserve the current agent/session media access context and translate sandbox-visible paths such as The skill wording, though, is too narrow and likely wrong: <qqmedia>/workspace/文件名或URL</qqmedia>
Please keep the usage generic, for example
With that wording fixed, the runtime implementation looks like the right shape. Without it, this PR risks replacing a runtime path bug with an agent-instruction bug where non-sandbox or attachment-return cases get incorrectly pushed toward |
|
Thanks @sliverp, for the detail review. I updated the QQBot media skill wording so The generic form is now |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
I re-ran the latest PR head against a live QQBot C2C session and the current scoped-media fix looks correct for the covered path. Evidence from the live run:
One clarification: in this run, the plain CLI text form |
|
Pushed a follow-up commit to tighten the two remaining path-boundary gaps: What changed:
Added regression coverage for both the successful relative plain payload case and the workspace-escape rejection case. Proof:
|
|
One remaining boundary gap I found while re-reviewing the latest head: Structured Concretely, but then passes raw roots such as: to The fix should be small: in |
|
Pushed the follow-up fix for the structured The structured payload validator now maps allowed local roots through Updated the regression assertion so the structured payload test now expects the scoped real root instead of the raw virtual root. Proof:
|
Cover the seven QQBot outbound media paths that dropped core scoped sandbox media access: gateway block/tool/streaming deliver, direct text qqmedia tags, structured QQBOT_PAYLOAD, and the scoped workspace relative/missing-voice path resolution. The low-level sendPhoto / sendVideoMsg / sendDocument now consume core mediaAccess.readFile via the shared outbound loader with explicit localRoots guard, so host-mediated workspace media can be sent without widening QQBot FS access. Voice stays on the existing SILK transcode path to avoid bypassing QQ-required format conversion.
Gateway dispatchOutbound previously built gatewayMediaContext with only mediaLocalRoots, so a sandboxed block reply that carried a relative media tag (e.g. <qqmedia>report.docx</qqmedia>) reached the downstream sender without mediaAccess.workspaceDir and was rejected by containment checks before the file could be resolved against the agent workspace. Resolve the agent workspace dir via the public plugin-sdk agent-runtime helper and pass it through mediaAccess.workspaceDir alongside the existing mediaLocalRoots so the gateway block reply, tool forwarding, QQBOT_PAYLOAD, and official C2C streaming paths all share the same scoped workspace resolution as direct outbound sends. No public plugin SDK boundary is widened; the helper is an existing export. Add a regression test that exercises a relative <qqmedia> tag in a gateway block reply and confirms it resolves against the configured agent workspace.
Keep the conflict-resolution update scoped to the QQBot media send files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Landed via temp rebase onto main (gate skipped per request). Thanks @zhangguiping-xydt! |


Related: #92827
What Problem This Solves
/workspace/...path or a relative generated-file path./workspace/report.pdforreport.pdfcould reach QQBot as host paths without the current session workspace mapping. The existing local-root/host-read safety checks then either rejected the file as outside allowed roots or could not read the generated file for upload.<qqmedia>/workspace/report.pdf</qqmedia>, structured QQBot media payloads, and explicit blockmediaUrlvalues.Why This Change Was Made
mediaAccess.workspaceDir, authorized local roots, and read callback through QQBot outbound delivery, media tags, block media URLs, structured payloads, and the C2C streaming media path. The lower media sender maps/workspace/...and bare relative generated-file paths through that scoped context before delegating to the existing local-root and host-read validation./workspace/../...or../...escapes before checking sibling roots.92827and QQBot sandbox media keywords) found this PR as the only same-issue QQBot sandbox-media update; another broad search hit (fix(control-ui): don't hard-deny local attachments before bootstrap roots load #78034) is an unrelated control-ui attachment bootstrap PR and does not cover this QQBot outbound contract.User Impact
/workspace/report.pdforreport.pdf, then replies through QQBot with a media tag, structured QQBot media payload, or blockmediaUrlreferring to that generated file.Evidence
Exact steps or command run after this patch:
Evidence after fix: focused QQBot regression on final local HEAD
5d5bb9be0142fc5d042279e01f7705d3030d6988passed.Observed result after fix:
Target test file:
extensions/qqbot/src/channel.message-adapter.test.ts,extensions/qqbot/src/engine/gateway/outbound-dispatch.test.ts,extensions/qqbot/src/engine/messaging/outbound-media-send.test.ts,extensions/qqbot/src/engine/messaging/outbound-deliver.test.ts, andextensions/qqbot/src/engine/messaging/reply-dispatcher.test.ts.ClawSweeper commit review passed on HEAD
5d5bb9be0142fc5d042279e01f7705d3030d6988withresult: nothing_found,highest_severity: none, andcheck_conclusion: success.git diff --checkpassed for the fullorigin/main...HEADPR diff, and the full diff was rescanned before submit.Existing redacted live QQBot C2C proof for this PR exercised the real Tencent inbound auto-reply path with
<qqmedia>/workspace/..., reached the PR dispatcher/tag parser/scoped workspace mapping, uploaded the generated file through the QQBot files API, and sent the QQBot media message successfully. Secrets, tokens, sender IDs, message IDs, and QQBot file metadata are redacted.New redacted live QQBot group proof on final local HEAD
5d5bb9be0142fc5d042279e01f7705d3030d6988used a group-capable QQBot account and a realGROUP_AT_MESSAGE_CREATEevent, reached mock-openai after inbound, returned<qqmedia>/workspace/<proof-file></qqmedia>, detected the media tag, mapped the sandbox workspace path, posted to Tencent/v2/groups/<redacted>/files, posted the group media message, and completed dispatch. App IDs, secrets, tokens, group IDs, sender IDs, message IDs,message_scenetokens, file data, and QQBot file metadata are redacted.RF-001: the scoped security boundary is addressed by mapping
/workspaceonly through the active session'smediaAccess.workspaceDir, passing current scoped roots/read callbacks through QQBot outbound, and keeping traversal rejection tests for/workspace/../...and../....RF-002: the live external proof boundary is now covered for both QQBot C2C and QQBot group delivery; webchat media rendering remains outside this PR.
RF-003: the QQBot media skill wording was updated so the generic instruction is
<qqmedia>{实际路径或URL}</qqmedia>, with/workspace/...documented only when that is the actual sandbox-visible path.RF-004: the redacted live QQBot C2C and group proofs remain included as supporting real-behavior evidence for the covered QQBot path.
RF-005: the remaining path-boundary gaps are included in this final local branch: host-read requires scoped workspace context, broad default sandbox roots are not exposed, and explicit relative block media paths route through the scoped QQBot media resolver.
What was not tested: this update does not test or change webchat media rendering.