Skip to content

emrg: the GUI chat renders no local image, leaving base64 as the only way to show one #2638

Closed

Description

@argszero

What happens

A reply that names a local image in Markdown renders in the GUI chat as a broken image — no picture:

![shot](/Users/me/shot.png)
![shot](file:///Users/me/shot.png)

So an agent asked to show a picture has exactly one technique that works: base64. A single screenshot then becomes hundreds of thousands of characters in one message, and one broken newline turns the remainder of the payload into body text that floods the transcript.

Why it happens

Two independent filters, both in the renderer:

  1. emrg/gui/renderer/src/lib/markdown.ts sanitizes with a bare DOMPurify.sanitize(html), so DOMPurify's default URI policy decides every attribute. file: is not in that policy, so the src attribute is dropped and the <img> survives with nothing but its alt.
  2. A root-absolute path is passed through untouched, so the renderer resolves /Users/me/shot.png against its own origin instead of against the filesystem. The CSP is already willing (renderer/src/index.html carries img-src 'self' data: file:), so the sanitizer — not the policy — is what refuses.

What the fix must satisfy

  • Both spellings render an image. ![alt](file:///abs/path.png) and ![alt](/abs/path.png) each produce an <img> that carries a src, not an <img> stripped down to its alt. The root-absolute form is rewritten to a file:// URL before it reaches the DOM, because origin-relative resolution reads the app's own root, not the host's.
  • file: is allowed only for img[src]. Every other URI rule stays exactly as it is: javascript: is still stripped, and file: on any other element or attribute is still stripped.
  • The guard drives the real sanitizer. A test that swaps in a stand-in which merely models DOMPurify's policy proves the stand-in, not the shipped behaviour, so the test uses the vendored DOMPurify under jsdom; removing the allowance must turn it red.

The prompt side

emrg/server/prompts/system.j2 says nothing about images at all, which is why every session has had to rediscover this. It needs a section that states:

  • the one way to show an image in a reply — Markdown image syntax with an absolute path, in either form;
  • that the GUI renders it and the TUI has no image surface, so the alt text must carry the meaning there;
  • that an inline base64 / data: URI is forbidden, and why (size and fragility).

A guard should assert the rendered prompt contains that section and its key sentence, following tests/test_plain_language_rule_reach.py rather than reading the template file alone.

Activity

  1. argszero commented on Oct 29, 2026

    @argszero
    OwnerAuthor

    Handled by #2639

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions