Quote images through the proxy, and unproxy them on the way out #413

Closed
opened 2026-09-19 23:05:27 +00:00 by jcoffey-dev · 0 comments
Owner

Summary

Reading a message routes its remote images through this server, so the sender learns nothing about the reader. Quoting the same message into a reply did not: reply() passed proxyRemote: false, so once images were allowed the composer fetched them straight from wherever the mail pointed — same pixel, same reader, but now carrying their IP and user agent. openDraftEmail and composeAsNew did the same.

  • A quote is proxied the way the message view is, following the deployment's own imageProxy setting.
  • The sent copy is not. A proxied URL belongs to this deployment; left in, the recipient would get images only this server can serve — broken for them, a beacon back here for anyone who could load them. buildEmailObject turns them back into the addresses they came from, beside the pass that restores images blocked under #411 and the one that turns editor blob URLs into cid: references.
  • Proxy off, nothing changes: the quote fetches directly, as reading does there.

Related issues

Fixes #412. Follows #411, where this was noted and deliberately left.

Translations

Adds none.

Testing

  • quote-image-proxy.test.ts: the quote is fetched through the server with the proxy on, directly with it off, and on a forward too; the sent copy carries the image's own address and no /api/image URL; a signature or template image that used the proxy is restored the same way; cid:, blob: and ordinary links are left alone. Two of these fail on the previous code.
  • Three tests from #411 asserted the address sat in src when images were allowed — the old behaviour. They now ask whether the draft fetches the image at all, proxied or not, which is what they were really about.
  • npm test (1430 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, images allowed. The quote's src was /api/image?url=… and the network log shows both requests going to localhost, none to example.com. Sending the reply and reading it back out of the mock's Sent folder: the stored copy carries https://example.com/tracker.gif, with no /api/image URL and no blocked markers left behind.

Merged 2026-09-19 as coffey-labs/ihasmail@23557a72a2

Rebuilt from: git history, session transcript.

## Summary Reading a message routes its remote images through this server, so the sender learns nothing about the reader. Quoting the same message into a reply did not: `reply()` passed `proxyRemote: false`, so once images were allowed the composer fetched them straight from wherever the mail pointed — same pixel, same reader, but now carrying their IP and user agent. `openDraftEmail` and `composeAsNew` did the same. - **A quote is proxied the way the message view is**, following the deployment's own `imageProxy` setting. - **The sent copy is not.** A proxied URL belongs to this deployment; left in, the recipient would get images only this server can serve — broken for them, a beacon back here for anyone who could load them. `buildEmailObject` turns them back into the addresses they came from, beside the pass that restores images blocked under #411 and the one that turns editor blob URLs into `cid:` references. - **Proxy off, nothing changes:** the quote fetches directly, as reading does there. ## Related issues Fixes #412. Follows #411, where this was noted and deliberately left. ## Translations Adds none. ## Testing - `quote-image-proxy.test.ts`: the quote is fetched through the server with the proxy on, directly with it off, and on a forward too; the sent copy carries the image's own address and no `/api/image` URL; a signature or template image that used the proxy is restored the same way; `cid:`, `blob:` and ordinary links are left alone. **Two of these fail on the previous code.** - Three tests from #411 asserted the address sat in `src` when images were allowed — the old behaviour. They now ask whether the draft fetches the image at all, proxied or not, which is what they were really about. - `npm test` (1430 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`, images allowed. The quote's `src` was `/api/image?url=…` and **the network log shows both requests going to localhost, none to example.com**. Sending the reply and reading it back out of the mock's Sent folder: the stored copy carries `https://example.com/tracker.gif`, with no `/api/image` URL and no blocked markers left behind. **Merged** 2026-09-19 as coffey-labs/ihasmail@23557a72a2a7 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.