Postgres refuses a NUL in a TEXT column outright — `invalid byte sequence for
encoding "UTF8": 0x00` — and that arrived here as an anonymous `sqlx::Error`,
became `AppError::Internal`, and returned a 500. The photo went with it: the
transaction rolls back with the file already streamed to disk, so the guest
loses the upload as well as the caption, with nothing in the message telling
them why.
Only `display_name` was validated for control characters; `caption`, the hashtag
CSV and `comment.body` were length-checked alone.
Rejected at the edge with a 400 instead, and deliberately narrower than
`validate_display_name`: a caption legitimately carries newlines and emoji, and
every control character except NUL stores and renders harmlessly, so this
refuses exactly the byte that cannot work. Rejected rather than stripped —
silently rewriting what a guest wrote is the worse failure, and no real client
emits a NUL by accident.
Found by the event simulation's abuse suite; it was the only input of 37 that
produced a 5xx.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>