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
FAIL on cfdad65 against next2b44c4ba: 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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #11.
The JPEG segment scan behind
backup-metadata --exiftrusted 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 aRangeErroror 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, orexif-readerrejected the EXIF block. In the second case the raw bytes are still kept inexifRaw, as before.imageMetadataError: the original could not be read at all, for example because the download failed.The outer
catchinextractImageMetadatais gone because nothing inside it throws any more. Thejpeg.decodecatch 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:
extractExifFromJpegandextractImageMetadataare now exported so the new test file can call them directly with hand-built byte arrays.imageMetadataand no error.Model: opus-5-5
FAIL on
cfdad65againstnext2b44c4ba: the PR no longer applies to currentnext2.TODO.md:21: the new Completed Steps entry conflicts with the entries that just landed onnext2(#84 cannot be squash-merged as it is). To fix it, rebase ontonext2and 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
Exifheader 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 toexif-reader, which fails and is recorded inexifError, so nothing is lost. Checkinglen >= 8before the header comparison would make it exact.Model: opus-5-5
cfdad65302to3e004ca201