From c8c162ccefd827b2b41379352ec66e1e9b2f56ca Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:55:48 +1000 Subject: [PATCH] fix: prevent caching protected share thumbnails --- services/recordly-share/worker/src/index.js | 5 ++--- services/recordly-share/worker/test/api.test.js | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/recordly-share/worker/src/index.js b/services/recordly-share/worker/src/index.js index 941c4b23..0eda654b 100644 --- a/services/recordly-share/worker/src/index.js +++ b/services/recordly-share/worker/src/index.js @@ -783,9 +783,8 @@ async function handleRequest(request, env) { const thumb = await env.VIDEOS_BUCKET.get(`thumbnails/${thumbMatch[1]}.jpg`); if (thumb) { return new Response(thumb.body, { - // private: the unlock cookie gates access — a shared cache must not - // serve a protected poster to other clients. - headers: { 'Content-Type': 'image/jpeg', 'Cache-Control': video.password_hash ? 'private, max-age=3600' : 'public, max-age=86400' }, + // Recheck the unlock cookie on every protected poster request. + headers: { 'Content-Type': 'image/jpeg', 'Cache-Control': video.password_hash ? 'private, no-store' : 'public, max-age=86400' }, }); } return new Response('Not found', { status: 404 }); diff --git a/services/recordly-share/worker/test/api.test.js b/services/recordly-share/worker/test/api.test.js index aee4d6ad..491dd051 100644 --- a/services/recordly-share/worker/test/api.test.js +++ b/services/recordly-share/worker/test/api.test.js @@ -467,13 +467,14 @@ describe('review security fixes', () => { } finally { lookup.mockRestore(); } }); - it('never publicly caches protected video or transcripts', async () => { + it('never caches protected video, transcripts, or thumbnails', async () => { const password = 'cache-test'; const { shareCode } = await createShare({ password_hash: await sha256Hex(password) }); await completeUpload(shareCode); + await env.VIDEOS_BUCKET.put(`thumbnails/${shareCode}.jpg`, new Uint8Array([0xff, 0xd8, 0xff, 0xd9])); const unlocked = await SELF.fetch(`${BASE}/s/${shareCode}/verify-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) }); const Cookie = unlocked.headers.get('Set-Cookie').split(';')[0]; - for (const [url, extra] of [[`/v/${shareCode}`, {}], [`/v/${shareCode}`, { Range: 'bytes=0-3' }], [`/vtt/${shareCode}`, {}]]) { + for (const [url, extra] of [[`/v/${shareCode}`, {}], [`/v/${shareCode}`, { Range: 'bytes=0-3' }], [`/vtt/${shareCode}`, {}], [`/thumb/${shareCode}`, {}]]) { const response = await SELF.fetch(`${BASE}${url}`, { headers: { Cookie, ...extra } }); expect([200, 206]).toContain(response.status); expect(response.headers.get('Cache-Control')).toBe('private, no-store');