fix(security): re-review follow-ups — drop HEIC/HEIF, fix stale comment

Address the two items from the adversarial re-review of batch-2.

- upload: remove image/heic + image/heif from ALLOWED_MEDIA. Neither the
  `image` crate nor the bundled ffmpeg 6.1 (Alpine 3.21 — HEIF demuxer
  only landed in ffmpeg 7.0) can decode them, so accepting them stored
  posts that never got a thumbnail. iOS Safari transcodes HEIC->JPEG on
  file-input selection, so this rejects only the rare HEIC-preserving
  path, now with a clear error instead of a silently broken post.
- data-mode-store: correct the stale "auth-gated" comment — the
  /original route is intentionally unauthenticated (UUID-as-capability)
  so it works from plain <img src> / <video src>.

Re-verified: cargo build (only the pre-existing middleware.rs warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
fabi
2026-06-29 19:48:46 +02:00
parent edcef0258c
commit 1bb58b59ad
2 changed files with 9 additions and 3 deletions

View File

@@ -21,13 +21,17 @@ const MAX_CAPTION_LENGTH: usize = 2000;
/// we trust, store, and hand to the compression pipeline — so a text-based payload /// we trust, store, and hand to the compression pipeline — so a text-based payload
/// (SVG/HTML/JS) can never be stored or served on-origin. Each entry maps to the /// (SVG/HTML/JS) can never be stored or served on-origin. Each entry maps to the
/// server-controlled file extension we persist the original under. /// server-controlled file extension we persist the original under.
///
/// HEIC/HEIF are deliberately excluded: the preview pipeline (`image` crate, and
/// the bundled ffmpeg 6.1) cannot decode them, so accepting them would store files
/// that never get a thumbnail. iOS Safari already transcodes HEIC→JPEG when a photo
/// is selected via a file input, so this rejects only the rare HEIC-preserving
/// upload path — with a clear error rather than a silently broken post.
const ALLOWED_MEDIA: &[(&str, &str)] = &[ const ALLOWED_MEDIA: &[(&str, &str)] = &[
("image/jpeg", "jpg"), ("image/jpeg", "jpg"),
("image/png", "png"), ("image/png", "png"),
("image/webp", "webp"), ("image/webp", "webp"),
("image/gif", "gif"), ("image/gif", "gif"),
("image/heic", "heic"),
("image/heif", "heif"),
("video/mp4", "mp4"), ("video/mp4", "mp4"),
("video/quicktime", "mov"), ("video/quicktime", "mov"),
("video/webm", "webm"), ("video/webm", "webm"),

View File

@@ -1,5 +1,7 @@
// Per-device "Datenmodus" — Saver loads compressed previews (default), Original loads // Per-device "Datenmodus" — Saver loads compressed previews (default), Original loads
// the full file via the auth-gated `/api/v1/upload/{id}/original` endpoint. // the full file via the `/api/v1/upload/{id}/original` endpoint. That route is
// intentionally unauthenticated (the UUID acts as the capability) so it works from
// plain `<img src>` / `<video src>` without an Authorization header.
// //
// Stored per-device in localStorage (not per-user) because data plans are a property // Stored per-device in localStorage (not per-user) because data plans are a property
// of the device the guest is currently holding, not their identity. // of the device the guest is currently holding, not their identity.