iOS: camera photos never upload — rejected as unsanitized metadata, surfaced to the user as a timeout
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
Self-hoster report, measured against a live relay (`buzz-relay 0.2.1`). Same root cause as #5415 and #5689, but on the path that matters most for field/mobile users: **still images from the phone camera**. Filing separately because neither existing issue covers it and the user-visible symptom is different.
## Symptom
On iOS, sending a **screenshot** in a channel works. Sending a **photo taken with the camera** never arrives — the app shows a **timeout**, never an error message. Same device, same network, same channel, seconds apart.
The timeout is misleading: the upload is being rejected, not lost. This is the same "client swallows the reason" behaviour as #5193.
## Minimal reproduction (relay side)
Two files, **identical image data**, 42 bytes apart. Only difference is an `APP1`/`Exif` segment:
```bash
# 1. clean JPEG, no metadata
python3 -c "from PIL import Image; Image.new('RGB',(1400,1400)).save('clean.jpg','JPEG',quality=85)"
buzz upload file --file clean.jpg
# -> 200 OK, stored and served
# 2. same JPEG + a minimal APP1/Exif segment (Make="Apple") injected after SOI
buzz upload file --file with-exif.jpg
# -> relay error 422: {"error":"media contains metadata or a non-canonical metadata channel"}
```
The relay policy is working as designed — `crates/buzz-media/src/validation.rs` enforces that media arrives metadata-free, and the test suite is explicit that sanitizing is the **client's** job:
```
validation.rs: panic!("rejected iOS UIKit-sanitized {name} fixture: {error}")
validation.rs: "accepted unsanitized iOS UIKit {name} fixture"
validation.rs: "produced by the client sanitizer is permitted"
```
So the defect is on the client side: **the iOS app is sending camera photos without running that sanitizer.**
## A second, separate gap: HEIC is not an accepted type
The accepted image types in `validation.rs` are:
```
image/jpeg image/png image/gif image/webp image/svg+xml
```
`grep -ri "heic\|heif\|avif" crates/buzz-media/src/` returns **zero matches** — not in the accepted list, not in the tests, not in fixtures.
Since iOS 11, **HEIC is the default camera format** on iPhone. So a camera photo hits two problems at once: an unsupported container *and* EXIF. A correctly-working client would transcode and strip in one step; whatever the app is doing today, it does neither successfully.
We are not arguing HEIC should be accepted server-side — stripping/transcoding on the server means the GPS coordinates were already in server memory and logs, which defeats the point of the policy. This is only to note that the client has two conversions to get right, not one.
## Why the policy is right and the reporting is wrong
Worth stating plainly, because it would be easy to "fix" this in the wrong direction: **rejecting metadata is a genuinely good design.** A phone photo carries GPS coordinates, device model, and timestamp in EXIF. A relay that refuses it prevents a field technician's location from leaking alongside a photo of equipment. Please don't relax the policy.
The problem is entirely that:
1. the clients produce media their own relay rejects (#5415 for video on Android, this for images on iOS), and
2. the failure is presented to the user as a timeout instead of the actual reason (#5193).
Point 2 is the expensive one for adoption. A user who sees "timeout" concludes the app is broken. A user who sees "this photo carries location data that can't be uploaded" understands, and might even appreciate it.
## Environment
- Relay: self-hosted, `buzz-relay 0.2.1`, Docker Compose (Postgres + Redis + MinIO), behind Traefik
- Client: iOS app from the App Store
- Not a size limit: a 5.6 MB PNG uploads via `buzz-cli` in 6.4s against the same relay; `BUZZ_MAX_IMAGE_BYTES` defaults to 50 MB
## Related
- #5415 — mobile video transcode produces MP4s the relay rejects (same root cause, video/Android)
- #5689 — `buzz upload file` never strips image metadata (same root cause, CLI)
- #5193 — upload fails and the client swallows the reason (same reporting defect)
Contributor guide
Research direction
The relay-side policy is defined and tested in crates/buzz-media/src/validation.rs; read its accepted types and sanitizer fixtures first. Then trace the iOS camera upload path and failure handling, using #5415, #5689, and #5193 as context; done means camera photos are converted and sanitized before upload, and relay rejection reasons reach the user instead of a timeout.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, rust
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100