A cached avatar is fetched over a scheme the policy only allows for images #141

Closed
opened 2026-09-07 17:52:37 -04:00 by Salastil · 1 comment
Owner

Found while reviewing the renderer for memory; unrelated to it, and not fixed.

The problem

Every launch logs this, twice:

Refused to connect to
moho-media://file/?p=%2Fhome%2Fultra%2F.cache%2Fnobilis%2Fsneedchat-avatars%2F62488.webp
because it violates the following Content Security Policy directive:
connect-src 'self' https: wss: ws://localhost:* http://localhost:*

The policy in src/renderer/index.html allows moho-media: in img-src and
media-src but not in connect-src, and has since the initial commit - so
this is a fetch()/XHR against a moho-media URL rather than an <img>, and it
has never once succeeded.

The likely culprit is the content-type sniffer (sniffUrl in
src/renderer/src/lib/sniff.ts, reached through extractMedia's onNeedSniff
in MessageRow) being handed a URL that resolveMediaUrl has already turned
into a local one. Harmless as far as anything drawn goes - the sniff fails and
is remembered as "not media" - but it is a request made and refused on every
message carrying one, and a permanent line of red in the console.

The solution

Fix it at the source rather than by widening the policy: a file the daemon has
already fetched and cached does not need its content type probed over HTTP.
The sniffer should skip anything carrying the MEDIA_SCHEME from
src/renderer/src/lib/util.ts and treat it as already known.

Found while reviewing the renderer for memory; unrelated to it, and not fixed. ## The problem Every launch logs this, twice: > Refused to connect to > `moho-media://file/?p=%2Fhome%2Fultra%2F.cache%2Fnobilis%2Fsneedchat-avatars%2F62488.webp` > because it violates the following Content Security Policy directive: > `connect-src 'self' https: wss: ws://localhost:* http://localhost:*` The policy in `src/renderer/index.html` allows `moho-media:` in `img-src` and `media-src` but not in `connect-src`, and has since the initial commit - so this is a `fetch()`/XHR against a moho-media URL rather than an `<img>`, and it has never once succeeded. The likely culprit is the content-type sniffer (`sniffUrl` in `src/renderer/src/lib/sniff.ts`, reached through `extractMedia`'s `onNeedSniff` in `MessageRow`) being handed a URL that `resolveMediaUrl` has already turned into a local one. Harmless as far as anything drawn goes - the sniff fails and is remembered as "not media" - but it is a request made and refused on every message carrying one, and a permanent line of red in the console. ## The solution Fix it at the source rather than by widening the policy: a file the daemon has already fetched and cached does not need its content type probed over HTTP. The sniffer should skip anything carrying the `MEDIA_SCHEME` from `src/renderer/src/lib/util.ts` and treat it as already known.
Salastil added the buglow priority labels 2026-09-07 17:52:37 -04:00
Author
Owner

Finished - 433d2708e8

The description above had the wrong culprit, so for the record: it is not the
sniffer. extractMedia only ever calls onNeedSniff for a URL matching
^https?://, and that one fetch in lib/sniff.ts is the only fetch in the
whole renderer.

It was Notifier.transcodeToPng in src/main/notifications.ts, injecting a
fetch("moho-media://...") into the window with executeJavaScript. Which
makes it not cosmetic at all: that transcode is what draws a notification icon
for an avatar nativeImage cannot read, the fetch was refused every time, the
catch swallowed it, and the notification went out with no icon. 83% of the
Sneedchat avatars in this cache are WebP or GIF
- so on that service the
feature has never once worked.

Fixed by not fetching: main already holds the path and may read it, so it
hands the renderer the bytes as a data URL. A data: image does not taint the
canvas, so the PNG comes back out. The type is sniffed from the first bytes
rather than taken from the filename, because Sneedchat serves WebP called
.jpg - ten of two hundred cached avatars are misfiled that way.

Deliberately not the other fix. Widening connect-src to moho-media: is the
smaller diff and the worse answer: that scheme can also reach the daemon's
config directory, where accounts.toml and its tokens live, and allowing
connect there turns "a script in this window could ask for that file" into "a
script in this window could read it".

Verified in the running client against a real cached avatar
(sneedchat-avatars/62488.webp):

old path (fetch moho-media): FAILED: Failed to fetch
new path (data URL)     : source 96x96 -> png 96x96, 33146 chars, starts data:image/png;base64,
Finished - https://git.salastil.com/Salastil/moho/commit/433d2708e8a483a0cd8647c63e1b7adb2996cfcf The description above had the wrong culprit, so for the record: it is not the sniffer. `extractMedia` only ever calls `onNeedSniff` for a URL matching `^https?://`, and that one `fetch` in `lib/sniff.ts` is the only fetch in the whole renderer. It was `Notifier.transcodeToPng` in `src/main/notifications.ts`, injecting a `fetch("moho-media://...")` into the window with `executeJavaScript`. Which makes it not cosmetic at all: that transcode is what draws a notification icon for an avatar `nativeImage` cannot read, the fetch was refused every time, the `catch` swallowed it, and the notification went out with no icon. **83% of the Sneedchat avatars in this cache are WebP or GIF** - so on that service the feature has never once worked. Fixed by not fetching: main already holds the path and may read it, so it hands the renderer the bytes as a data URL. A `data:` image does not taint the canvas, so the PNG comes back out. The type is sniffed from the first bytes rather than taken from the filename, because Sneedchat serves WebP called `.jpg` - ten of two hundred cached avatars are misfiled that way. Deliberately not the other fix. Widening `connect-src` to `moho-media:` is the smaller diff and the worse answer: that scheme can also reach the daemon's config directory, where `accounts.toml` and its tokens live, and allowing connect there turns "a script in this window could ask for that file" into "a script in this window could read it". Verified in the running client against a real cached avatar (`sneedchat-avatars/62488.webp`): ``` old path (fetch moho-media): FAILED: Failed to fetch new path (data URL) : source 96x96 -> png 96x96, 33146 chars, starts data:image/png;base64, ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Salastil/moho#141