fix(journey): let the browser read a picked clip, so it previews and gets a poster

A clip picked in the entry editor is previewed through a <video> on an object
URL, and its poster frame is grabbed the same way before anything is uploaded.
The CSP never named media-src, so the browser fell back to default-src, which
does not admit blob: at all. The preview stayed empty and the poster capture
failed silently, so every clip landed without a poster and its thumbnail route
answered 404 (issue #2341, the part that survived 4.3.0). None of it showed in
development, where Vite serves the document and helmet never sees it.

media-src now carries 'self' and blob:. For the clips that still come up
without a poster, because the browser cannot decode them, the entry card and
the editor tile draw the play badge on a black tile instead of asking for a
thumbnail that is 404 by design, the way the gallery already did.

Closes #2341
This commit is contained in:
Maurice
2026-09-21 10:35:37 +02:00
parent 43a3a5217e
commit 141afe708d
8 changed files with 127 additions and 14 deletions
@@ -103,6 +103,21 @@ describe('global CSP: script-src', () => {
});
});
describe('global CSP: media-src (#2341)', () => {
it('allows blob:, which is where a picked clip lives until it is uploaded', async () => {
// The editor previews the clip and grabs its poster frame through a <video>
// on an object URL. With media-src unset the browser fell back to
// default-src, which never admits blob:, so the preview stayed empty and
// the clip went up without a poster. Nothing of this shows in dev: Vite
// serves the document there and helmet never sees it.
expect(await directiveSources('media-src')).toContain('blob:');
});
it("keeps 'self' so the uploaded clip still plays from /api/photos", async () => {
expect(await directiveSources('media-src')).toContain("'self'");
});
});
describe('forced-HTTPS redirect', () => {
const saved = { FORCE_HTTPS: process.env.FORCE_HTTPS, APP_URL: process.env.APP_URL };