openclaw - ✅(Solved) Fix Browser screenshot delivery to chat fails — MEDIA:~/... paths rejected by media-source validator [1 pull requests, 1 comments, 2 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#73796Fetched 2026-04-29 06:15:04
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
referenced ×3commented ×1cross-referenced ×1

When the browser plugin saves a screenshot and the agent emits a MEDIA: line referencing it with a ~/-prefixed path, the channel outbound silently drops the attachment. The user receives ⚠️ Media failed. instead of the photo. This makes "send me a screenshot" requests over Telegram unable to deliver media, even though the screenshot is captured successfully and the file exists on disk.

Root Cause

In dist/parse-DbkqxPau.js:

function hasTraversalOrHomeDirPrefix(candidate) {
    return candidate.startsWith("../")
        || candidate === ".."
        || candidate.startsWith("~")           // ← treats ~/ as traversal
        || TRAVERSAL_SEGMENT_RE.test(candidate);
}

function isLikelyLocalPath(candidate) {
    if (hasTraversalOrHomeDirPrefix(candidate)) return false;  // ← rejects ~/...
    return candidate.startsWith("/") || ...;
}

function isValidMedia(candidate, opts) {
    ...
    if (isLikelyLocalPath(candidate)) return true;
    if (hasTraversalOrHomeDirPrefix(candidate)) return false;  // ← rejects ~/... again
    ...
}

Any MEDIA:~/... line fails isValidMedia, so the path never reaches the channel-attach pipeline. Meanwhile, the browser plugin's screenshot result text uses ~/ form, and the agent passes it through verbatim.

Fix Action

Fix / Workaround

A workspace-instructions workaround (telling the agent in TOOLS.md to emit absolute paths) was attempted and ignored on three consecutive screenshot requests; the model defaulted to ~/ regardless.

PR fix notes

PR #73798: fix(browser): emit MEDIA: with absolute path so reply media validator accepts it (#73796)

Description (problem / solution / changelog)

Fixes #73796.

Problem

The browser CLI emits MEDIA:<path> lines wrapped with `shortenHomePath`, producing tilde-prefixed paths like `MEDIA:/.openclaw/media/browser/abc.png`. The agent then forwards that line verbatim into its reply, where the shared media validator (`isLikelyLocalPath` / `hasTraversalOrHomeDirPrefix` in `src/media/parse.ts`) treats any ``-prefixed path as a home-dir traversal candidate and rejects it. Telegram (and other channels) silently drop the attachment with "⚠️ Media failed" even though the screenshot succeeded.

Fix

Stop emitting the tilde form on the machine-readable `MEDIA:` line. The browser CLI now logs `MEDIA:<absolute-path>` at all three sites (screenshot command, snapshot AI image, and the `--out` AI snapshot). `/Users/...` and `/home/...` paths are accepted by the existing parser; real `../` traversal stays blocked.

The visual log of `--out` (line 129, the human-readable file path summary) keeps `shortenHomePath` — only the agent-consumed `MEDIA:` line is changed.

This matches the "stop machine-readable browser MEDIA output from using shortenHomePath" path that clawsweeper recommended in its triage of #73796 — keeps the parser's security contract intact (no parser change, no test changes there).

What changed

LocationBeforeAfter
`browser-cli-inspect.ts:51` (screenshot)`MEDIA:${shortenHomePath(result.path)}``MEDIA:${result.path}`
`browser-cli-inspect.ts:131` (snapshot --out AI)`MEDIA:${shortenHomePath(result.imagePath)}``MEDIA:${result.imagePath}`
`browser-cli-inspect.ts:145` (snapshot AI image)`MEDIA:${shortenHomePath(result.imagePath)}``MEDIA:${result.imagePath}`

`shortenHomePath` import stays — line 129 still uses it for the human-readable `--out` path log.

Tests

Two regression tests added in `browser-cli-inspect.test.ts`:

  • `emits screenshot MEDIA: with absolute path so the shared media validator accepts it (regression for #73796)`
  • `emits snapshot MEDIA: with absolute path for ai snapshots (regression for #73796)`

Both assert the emitted line equals `MEDIA:<abs-path>` and does NOT match `/^MEDIA:~/`.

``` pnpm vitest run --config test/vitest/vitest.extension-browser.config.ts extensions/browser/src/cli/browser-cli-inspect.test.ts

10 passed (was 8, +2 regression tests)

pnpm vitest run --config test/vitest/vitest.extension-browser.config.ts

1017 passed | 1 skipped

```

Why not change the parser

The parser's tilde-rejection is intentional — `MEDIA:/.ssh/id_rsa` is in the existing rejected-paths test list (`src/media/parse.test.ts:64`) so an agent can't ask for private files via tilde. Changing the parser to expand `` would require adding a separate boundary check downstream and might widen the attack surface unintentionally. Fixing the producer is smaller and surgical.

🦞 lobster-biscuit


Sign-Off: hclsys

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • extensions/browser/src/cli/browser-cli-inspect.test.ts (modified, +57/-1)
  • extensions/browser/src/cli/browser-cli-inspect.ts (modified, +3/-3)

Code Example

function hasTraversalOrHomeDirPrefix(candidate) {
    return candidate.startsWith("../")
        || candidate === ".."
        || candidate.startsWith("~")           // ← treats ~/ as traversal
        || TRAVERSAL_SEGMENT_RE.test(candidate);
}

function isLikelyLocalPath(candidate) {
    if (hasTraversalOrHomeDirPrefix(candidate)) return false;  // ← rejects ~/...
    return candidate.startsWith("/") || ...;
}

function isValidMedia(candidate, opts) {
    ...
    if (isLikelyLocalPath(candidate)) return true;
    if (hasTraversalOrHomeDirPrefix(candidate)) return false;  // ← rejects ~/... again
    ...
}

---

function normalizeMediaSource(src) {
       let out = src.startsWith("file://") ? src.replace("file://", "") : src;
       if (out === "~" || out.startsWith("~/")) {
           const home = process.env.HOME;
           if (home) out = home + out.slice(1);
       }
       return out;
   }

---

[gateway] ⇄ res ✓ browser.request 52ms ...
[claude] MEDIA:~/.openclaw/media/browser/9d00d84a-b761-43b4-a038-12b19e787070.png
[telegram] sendMessage ok chat=... message=38
RAW_BUFFERClick to expand / collapse

Summary

When the browser plugin saves a screenshot and the agent emits a MEDIA: line referencing it with a ~/-prefixed path, the channel outbound silently drops the attachment. The user receives ⚠️ Media failed. instead of the photo. This makes "send me a screenshot" requests over Telegram unable to deliver media, even though the screenshot is captured successfully and the file exists on disk.

Environment

  • OpenClaw 2026.4.26 (cbcfdf6 / be8c246)
  • Node 24.15.0
  • Ubuntu 24.04 LTS, headless VPS
  • Channel: Telegram (bot, polling)
  • Agent runtime: claude-cli (Claude Opus 4.7)
  • Browser: Google Chrome stable 147 at /usr/bin/google-chrome-stable

Steps to reproduce

  1. Configure Telegram channel and browser plugin.
  2. From Telegram, ask the bot to take a screenshot of any web page (e.g., "Go to data.cms.gov, find any ACO dataset, screenshot a few rows").
  3. The bot navigates and screenshots successfully; gateway logs show browser.request calls returning OK and the file is written under /home/<user>/.openclaw/media/browser/<uuid>.png.
  4. The agent's reply includes a line like MEDIA:~/.openclaw/media/browser/<uuid>.png.
  5. Telegram receives only the text reply with ⚠️ Media failed. — no sendPhoto/sendDocument is issued (verified via telegram sendMessage ok ... in logs, no media-send call).

Expected behavior

The screenshot is delivered to the chat as an attached photo.

Actual behavior

The MEDIA: line is silently dropped because its ~/-prefixed path fails validation. The user sees "Media failed".

Root cause

In dist/parse-DbkqxPau.js:

function hasTraversalOrHomeDirPrefix(candidate) {
    return candidate.startsWith("../")
        || candidate === ".."
        || candidate.startsWith("~")           // ← treats ~/ as traversal
        || TRAVERSAL_SEGMENT_RE.test(candidate);
}

function isLikelyLocalPath(candidate) {
    if (hasTraversalOrHomeDirPrefix(candidate)) return false;  // ← rejects ~/...
    return candidate.startsWith("/") || ...;
}

function isValidMedia(candidate, opts) {
    ...
    if (isLikelyLocalPath(candidate)) return true;
    if (hasTraversalOrHomeDirPrefix(candidate)) return false;  // ← rejects ~/... again
    ...
}

Any MEDIA:~/... line fails isValidMedia, so the path never reaches the channel-attach pipeline. Meanwhile, the browser plugin's screenshot result text uses ~/ form, and the agent passes it through verbatim.

Suggested fix

Either:

  1. Expand ~/ in normalizeMediaSource before validation (preferred — fixes any tool emitting tildes):

    function normalizeMediaSource(src) {
        let out = src.startsWith("file://") ? src.replace("file://", "") : src;
        if (out === "~" || out.startsWith("~/")) {
            const home = process.env.HOME;
            if (home) out = home + out.slice(1);
        }
        return out;
    }

    hasTraversalOrHomeDirPrefix continues to block real traversal (../).

  2. Or have the browser plugin emit absolute paths in its tool result so the agent never sees ~/. Less general (other tools may also produce ~/ paths).

A workspace-instructions workaround (telling the agent in TOOLS.md to emit absolute paths) was attempted and ignored on three consecutive screenshot requests; the model defaulted to ~/ regardless.

Logs / evidence

[gateway] ⇄ res ✓ browser.request 52ms ...
[claude] MEDIA:~/.openclaw/media/browser/9d00d84a-b761-43b4-a038-12b19e787070.png
[telegram] sendMessage ok chat=... message=38

(No sendPhoto or media call. File /home/openclaw/.openclaw/media/browser/9d00d84a-...png exists, 34 KB, valid PNG.)

extent analysis

TL;DR

The most likely fix is to expand the ~/ path in the normalizeMediaSource function before validation to ensure that the media attachment is not silently dropped.

Guidance

  • The issue is caused by the ~/-prefixed path in the MEDIA: line failing validation due to the hasTraversalOrHomeDirPrefix function treating it as a traversal.
  • To fix this, the normalizeMediaSource function can be modified to expand the ~/ path to an absolute path before validation.
  • The suggested fix involves adding a check for ~/ paths in the normalizeMediaSource function and replacing them with the absolute path using the process.env.HOME variable.
  • An alternative solution is to have the browser plugin emit absolute paths in its tool result, but this may not be feasible if other tools also produce ~/ paths.

Example

function normalizeMediaSource(src) {
    let out = src.startsWith("file://") ? src.replace("file://", "") : src;
    if (out === "~" || out.startsWith("~/")) {
        const home = process.env.HOME;
        if (home) out = home + out.slice(1);
    }
    return out;
}

Notes

  • The hasTraversalOrHomeDirPrefix function is intended to block real traversal attacks, so it should not be modified to allow ~/ paths.
  • The suggested fix only expands ~/ paths and does not affect the validation of other types of paths.

Recommendation

Apply the workaround by expanding the ~/ path in the normalizeMediaSource function, as it is a more general solution that fixes the issue for any tool that emits ~/ paths.

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…

FAQ

Expected behavior

The screenshot is delivered to the chat as an attached photo.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING