fix(review-2): critical — repair comment posting + close export data leak
CR1: the lightbox comment button POSTed to /upload/{id}/comment (singular);
no such route exists, so every UI-submitted comment 404'd and was lost.
Fixed to /comments (plural). The prior e2e passed because its seed helper
POSTs the API directly — added comment-ui.spec.ts which drives the real
component so this can't regress silently again.
CR2: export archives (Gallery.zip / Memories.zip / HTML viewer) were written
under media_path, which is a public ServeDir — so GET /media/exports/
Gallery.zip served the entire event (every photo, caption, comment,
uploader name) to any anonymous visitor at a guessable URL, bypassing the
ticket + release gate. Moved exports to a separate EXPORT_PATH (=/exports)
on its own volume (Dockerfile chown, compose volume, gated handler reads
the new path). export-leak.spec.ts asserts /media/exports/Gallery.zip → 404.
Riders (git can't split hunks): LightboxModal also gains H3 live-eviction on
comment-deleted/upload-deleted; config.rs also wires compression_concurrency
from boot config (medium).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
22
e2e/specs/06-export/export-leak.spec.ts
Normal file
22
e2e/specs/06-export/export-leak.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Regression for the review's CR2: export archives (Gallery.zip / Memories.zip)
|
||||
* were written under media_path/exports, and /media is a public ServeDir — so
|
||||
* anyone could GET /media/exports/Gallery.zip and download the whole gallery,
|
||||
* bypassing the ticket + export_*_ready gate. Exports now live OUTSIDE media_path
|
||||
* and are reachable only via the gated /api/v1/export/{zip,html} handlers (covered
|
||||
* by export.spec.ts). Here we assert the public path is dead.
|
||||
*/
|
||||
import { test, expect } from '../../fixtures/test';
|
||||
|
||||
const BASE = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101';
|
||||
|
||||
test.describe('Export — no public leak (CR2)', () => {
|
||||
test('archives are not reachable through the public /media path', async () => {
|
||||
for (const name of ['Gallery.zip', 'Memories.zip']) {
|
||||
const res = await fetch(`${BASE}/media/exports/${name}`);
|
||||
// 404 (not 200): a 200 here would mean the whole-gallery archive is
|
||||
// downloadable without any auth — the CR2 data-exposure regression.
|
||||
expect(res.status).toBe(404);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user