Harden the JPEG EXIF scan against malformed input #84

Merged
clawbot merged 1 commits from issue-11-exif-hardening into next2 2026-09-23 02:28:15 +02:00
Collaborator

Fixes #11.

The JPEG segment scan behind backup-metadata --exif trusted length fields from the file. It now checks every segment length against the bytes that remain, and it stops on lengths under 2. A truncated or corrupt original can no longer throw a RangeError or step through the file reading garbage as markers.

Failures are now recorded in the per-file JSON instead of being dropped:

  • imageMetadata.exifError: the scan found a malformed segment, or exif-reader rejected the EXIF block. In the second case the raw bytes are still kept in exifRaw, as before.
  • imageMetadataError: the original could not be read at all, for example because the download failed.

The outer catch in extractImageMetadata is gone because nothing inside it throws any more. The jpeg.decode catch stays, with a comment: most originals that are not JPEGs (PNG, HEIC, video) fail that decode, and that is expected.

Things the diff does not show:

  • extractExifFromJpeg and extractImageMetadata are now exported so the new test file can call them directly with hand-built byte arrays.
  • A file that is not a JPEG still produces no imageMetadata and no error.
  • Judgement call: a JPEG that ends before the start-of-scan marker, or that has a non-marker byte where a segment should begin, is reported as malformed. The old code treated both as having no EXIF.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/quak/issues/11. The JPEG segment scan behind `backup-metadata --exif` trusted length fields from the file. It now checks every segment length against the bytes that remain, and it stops on lengths under 2. A truncated or corrupt original can no longer throw a `RangeError` or step through the file reading garbage as markers. Failures are now recorded in the per-file JSON instead of being dropped: - `imageMetadata.exifError`: the scan found a malformed segment, or `exif-reader` rejected the EXIF block. In the second case the raw bytes are still kept in `exifRaw`, as before. - `imageMetadataError`: the original could not be read at all, for example because the download failed. The outer `catch` in `extractImageMetadata` is gone because nothing inside it throws any more. The `jpeg.decode` catch stays, with a comment: most originals that are not JPEGs (PNG, HEIC, video) fail that decode, and that is expected. Things the diff does not show: - `extractExifFromJpeg` and `extractImageMetadata` are now exported so the new test file can call them directly with hand-built byte arrays. - A file that is not a JPEG still produces no `imageMetadata` and no error. - Judgement call: a JPEG that ends before the start-of-scan marker, or that has a non-marker byte where a segment should begin, is reported as malformed. The old code treated both as having no EXIF. Model: opus-5-5
clawbot added the needs-review label 2026-09-23 02:09:54 +02:00
clawbot self-assigned this 2026-09-23 02:09:54 +02:00
Author
Collaborator

FAIL on cfdad65 against next2 b44c4ba: the PR no longer applies to current next2.

  • TODO.md:21: the new Completed Steps entry conflicts with the entries that just landed on next2 (#84 cannot be squash-merged as it is). To fix it, rebase onto next2 and keep both entries, with the new 2026-09-23 entry on top. Nothing else needs to change: the code and tests are fine once the conflict is resolved.

Non-blocking: an APP1 segment whose length is under 8 still has its Exif header compared against the bytes of the next segment (src/metadata-backup.ts:49). When those bytes happen to match, an empty or short buffer goes to exif-reader, which fails and is recorded in exifError, so nothing is lost. Checking len >= 8 before the header comparison would make it exact.

Model: opus-5-5

FAIL on `cfdad65` against `next2` `b44c4ba`: the PR no longer applies to current `next2`. - `TODO.md:21`: the new Completed Steps entry conflicts with the entries that just landed on `next2` (https://git.eeqj.de/sneak/quak/pulls/84 cannot be squash-merged as it is). To fix it, rebase onto `next2` and keep both entries, with the new 2026-09-23 entry on top. Nothing else needs to change: the code and tests are fine once the conflict is resolved. Non-blocking: an APP1 segment whose length is under 8 still has its `Exif` header compared against the bytes of the next segment (`src/metadata-backup.ts:49`). When those bytes happen to match, an empty or short buffer goes to `exif-reader`, which fails and is recorded in `exifError`, so nothing is lost. Checking `len >= 8` before the header comparison would make it exact. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-09-23 02:20:12 +02:00
clawbot added needs-rebase and removed needs-rework labels 2026-09-23 02:24:24 +02:00
clawbot added 1 commit 2026-09-23 02:25:29 +02:00
The segment scan behind `backup-metadata --exif` now checks every
segment length against the bytes that remain and stops on lengths
under 2, so a truncated or corrupt original can neither throw nor loop.
A malformed or unparseable EXIF segment is recorded as
`imageMetadata.exifError`, and a failure to read the original as
`imageMetadataError` in the per-file JSON, instead of the field being
silently left out. Tests use short hand-built byte arrays.

Model: opus-5-5
clawbot force-pushed issue-11-exif-hardening from cfdad65302 to 3e004ca201 2026-09-23 02:25:29 +02:00 Compare
clawbot added needs-review and removed needs-rebase labels 2026-09-23 02:25:32 +02:00
clawbot merged commit 52f58f5d2b into next2 2026-09-23 02:28:15 +02:00
clawbot deleted branch issue-11-exif-hardening 2026-09-23 02:28:15 +02:00
Sign in to join this conversation.