Commit Graph

2 Commits

Author SHA1 Message Date
fabi
c48d43f5b3 test(e2e): cover the untested security lifecycles (PIN reset, logout-all, ban gating, caption)
Closes the coverage gaps the audit flagged and I verified were real:

  pin-reset-lifecycle.spec.ts (new): the whole "I forgot my PIN" journey (§4) had only its
    403 gate tested. Now: the always-204 non-enumeration contract (unknown name looks
    identical to known); dedup (ON CONFLICT); admins are EXCLUDED from the queue; the
    3-per-15-min throttle; and a host reset REVOKES the target's sessions (the pre-reset JWT
    401s afterward) and clears the request. Sessions are validated per-request against the
    DB, so the revoke is observable.

  logout-everywhere.spec.ts (new): DELETE /sessions ("sign out everywhere") had ZERO tests.
    Proves it revokes ALL of the caller's sessions across two devices (both tokens 401 after),
    not just the current one, and doesn't touch another user's sessions.

  media-gating: the file claimed delete AND ban-hide both revoke preview access, but only
    delete was exercised. Add the ban case — a banned uploader's gated preview 404s, same as
    a takedown. (Thumbnail shares the identical find_by_id_visible gate; the seed fixture
    produces no thumbnail derivative, so preview is the honest thing to assert.)

  export caption test: an edit after release regenerates the viewer (epoch bumps) while the
    ZIP is carried forward, not rebuilt — guards the fix in the previous commit.

Helpers: api.listPinResetRequests; db.countSessionsForUser / countPinResetRequestsForUser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:48:31 +02:00
fabi
a4b2c5bd1c fix(security): harden authz, media gating, recover, SSE, deps
Security audit follow-up. No Critical/High existed; these close the
Medium/Low findings.

F1 [Med] Host privilege boundary: a host could demote a peer host to
  guest and then ban / PIN-reset (→ account takeover via /recover) them,
  because the ban/pin-reset peer guards key off the target's *current*
  role. set_role now blocks a non-admin from demoting a host.

F2 [Med] Moderation now revokes preview/thumbnail access: they are served
  through visibility-checked aliases (/api/v1/upload/{id}/{preview,
  thumbnail}) that filter soft-deleted / ban-hidden uploads, and direct
  /media/previews|thumbnails is 404-blocked. Feed emits the gated URLs;
  Caddy keeps them privately cacheable (max-age=300, short so moderation
  revokes promptly — the live feed already evicts cards via SSE). Uses a
  lean find_visible_media() (paths+mime only) on the media hot path.

F3 [Med/Low] npm audit fix: svelte 5.55.1→5.56.4 (SSR-XSS advisories),
  devalue 5.6.4→5.8.1 (DoS). Prod deps: 0 vulnerabilities.

F4 [Low] /recover no longer leaks account existence: unknown display name
  returns the same 401 as a wrong PIN, and runs a dummy bcrypt verify so
  timing matches — closing the enumeration + timing oracle.

F5 [Low] SSE streams re-validate the session every ~60s and close once it
  is logged out / expired, instead of running until client disconnect.

F6 [Info] X-Content-Type-Options: nosniff set at the app layer on all
  media responses (defense-in-depth alongside the edge).

Tests: new e2e specs for F1 (host cannot demote peer host), F2 (preview
gated + 404 after delete + direct /media blocked), F4 (uniform 401).
40 backend unit tests + 182 e2e passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 06:14:00 +02:00