Found while tracing #141; unrelated to it, and not fixed here.
What is real
RichText renders a formatted body's <img> directly, and safeImageSrc
passes anyhttps:// URL through untouched - which is what makes custom
emoji and inline images work, so it is a feature rather than an oversight.
It also means a message can name a URL its author controls. Rendering it
fetches it, which tells that author the message was read, when, and from what
IP address. Matrix's formatted_body is the obvious way in - a sender writes
the HTML - and moho draws it as sent.
Nothing in the store does this today: of every message currently held, none
has an <img> with an https source in its formatted body. So this is a
capability, not an incident.
What is not real
The suspicion in #142 - that sanitising a body fetches the things it names -
is disproven. A body carrying <link rel="stylesheet">, <style>@font-face, <img src="https://...">, a CSS background: url() and a <script src> was
parsed through the same DOMParser path with the network watched:
network requests for probe assets: none
CSP violations: []
The parse is inert. The exposure is at render, and only for <img>.
What to do about it
A decision rather than a fix, which is why this is its own issue:
Leave it. Inline images and custom emoji keep working. A sender who
wants a read receipt can have one.
Route remote images through the daemon, as Sneedchat's already are -
the author learns the daemon fetched it, not when a person read it, and
never their address. Costs a fetch and a cache; the daemon already has
both.
A preference, defaulting to off for services where a sender writes the
HTML. Honest, and one more thing to explain.
Option 2 is the one that keeps the feature and removes the tell.
Found while tracing #141; unrelated to it, and not fixed here.
## What is real
`RichText` renders a formatted body's `<img>` directly, and `safeImageSrc`
passes **any** `https://` URL through untouched - which is what makes custom
emoji and inline images work, so it is a feature rather than an oversight.
It also means a message can name a URL its author controls. Rendering it
fetches it, which tells that author the message was read, when, and from what
IP address. Matrix's `formatted_body` is the obvious way in - a sender writes
the HTML - and moho draws it as sent.
Nothing in the store does this today: of every message currently held, none
has an `<img>` with an `https` source in its formatted body. So this is a
capability, not an incident.
## What is not real
The suspicion in #142 - that sanitising a body *fetches* the things it names -
is disproven. A body carrying `<link rel="stylesheet">`, `<style>@font-face`,
`<img src="https://...">`, a CSS `background: url()` and a `<script src>` was
parsed through the same `DOMParser` path with the network watched:
```
network requests for probe assets: none
CSP violations: []
```
The parse is inert. The exposure is at render, and only for `<img>`.
## What to do about it
A decision rather than a fix, which is why this is its own issue:
1. **Leave it.** Inline images and custom emoji keep working. A sender who
wants a read receipt can have one.
2. **Route remote images through the daemon**, as Sneedchat's already are -
the author learns the daemon fetched it, not when a person read it, and
never their address. Costs a fetch and a cache; the daemon already has
both.
3. **A preference**, defaulting to off for services where a sender writes the
HTML. Honest, and one more thing to explain.
Option 2 is the one that keeps the feature and removes the tell.
Option 2 was written up here as the one that keeps the feature and removes the
tell. It turned out not to be needed: there was no feature to keep.
RichText now takes ownMarkup, off by default, and only markup this
client wrote may fetch a remote picture. A formatted body from a sender gets
none. The two call sites say which they are: a message body is ours unless the
service sent formatting with it, and an embed description is always ours.
A refused image keeps its alt text rather than vanishing. What a picture was
called is worth having; a tracking pixel has no name to keep.
Measured
A formatted body carrying a tracker was put through the running client - a row
written straight into the scrollback store, so nothing was sent anywhere:
<img src="https://cdn.example.com/tracker-probe.png" alt="[the picture]">
rendered: "a message with a picture the sender chose [the picture]"
img tags: 0
requests: none
And the other way, so the fix is not simply "no images": on a Discord channel,
where the markup is ours, custom emoji still draw and still fetch from the CDN.
Why not the daemon proxy
Because there is nothing to proxy. Inline images do not render on Matrix today
and did not before this: safeImageSrc already refused mxc://, so the safe
source was being dropped while the dangerous one was honoured. Of every message
in this store, not one has an <img> in its formatted body - mxc or https.
Building the proxy would have been machinery for a case with no users, and it
brings its own question (a daemon that fetches URLs named by strangers needs to
be told about loopback and private addresses first). If inline Matrix images
are ever wanted, the route is cached_media_path in backend/matrix/media.rs,
which already resolves mxc:// with the account's token and caches under a
size cap - the work is an RPC and a small renderer cache, not a new fetcher.
The trade-off worth stating: if a bridge ever sends an https image inside a
formatted body, it now shows its alt text instead of the picture. That is the
same rule applied evenly, and the fix for it is the mxc route above rather
than trusting the sender's URL.
Finished - https://git.salastil.com/Salastil/moho/commit/ede11ca
Option 2 was written up here as the one that keeps the feature and removes the
tell. It turned out not to be needed: there was no feature to keep.
`RichText` now takes `ownMarkup`, **off by default**, and only markup this
client wrote may fetch a remote picture. A formatted body from a sender gets
none. The two call sites say which they are: a message body is ours unless the
service sent formatting with it, and an embed description is always ours.
A refused image keeps its `alt` text rather than vanishing. What a picture was
called is worth having; a tracking pixel has no name to keep.
## Measured
A formatted body carrying a tracker was put through the running client - a row
written straight into the scrollback store, so nothing was sent anywhere:
```
<img src="https://cdn.example.com/tracker-probe.png" alt="[the picture]">
rendered: "a message with a picture the sender chose [the picture]"
img tags: 0
requests: none
```
And the other way, so the fix is not simply "no images": on a Discord channel,
where the markup is ours, custom emoji still draw and still fetch from the CDN.
## Why not the daemon proxy
Because there is nothing to proxy. Inline images do not render on Matrix today
and did not before this: `safeImageSrc` already refused `mxc://`, so the safe
source was being dropped while the dangerous one was honoured. Of every message
in this store, not one has an `<img>` in its formatted body - mxc or https.
Building the proxy would have been machinery for a case with no users, and it
brings its own question (a daemon that fetches URLs named by strangers needs to
be told about loopback and private addresses first). If inline Matrix images
are ever wanted, the route is `cached_media_path` in `backend/matrix/media.rs`,
which already resolves `mxc://` with the account's token and caches under a
size cap - the work is an RPC and a small renderer cache, not a new fetcher.
The trade-off worth stating: if a bridge ever sends an `https` image inside a
formatted body, it now shows its alt text instead of the picture. That is the
same rule applied evenly, and the fix for it is the mxc route above rather
than trusting the sender's URL.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while tracing #141; unrelated to it, and not fixed here.
What is real
RichTextrenders a formatted body's<img>directly, andsafeImageSrcpasses any
https://URL through untouched - which is what makes customemoji and inline images work, so it is a feature rather than an oversight.
It also means a message can name a URL its author controls. Rendering it
fetches it, which tells that author the message was read, when, and from what
IP address. Matrix's
formatted_bodyis the obvious way in - a sender writesthe HTML - and moho draws it as sent.
Nothing in the store does this today: of every message currently held, none
has an
<img>with anhttpssource in its formatted body. So this is acapability, not an incident.
What is not real
The suspicion in #142 - that sanitising a body fetches the things it names -
is disproven. A body carrying
<link rel="stylesheet">,<style>@font-face,<img src="https://...">, a CSSbackground: url()and a<script src>wasparsed through the same
DOMParserpath with the network watched:The parse is inert. The exposure is at render, and only for
<img>.What to do about it
A decision rather than a fix, which is why this is its own issue:
wants a read receipt can have one.
the author learns the daemon fetched it, not when a person read it, and
never their address. Costs a fetch and a cache; the daemon already has
both.
HTML. Honest, and one more thing to explain.
Option 2 is the one that keeps the feature and removes the tell.
Finished - https://git.salastil.com/Salastil/moho/commit/ede11ca
Option 2 was written up here as the one that keeps the feature and removes the
tell. It turned out not to be needed: there was no feature to keep.
RichTextnow takesownMarkup, off by default, and only markup thisclient wrote may fetch a remote picture. A formatted body from a sender gets
none. The two call sites say which they are: a message body is ours unless the
service sent formatting with it, and an embed description is always ours.
A refused image keeps its
alttext rather than vanishing. What a picture wascalled is worth having; a tracking pixel has no name to keep.
Measured
A formatted body carrying a tracker was put through the running client - a row
written straight into the scrollback store, so nothing was sent anywhere:
And the other way, so the fix is not simply "no images": on a Discord channel,
where the markup is ours, custom emoji still draw and still fetch from the CDN.
Why not the daemon proxy
Because there is nothing to proxy. Inline images do not render on Matrix today
and did not before this:
safeImageSrcalready refusedmxc://, so the safesource was being dropped while the dangerous one was honoured. Of every message
in this store, not one has an
<img>in its formatted body - mxc or https.Building the proxy would have been machinery for a case with no users, and it
brings its own question (a daemon that fetches URLs named by strangers needs to
be told about loopback and private addresses first). If inline Matrix images
are ever wanted, the route is
cached_media_pathinbackend/matrix/media.rs,which already resolves
mxc://with the account's token and caches under asize cap - the work is an RPC and a small renderer cache, not a new fetcher.
The trade-off worth stating: if a bridge ever sends an
httpsimage inside aformatted body, it now shows its alt text instead of the picture. That is the
same rule applied evenly, and the fix for it is the mxc route above rather
than trusting the sender's URL.