Quoting follows the message's own image decision #411

Closed
opened 2026-09-19 22:45:04 +00:00 by jcoffey-dev · 0 comments
Owner

Summary

Reported while testing #409: switching a reply to rich text fetched the quoted message's remote images, on a message whose images had been left blocked while reading it.

reply() sanitized the quote with allowRemote: true unconditionally. Quoting renders the message a second time, so a tracking pixel in the quote reported the message read and the address live — the thing leaving images blocked exists to prevent. Edit as new and opening a draft that quotes a message did the same, so all three now go through one rule.

  • One decision, asked the same way. remoteImagesAllowed() takes the image policy, the trusted senders, whether the sender is a contact, and whether Show images was pressed on that message. MessageView now uses it too, rather than keeping its own copy of the condition.
  • "Show images" moves into the mail store. It was component state, invisible to the composer, so a message the reader had explicitly opened up would still be quoted with its images blocked.
  • Nothing is lost by not fetching. The sanitizer already parks the address in data-ihm-remote; buildEmailObject puts it back, so the sent quote is what its sender wrote and the recipient's client decides for itself. That was your call between the two options.

Related issues

Fixes #410. Found while checking #409.

Translations

Adds none.

Testing

  • quote-image-policy.test.ts: a blocked message's quote carries no fetchable address, the address survives to the sent copy, and images are fetched when the reader pressed Show images, when the policy is always, and for a trusted sender — but not for a stranger under contacts. Two of these fail on the previous code; I ran them against it to be sure they pin the leak.
  • npm test (1424 web, 251 server) and npm run typecheck pass.
  • Driven in Chrome against npm run dev:mock, on a seeded message carrying https://example.com/tracker.gif. With the policy at ask: reading it blocked the pixel, replying and switching to rich text left it blocked, its address preserved on the element, and the network log recorded no request to example.com. Typing in the editor kept both the marker and the address, so the send-time restore still has something to restore. With the policy at always the same quote came through with the real URL and nothing blocked.

One thing I did not change: when images are allowed, the quote uses direct URLs rather than the image proxy, as it did before. The proxy rewrites to an app-relative path, which would have to be undone at send time or the recipient would get links pointing at your server.

Merged 2026-09-19 as coffey-labs/ihasmail@d329b33912

Rebuilt from: git history, session transcript.

## Summary Reported while testing #409: switching a reply to rich text fetched the quoted message's remote images, on a message whose images had been left blocked while reading it. `reply()` sanitized the quote with `allowRemote: true` unconditionally. Quoting renders the message a second time, so a tracking pixel in the quote reported the message read and the address live — the thing leaving images blocked exists to prevent. *Edit as new* and opening a draft that quotes a message did the same, so all three now go through one rule. - **One decision, asked the same way.** `remoteImagesAllowed()` takes the image policy, the trusted senders, whether the sender is a contact, and whether *Show images* was pressed on that message. `MessageView` now uses it too, rather than keeping its own copy of the condition. - **"Show images" moves into the mail store.** It was component state, invisible to the composer, so a message the reader had explicitly opened up would still be quoted with its images blocked. - **Nothing is lost by not fetching.** The sanitizer already parks the address in `data-ihm-remote`; `buildEmailObject` puts it back, so the sent quote is what its sender wrote and the recipient's client decides for itself. That was your call between the two options. ## Related issues Fixes #410. Found while checking #409. ## Translations Adds none. ## Testing - `quote-image-policy.test.ts`: a blocked message's quote carries no fetchable address, the address survives to the sent copy, and images are fetched when the reader pressed *Show images*, when the policy is `always`, and for a trusted sender — but not for a stranger under `contacts`. **Two of these fail on the previous code**; I ran them against it to be sure they pin the leak. - `npm test` (1424 web, 251 server) and `npm run typecheck` pass. - Driven in Chrome against `npm run dev:mock`, on a seeded message carrying `https://example.com/tracker.gif`. With the policy at *ask*: reading it blocked the pixel, replying and switching to rich text left it blocked, its address preserved on the element, and **the network log recorded no request to example.com**. Typing in the editor kept both the marker and the address, so the send-time restore still has something to restore. With the policy at *always* the same quote came through with the real URL and nothing blocked. One thing I did not change: when images are allowed, the quote uses direct URLs rather than the image proxy, as it did before. The proxy rewrites to an app-relative path, which would have to be undone at send time or the recipient would get links pointing at your server. **Merged** 2026-09-19 as coffey-labs/ihasmail@d329b3391292 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.