# Security & Hardening Backlog
Tracks the deliberately-deferred items from the 2026-06-27 audit and its review passes. The
Critical→Medium findings and the cheap "bucket 🅰" LOWs are fixed on
`fix/audit-2026-06-27-critical-medium`. What remains is recorded here so the decisions are
explicit, not forgotten.
## 🅱 Worth a tracked ticket (real, not one-liners)
- **Moderation UI gap** — the backend `DELETE /host/upload/{id}` and `DELETE /host/comment/{id}`
endpoints have **no frontend caller**, so a host cannot remove a guest's content from the UI.
This is a functional hole, not polish. Needs a host-facing "remove" action wired to those
endpoints (a `ContextSheet` action gated on host role).
- **Feed reactivity** — a single global SSE event (`like-update`/`new-comment`/`upload-processed`)
triggers `loadFeed(true)`, which full-replaces the list with the first 20, collapsing a
scrolled feed; and owner-deleted uploads aren't broadcast to other clients (only host deletes
are). Patch the affected item from the SSE payload instead of full-reloading; emit
`upload-deleted` from the owner `delete_upload` path too.
- **Media HMAC domain separation** — the signed-media tokens reuse `jwt_secret` as the HMAC key.
Correct today (different message structure), but a dedicated derived key
(`HKDF(jwt_secret, "media-url")`) would isolate the domains so a future change to one can't
weaken the other.
- **Quota mount-detection + low-disk guard** — `compute_storage_quota` picks the disk via
`starts_with` (root is a wildcard prefix → can match the wrong device) and there's no hard
min-free-space precheck when the quota is disabled. Use longest-prefix match; add an
unconditional 507/429 when free space is critically low.
## 🅲 Consciously won't-fix at ~100-guest single-box scale
Diminishing returns vs. the deployment's actual threat model. Revisit only if the scale or
tenancy model changes.
- Rate-limiter `HashMap` key LRU/cap (attacker-chosen `recover:{ip}:{name}` keys accumulate up to
the 24h prune ceiling) — bounded and pruned; not worth an LRU.
- "Last host" / host↔host role-churn guard — operational, low blast radius.
- Performance micro-indexes (`idx_like_user_upload`, comment pagination index) — current queries
are sub-ms at this row count.
- Optimistic-like in-flight guard, ownership-snapshot-at-mount, assorted copy tweaks — UX polish.
## By-design notes (documented, not bugs)
- **Banned user retains ≤24h media access via already-held signed URLs.** The media gateway
authorizes by *signature + uploader visibility*, not requester identity, and signed URLs are
time-boxed (24h, bucketed). A banned/revoked-session user cannot mint new URLs (every API call
401/403s) but can replay URLs they already hold until expiry — only for content they already
saw. Accepted: tightening this would require per-request identity on every `
` load, which
the `
`-can't-send-a-Bearer constraint precludes. Shorten the TTL in `media_token.rs` if a
stricter bound is ever needed.
- **Two DB queries per *cold* media fetch** (`upload` row + uploader row). Mitigated by browser
caching and the stable bucketed URL (so warm fetches don't hit the backend at all). Could be one
JOIN if it ever shows up in profiling.