`driver.mjs` is a pipeline benchmark: synthetic images, uniform load, rate limits
off, and — the part that mattered — an unconstrained host, so the 1 GB app cap
was never exercised and the disk gate never fired. It could not have found
either of the two defects fixed in the preceding commits.
This harness differs in three ways that earn their keep:
REAL CONTENT. Uploads come from a pool of actual wedding photos and videos,
unedited, including the HEIC files and 25 MB frames the app is supposed to
REFUSE. Those refusals are the test, not noise to filter out — 152 of 932
attempts were refused, and the breakdown of WHY is the most actionable output.
PERSONAS. ~100 viewers and ~50 uploaders across nine behaviour profiles, six
device profiles, each with a join time and a session length. A casual guest who
posts four photos generates a completely different request mix than a
photographer dumping 130, and both differ from a kiosk holding one SSE stream all
night. A 37-case abuse suite covers malicious payloads, injection, cross-user
tampering, enumeration and the rate limiters.
RATE LIMITS STAY ON. `driver.mjs` disabled them because it ran every guest from
one IP. Almost every limit that matters is per USER, not per IP, and those are as
real for 150 synthetic sessions as for 150 phones — leaving them on is what lets
the abuse personas prove the defences work. The per-IP limits ARE distorted by
the single source address; that distortion is measured and reported rather than
configured away.
`docker-compose.sim.yml` reproduces the CX22 rather than asserting it: production's
per-service cpus/memory/cpu_shares verbatim, every container pinned to the SAME two
cores with `cpuset` so they genuinely contend, and a real loopback ext4 volume so
the app's own statvfs returns true numbers. Run the driver under `taskset` onto
other cores, or the load generator competes with the thing it measures.
`browser-check.mjs` covers what an HTTP driver structurally cannot: the SvelteKit
container, and whether the frontend ESCAPES the XSS caption the backend stores
verbatim. The backend stores captions raw by design, so the renderer is the entire
defence — and only a browser can prove the payload is inert. It reports
INCONCLUSIVE rather than PASS when the payload never reached the DOM, because a
check that renders nothing proves nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`.gitignore` carried an unanchored `media/`, commented "Media uploads (mounted volume in
production)". Production media lives in the `media_data` Docker volume and never
appears in the working tree, so that rule guarded nothing real — but unanchored it
matches a directory of that name at ANY depth, and the only one in the repo is
`e2e/fixtures/media/`.
Its entire practical effect was to keep every E2E fixture untracked. A fresh clone got
the specs and none of the images or videos they read, and
`.github/workflows/e2e.yml` does a plain `actions/checkout` and generates nothing — so
the committed CI job could not have run the upload, video, EXIF, quota, oversized-image
or export suites at all. It only ever looked green locally, where the fixtures survive
as untracked leftovers from whoever first created them.
The `origin` remote is git.mc02.dev rather than GitHub, so those workflows have most
likely never executed against this repo, which is consistent with nobody noticing. That
also makes this a live blocker for the standing "rotate the token and push" item: the
first time CI runs, it fails on ENOENT in a way that looks like a broken suite rather
than a missing file.
Anchors the pattern to `/media/` and commits the six fixtures (672 KB total). Verified
`e2e/fixtures/media` is the ONLY directory the old pattern matched, so nothing else
changes visibility.
Found while adding `sample-5s.mp4` for the video-poster work: the new fixture would
have been invisible to every other machine, which is how the existing ones got here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- compression_concurrency wired to boot config (state.rs) and removed as a
dead admin control.
- patch_config gains per-key integer/range validation so numerically-valid-but-
nonsensical values (-1/NaN/3.5) are rejected instead of silently reverting to
default at read time.
- clearAuth now also clears the display name (shared-device residual).
- e2e/Caddyfile.test mirrors prod security headers + the SSE encode-exclusion so
the test stack is representative and can catch header regressions.
- .gitignore: ignore root-level Playwright artifacts (test-results/, report).
- docs: USER_JOURNEYS §10 and SECURITY-BACKLOG updated to match the implemented
read-only-ban behavior and the export-path fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standalone SvelteKit project at frontend/export-viewer/ using
adapter-static. Replicates the live feed experience as a read-only
offline gallery: list/grid views, search with autocomplete, hashtag
filtering, lightbox with swipe navigation and comments.
Built output goes to backend/static/export-viewer/ for embedding
into the HTML export ZIP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>