openclaw - ✅(Solved) Fix QQ Bot cannot send images saved to media/outbound (Media path must be inside QQ Bot media storage) [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#68016Fetched 2026-04-18 05:54:25
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1referenced ×1

When an AI agent (e.g., kimi-for-coding) takes a screenshot or generates an image, it saves the file to ~/.openclaw/media/outbound/. However, when the QQ Bot channel tries to send this image, it fails with:

sendMedia(auto) error for C:\Users\...\.openclaw\media\outbound\xxx.png: Media path must be inside QQ Bot media storage

Error Message

sendMedia(auto) error for C:\Users....openclaw\media\outbound\xxx.png: Media path must be inside QQ Bot media storage

Root Cause

In dist/extensions/qqbot/outbound-*.js, the resolveQQBotPayloadLocalFilePath() function validates local file paths against allowedRoots, which only includes getQQBotMediaDir() (i.e., ~/.openclaw/media/qqbot/). Files in media/outbound/ or media/inbound/ are rejected.

The relevant code:

const allowedRoots = [getQQBotMediaDir()];
for (const root of allowedRoots) {
    const resolvedRoot = path.resolve(root);
    if (isPathWithinRoot(canonicalCandidate, fs.realpathSync(resolvedRoot))) return canonicalCandidate;
}

Meanwhile, the resolveOutboundMediaPath() function does support options.extraLocalRoots, but the caller (image sending path) doesn't pass outbound or inbound as extra roots.

Fix Action

Workaround

Adding media/outbound and media/inbound to allowedRoots:

const allowedRoots = [getQQBotMediaDir(), path.join(getHomeDir(), ".openclaw", "media", "outbound"), path.join(getHomeDir(), ".openclaw", "media", "inbound")];

PR fix notes

PR #68025: fix(qqbot): allow OpenClaw managed media outbound/inbound dirs in payload allowlist

Description (problem / solution / changelog)

Summary

Fixes #68016 — QQ Bot was failing to send any image an agent saved to the framework-managed media/outbound/ directory with:

``` Media path must be inside QQ Bot media storage ```

Screenshots taken by agents (kimi-for-coding, et al.) land in `/.openclaw/media/outbound/`, not in the QQ Bot-specific `/.openclaw/media/qqbot/`, so they got rejected at the allowlist check.

Root cause

PR #63271 correctly tightened `resolveQQBotPayloadLocalFilePath()` to prevent arbitrary local paths (e.g. `/etc/passwd`) from being sent via QQ Bot. But the hardcoded `allowedRoots = [getQQBotMediaDir()]` only covers `media/qqbot/`. It does not cover the other OpenClaw-managed media subdirectories that framework tooling writes to:

  • `~/.openclaw/media/outbound/` — agent-generated screenshots and outbound tool artifacts
  • `~/.openclaw/media/inbound/` — capture path for inbound attachments

The caller-side `resolveOutboundMediaPath()` does accept an `extraLocalRoots` option, but most send-path call sites (`sendPhoto`, `sendVideoMsg`, etc.) don't thread those extras through, so the restrictive default fires.

Fix

Extend `allowedRoots` in `resolveQQBotPayloadLocalFilePath()` to include both OpenClaw-managed media subdirectories. All three allowed roots now live under `~/.openclaw/media/`, so the containment semantics stay identical — the existing `realpath` + `isPathWithinRoot` guard still blocks anything that escapes via symlink or `..`. The core restriction introduced by #63271 (reject arbitrary local paths) is unchanged; the patch only broadens the allowlist to the peer OpenClaw-managed media dirs the framework itself writes to.

Test plan

  • Added two new test cases in `extensions/qqbot/src/utils/platform.test.ts`:
    • `allows structured payload files inside the OpenClaw managed media outbound directory`
    • `allows structured payload files inside the OpenClaw managed media inbound directory`
  • Existing negative tests continue to pass (arbitrary outside-dir files, data-dir files, and `..` escapes are still blocked).
  • `npx tsc --noEmit` — 247 pre-existing errors on `main`, 247 on branch (no delta on touched files).
  • `pnpm exec oxlint` on both touched files — 0 warnings, 0 errors.

Local full vitest is blocked by the pre-existing `test/non-isolated-runner.ts` drift (reproduces on `main`) — CI should run the new cases normally.

Security notes

  • Both added roots live inside `~/.openclaw/media/`, which is already OpenClaw-owned territory. No new filesystem surface is exposed.
  • `realpathSync` + `isPathWithinRoot` still enforce containment, so symlinks or `..` paths that try to escape the `media/outbound/` or `media/inbound/` root are rejected.
  • Anything outside `/.openclaw/media/{qqbot,outbound,inbound}/` — including `/.openclaw/qqbot/` (the data dir) — remains rejected, matching the existing `blocks structured payload files inside the QQ Bot data directory` test.

Changed files

  • extensions/qqbot/src/utils/platform.test.ts (modified, +36/-0)
  • extensions/qqbot/src/utils/platform.ts (modified, +21/-1)

Code Example

sendMedia(auto) error for C:\Users\...\.openclaw\media\outbound\xxx.png: Media path must be inside QQ Bot media storage

---

const allowedRoots = [getQQBotMediaDir()];
for (const root of allowedRoots) {
    const resolvedRoot = path.resolve(root);
    if (isPathWithinRoot(canonicalCandidate, fs.realpathSync(resolvedRoot))) return canonicalCandidate;
}

---

const allowedRoots = [getQQBotMediaDir(), path.join(getHomeDir(), ".openclaw", "media", "outbound"), path.join(getHomeDir(), ".openclaw", "media", "inbound")];
RAW_BUFFERClick to expand / collapse

Bug: QQ Bot cannot send locally-generated images

Description

When an AI agent (e.g., kimi-for-coding) takes a screenshot or generates an image, it saves the file to ~/.openclaw/media/outbound/. However, when the QQ Bot channel tries to send this image, it fails with:

sendMedia(auto) error for C:\Users\...\.openclaw\media\outbound\xxx.png: Media path must be inside QQ Bot media storage

Root Cause

In dist/extensions/qqbot/outbound-*.js, the resolveQQBotPayloadLocalFilePath() function validates local file paths against allowedRoots, which only includes getQQBotMediaDir() (i.e., ~/.openclaw/media/qqbot/). Files in media/outbound/ or media/inbound/ are rejected.

The relevant code:

const allowedRoots = [getQQBotMediaDir()];
for (const root of allowedRoots) {
    const resolvedRoot = path.resolve(root);
    if (isPathWithinRoot(canonicalCandidate, fs.realpathSync(resolvedRoot))) return canonicalCandidate;
}

Meanwhile, the resolveOutboundMediaPath() function does support options.extraLocalRoots, but the caller (image sending path) doesn't pass outbound or inbound as extra roots.

Impact

  • QQ Bot users cannot receive screenshots taken by the agent
  • QQ Bot users cannot receive any locally-generated images that are saved to media/outbound/
  • The agent enters an infinite retry loop trying different send methods, eventually timing out (3 min+)

Workaround

Adding media/outbound and media/inbound to allowedRoots:

const allowedRoots = [getQQBotMediaDir(), path.join(getHomeDir(), ".openclaw", "media", "outbound"), path.join(getHomeDir(), ".openclaw", "media", "inbound")];

Expected Fix

Either:

  1. Add media/outbound and media/inbound to the default allowedRoots in resolveQQBotPayloadLocalFilePath()
  2. Or automatically copy outbound images to media/qqbot/ before sending
  3. Or have the caller pass extraLocalRoots when sending images via QQ Bot

Environment

  • OpenClaw version: 2026.4.15
  • OS: Windows 11
  • Channel: QQ Bot
  • Model: kimi-for-coding

extent analysis

TL;DR

The most likely fix is to add media/outbound and media/inbound to the default allowedRoots in resolveQQBotPayloadLocalFilePath() to allow the QQ Bot to send locally-generated images.

Guidance

  • Modify the resolveQQBotPayloadLocalFilePath() function to include media/outbound and media/inbound in the allowedRoots array to enable sending of locally-generated images.
  • Alternatively, consider automatically copying outbound images to media/qqbot/ before sending or passing extraLocalRoots when sending images via QQ Bot.
  • Verify the fix by testing the image sending functionality with the updated allowedRoots configuration.
  • Ensure that the getHomeDir() function returns the correct path to the user's home directory.

Example

const allowedRoots = [getQQBotMediaDir(), path.join(getHomeDir(), ".openclaw", "media", "outbound"), path.join(getHomeDir(), ".openclaw", "media", "inbound")];

Notes

This fix assumes that the getHomeDir() function returns the correct path to the user's home directory. Additionally, this solution may have security implications if not properly validated, as it allows files from additional directories to be sent.

Recommendation

Apply the workaround by adding media/outbound and media/inbound to the default allowedRoots in resolveQQBotPayloadLocalFilePath(), as this is the most straightforward solution to enable sending of locally-generated images.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix QQ Bot cannot send images saved to media/outbound (Media path must be inside QQ Bot media storage) [1 pull requests, 1 participants]