Commit Graph

5 Commits

Author SHA1 Message Date
fabi
41ac742af8 test+docs: tighten H1 assertions, cover secret/XFF edges, document boot & SSE-ban
Re-review follow-ups (non-blocking quality items):

Tests:
- moderation H1 specs now assert exact `→ 403` instead of bare .rejects.toThrow(),
  so a spurious 500 can no longer masquerade as "revocation worked".
- config: added case-insensitivity (upper/mixed-case placeholder) and the
  len==32/31 boundary cases for validate_secrets.
- rate_limiter: added the trailing-comma empty-entry case for client_ip (must
  fall back, not return "").
  (Backend unit tests: 35 pass.)

Docs:
- README: note that with APP_ENV=production a placeholder .env makes the app
  refuse to boot and Caddy wait unhealthy — reason is in `docker compose logs app`.
- SECURITY-BACKLOG + sse.rs comment: document that a mid-session ban does not
  tear down an already-open SSE stream (new tickets are blocked; low blast radius).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 07:14:33 +02:00
fabi
91ff961850 fix(security): medium/low — event-lock, atomic config, a11y, diashow, hygiene
- social: likes/comments rejected on a closed event (e2e proven).
- admin: patch_config validates-then-writes atomically; char-based length.
- hashtag/comment models: atomic tag writes in edit + comment paths.
- Modal: inert the background (modal-inert action) for screen readers.
- sse.ts: idempotent visibilitychange listener (no duplicate reconnects).
- diashow: videos advance on `ended` (transitions + page wiring).
- docs/hygiene: README clone-case fix; removed stale committed .env.test and
  gitignored it; docker-compose.dev.yml tidy.

Left documented as accepted-risk per plan: PIN-persist-after-logout,
UUID-reachable hidden uploads, DECISION-media-auth.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:25:58 +02:00
fabi
f08d858281 fix(security): high — live authz, XFF, SSE buffering, atomicity, pagination
H1: auth extractor re-reads the live user row — trusts the DB role (not the
    JWT claim) and rejects banned users mid-session. e2e proves a demoted
    host loses powers and a banned host is locked out and can't self-unban.
H2: client_ip takes the right-most XFF hop (the one Caddy appends); spoofed
    left-most entries are ignored, restoring IP-based throttles.
H3: Caddy excludes /api/v1/stream from `encode` so SSE isn't buffered.
H4: upload — quota increment + row insert + hashtag links now one txn.
H5: feed keyset pagination tiebroken on (created_at, id) + composite index
    (migration 010); feed_delta bounded with LIMIT.
H7: LightboxModal keys its comment load off upload.id, ending the
    SSE-driven refetch storm.
H8: CSP via SvelteKit kit.csp (svelte.config.js) hardens the localStorage
    token model against injection.

Migration 010 also adds the latent comment/comment_hashtag indexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:25:50 +02:00
fabi
498b4e256b fix(security): critical — repair PIN reset + enforce real prod secrets
C1: reset_user_pin wrote to a non-existent column (pin_failed_attempts);
    the real column is failed_pin_attempts, so every PIN reset 500'd. Fixed
    the column name; new e2e (pin-reset.spec.ts) proves a reset returns a
    usable PIN and the target can recover with it.

C2: config.rs::validate_secrets now rejects placeholder-ish secrets
    (change_me/dev_secret/placeholder), enforces len>=32 in prod, and
    requires a real ADMIN_PASSWORD_HASH. docker-compose.yml sets
    APP_ENV=production so the guard actually runs. Corrected the false
    "fixed" claim in SECURITY-BACKLOG.md. .env.example documents the rule.

Riders in these files (documented here since git can't split hunks):
- host.rs also carries the event-scoped ban_user fix and the
  close_event/open_event no-op broadcast guard (medium).
- docker-compose.yml also adds ORIGIN (H6), app/frontend healthchecks with
  Caddy waiting on health, and per-service memory limits (medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:25:39 +02:00
MechaCat02
e42d8a92a1 feat(e2e): Playwright suite — 134 tests across 9 spec areas + UA matrix
Adds an end-to-end Playwright test suite under e2e/ that spins up an
isolated docker-compose stack (Postgres :55432, Caddy :3101, backend with
EVENTSNAP_TEST_MODE=1, SvelteKit adapter-node frontend) and exercises the
SvelteKit app against the real Rust backend.

Phase 1 — happy paths covering every documented USER_JOURNEYS.md flow:
  01-auth/      join, recover, admin login, leave event, PIN lockout
  02-upload/    gallery picker (API path), rate-limit + admin toggle
  03-feed/      like/comment SSE, filters, SSE reconnect on visibility
  04-host/      event lock API, ban/unban, promote
  05-admin/     config validation, foundational authz guards, stats
  06-export/    /export status + download stub
  __smoke/      cross-UA happy-path (runs on every UA project)

Phase 2 — adversarial + browser chaos:
  07-adversarial/  XSS payloads (6 × display name path), SQLi shapes,
                   length / encoding / RTL override / NUL byte;
                   file-upload boundaries (ELF body claimed as JPEG,
                   oversize vs max_image_size_mb, zero-byte, NUL
                   filename, path-traversal, SVG-with-script);
                   JWT alg:none, signature/payload tamper, expired
                   session, PIN brute-force (serial + parallel),
                   admin password brute-force; deep authz (cross-user
                   delete, banned user across like/comment/feed-read,
                   host→admin escalation); small-scale DDoS (20× /join,
                   10MB comment body, 10 concurrent SSE).
  08-browser-chaos/ localStorage / sessionStorage / cookie purge,
                    IndexedDB drop mid-session, offline → reconnect,
                    slow-3G, 503 flakes, 429 with no retry storm,
                    multi-tab same/different user, no-JS, hostile CSS,
                    clock skew ±1h / -2d, localStorage quota exhausted.

Phase 3 — mobile gestures (runs only on chromium-mobile / Pixel 7):
  09-mobile/    touch-target ≥44px audit, env(safe-area-inset-bottom)
                structural check, long-press (FeedListCard → ContextSheet,
                quick-tap negation, click-suppression), double-tap
                (feed card like + lightbox heart-burst, via synthetic
                pointer events to bypass the first-tap-fires-click trap),
                viewport reflow (portrait/landscape/narrow/phablet),
                plus fixme stubs documenting planned gestures (swipe
                lightbox L/R, swipe-down dismiss, pull-to-refresh,
                long-press-comment).

Cross-UA matrix (chromium-engine projects run @smoke only):
  chromium-pixel7, chromium-galaxy-s22, samsung-internet (Samsung UA
  emulation on Galaxy viewport), edge-android, plus webkit-iphone,
  chrome-ios, firefox-android, firefox-desktop — the latter four need
  libavif16 on the host (Playwright dep) but the configs are in place.

Infrastructure:
  - fixtures/test.ts central test.extend (api, db, adminToken, guest,
    host, signIn). Per-test DB truncate via the dev-only POST
    /admin/__truncate route, gated by EVENTSNAP_TEST_MODE=1.
  - helpers/sse-listener.ts, helpers/upload-client.ts (Node-side
    multipart for adversarial file-upload tests + JPEG/PNG/ELF magic
    constants), helpers/touch.ts (longPress / doubleTap / swipe /
    inlineStyle / computedStyle).
  - 10 page objects covering every route + UploadSheet/Lightbox.
  - global-setup waits for /health, logs in admin, disables every
    rate-limit and quota toggle.
  - .github/workflows/e2e.yml: PR check runs chromium-desktop + the
    smoke matrix in parallel, uploads playwright-report/ and traces on
    failure.

Findings the suite surfaces as live `[finding]` warnings (not silenced):
  1. /admin/login has no rate-limit or lockout (bcrypt cost only).
  2. PIN-attempt counter races under parallel /recover requests.
  3. Zero-byte uploads pass /api/v1/upload.
  4. SVG-with-script can pass the magic-byte check (consider CSP +
     X-Content-Type-Options on /media/*).

Stack-internal docs live in e2e/README.md (UA tier table, Samsung
Internet escalation tiers A/B/C, debugging tips, roadmap).

Final tally: 134 passed / 0 failed / 9 skipped (test.fixme stubs for
not-yet-shipped gestures and one UI-upload-flow investigation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:37:11 +02:00