element-hq / element-hq/element-web
Authenticated media thumbnails/downloads fail in Safari: <img> lacks crossorigin, forcing no-cors and stripping the Service Worker's Authorization header
- Dominant language
- TypeScript
- Stars
- 13.5k
- Forks
- 2.8k
- PR merge metrics
- PR metrics pending
Description
### Steps to reproduce
1. Log into a homeserver with authenticated media enabled (enable_authenticated_media: true), using Safari on macOS.
2. Open any room with multiple avatars/thumbnails (timeline, member list, room list).
3. Open Safari's Web Inspector → Network tab, reload.
4. Observe repeated FetchEvent.respondWith errors in the console for thumbnail/download requests.
For a failing request such as:
https://matrix.example.org/_matrix/media/v3/thumbnail/example.org/?width=60&height=60&method=crop&allow_redirect=true
Network panel shows:
- Status: — (empty)
- Response headers: none at all
- Request headers actually sent:
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-site
(no Authorization header present on the wire)
Replaying the same request with curl but without an Authorization header reproduces a 404 from Synapse (expected per MSC3916). This confirms the request that actually leaves the browser has no bearer token attached, even though sw.js clearly attempts to add one.
### Outcome
Root cause: sw.js correctly retrieves the access token and calls fetch(t, { headers: { Authorization: `Bearer ${token}` } }). However, the triggering element (avatars/thumbnails) does not set the crossorigin attribute. Per spec,
// default to mode: "no-cors" unless crossorigin is set on the element itself — and no-cors mode silently strips custom headers like Authorization, tied to the element's crossorigin setting rather than to the SW's own fetch() mode.
WebKit enforces this element-level restriction strictly even for Service-Worker-mediated responses. Same mechanism documented for in WebKit bug 184447 (bugs.webkit.org/show_bug.cgi?id=184447): "Since the load is in no-cors mode... headers expurged no matter whether the URL is cross origin or not [or what the SW's own fetch specifies]." Also see sjoerdlangkemper.nl/2021/01/06/adding-headers-to-image-request-using-service-workers/ and MDN's crossorigin docs.
Chromium/Gecko appear more permissive and let the SW's fetch() mode take effect on the network — this is why the issue is Safari-specific.
Expected: authenticated media should load in Safari, with the SW able to attach Authorization regardless of engine.
Suggested fix: add crossorigin="anonymous" to // elements used for Matrix media so the browser issues a cors-mode request from the outset, letting the SW's Authorization header actually reach the network in WebKit.
Related: #28370, #27759, #28011
### Operating system
macOS
### Browser information
Safari 26.5.2, WebKit/605.1.15
### URL for webapp
_No response_
### Application version
Element version 1.12.25
### Homeserver
Synapse 1.158.0
### Will you send logs?
No
Contributor guide
Research direction
Start by inspecting sw.js and searching the media-rendering code for the img, video, and audio elements used for avatars, thumbnails, and downloads. Reproduce the Safari failure with authenticated media and verify that the affected requests include the crossorigin setting and load successfully with the Authorization header attached.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- authentication, frontend, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100