Turn "accept-both-outcomes" documentation tests — which pass whether the app is
secure or vulnerable — into assertions that pin the secure behavior, and replace
fake-UUID authz tests that 404'd before ever reaching the ownership guard with
real cross-user resources.
file-upload-attacks:
- SVG-with-<script> → pin 400 (infer returns None for text → stored-XSS defense);
was [201,400], which accepted the vulnerable outcome.
- zero-byte → pin 400; path-traversal → pin 201 with UUID-derived storage and a
no-path-echo check (both were [201,400]).
- Fix the application/octet-stream rationale (no "application bypass" exists — the
handler ignores the declared type and keys off magic bytes).
authorization-deep (IDOR):
- B deleting A's comment: seed a real upload+comment as A, assert 403, assert the
comment survives, and assert the owner (A) still gets 204 — proving the 403 is
about identity, not a broken route. (Was a DELETE on the all-zeros UUID → 404
before the user_id guard, so authorization was never exercised.)
- Add B-deletes-A's-upload (403, countUploads unchanged) and B-edits-A's-caption
(403, caption intact) — the find_by_id_and_event + ownership guards.
export: pin the ZIP-download 404 (was [404,200] — a 200 is a data-exposure
regression) and split into the two real branches: not-yet-ready, and
ready-but-file-missing (new db.setExportZipReady helper).
All 21 assertions verified green against the live secure backend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The running test container had been built from an older tree; rebuilding it to
pick up current app code surfaced several tests that predated shipped
security/UX features:
- file-upload-attacks / rate-limit: assert the magic-byte rejection wording and
upload a real decodable JPEG (a zero-buffer is now rejected at the boundary).
- ddos: open SSE via the single-use /stream/ticket flow, not the dead ?token=.
- recover-page / join: the PIN field auto-submits on the 4th digit, so don't
race an explicit submit click against the ensuing navigation.
- gestures-doubletap / sheet-escape: target the lightbox by aria-labelledby and
anchor the radio accessible-name match at the start (gated dialog semantics).
playwright.config: camera/mic/clipboard are Chromium-only permissions (they threw
"Unknown permission: camera" on firefox/webkit and failed the test at context
creation); grant them per-Chromium-project. firefox-android drops the
unsupported isMobile flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>