Sanitize and bound image evidence at the server upload boundary #983

Closed
opened 2026-08-16 19:24:18 +00:00 by timmy · 0 comments
Owner

Product/security gap

Mobile evidence now flows through issue, pull-request, notification, Search, and offline-delivery journeys, but the authenticated server boundary only checks a claimed MIME signature and a 2 MiB compressed-byte cap. A direct client can therefore bypass browser re-encoding, retain EXIF/GPS metadata, submit malformed/truncated images, or force excessive decoded-pixel work upstream.

Evidence on origin/main 21eb223:

  • src/main.py:763-773 accepts signature-prefixed bytes without decoding.
  • src/main.py:794-796 forwards the same bytes unchanged.
  • Browser-only metadata stripping shipped in #949 and is not an authoritative boundary.
  • Historical issue-title and recent-commit scans show no server-side image decode/sanitization slice.

Acceptance criteria

  • Decode PNG, JPEG, and WebP at the server boundary and reject malformed, truncated, MIME-mismatched, animated, or over-12-megapixel images before Gitea upload.
  • Apply EXIF orientation, remove metadata, and deterministically re-encode static pixels in the claimed format under the existing 2 MiB limit.
  • JSON/base64 and multipart upload paths use the same sanitizer.
  • CPU-heavy decoding runs outside the event loop.
  • Behavioral tests prove metadata removal, orientation, malformed/oversized rejection, and endpoint forwarding of sanitized bytes; full suite and Actions remain green.
## Product/security gap Mobile evidence now flows through issue, pull-request, notification, Search, and offline-delivery journeys, but the authenticated server boundary only checks a claimed MIME signature and a 2 MiB compressed-byte cap. A direct client can therefore bypass browser re-encoding, retain EXIF/GPS metadata, submit malformed/truncated images, or force excessive decoded-pixel work upstream. Evidence on origin/main `21eb223`: - `src/main.py:763-773` accepts signature-prefixed bytes without decoding. - `src/main.py:794-796` forwards the same bytes unchanged. - Browser-only metadata stripping shipped in #949 and is not an authoritative boundary. - Historical issue-title and recent-commit scans show no server-side image decode/sanitization slice. ## Acceptance criteria - Decode PNG, JPEG, and WebP at the server boundary and reject malformed, truncated, MIME-mismatched, animated, or over-12-megapixel images before Gitea upload. - Apply EXIF orientation, remove metadata, and deterministically re-encode static pixels in the claimed format under the existing 2 MiB limit. - JSON/base64 and multipart upload paths use the same sanitizer. - CPU-heavy decoding runs outside the event loop. - Behavioral tests prove metadata removal, orientation, malformed/oversized rejection, and endpoint forwarding of sanitized bytes; full suite and Actions remain green.
timmy self-assigned this 2026-08-16 19:24:18 +00:00
timmy closed this issue 2026-08-16 19:38:35 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stackchain/stackchain-dashboard#983
No description provided.