ReviewOS

stacks/bench-review

assets: 15 of 24 PNGs in public/images are corrupt — PNG signature is missing its \r

#93
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Problem

Fifteen of the twenty-four PNG files under public/images/ are not decodable images. Their PNG signature is missing a byte:

avatar-1.png (corrupt):  89 50 4e 47 0a 1a 0a 00
logo.png     (healthy):  89 50 4e 47 0d 0a 1a 0a
                                     ^^ the 0d is gone

Every 0d 0a (CRLF) in the file has been collapsed to 0a (LF) — a text-mode / line-ending normalisation applied to a binary. file reports them as data rather than PNG image data, and browsers refuse to render them: an <img> fires error, while fetch() on the same URL returns 200 with content-type: image/png, which makes it look like a loading problem rather than a corrupt file.

They were already corrupt when first committed (bfdd8a32, May 2025) — git show bfdd8a32:public/images/avatars/avatar-1.png has the same broken signature. So this is not git mangling them on checkout and there is no .gitattributes rule to blame (the repo has none); the blobs themselves are bad, from before they entered this repository. Most look like stock Stacks scaffolding.

Affected

public/images/atomic-fx-diagram.png
public/images/atomic-ui-diagram.png
public/images/diagram.png
public/images/avatars/avatar-1.png … avatar-5.png
public/images/screenshots/contacts.png
public/images/screenshots/expenses.png
public/images/screenshots/inventory.png
public/images/screenshots/payroll.png
public/images/screenshots/profit-loss.png
public/images/screenshots/reporting.png
public/images/screenshots/vat-returns.png

Healthy, for contrast: bench/logo.png, logos/*.png, og-image.png, social.png, app/bench-review-app-icon.png, bench/hero-image-v1.png, screenshots/dashboard.png.

Impact today: none. None of the fifteen is referenced anywhere in resources/, app/ or config/ — I checked each filename. They are all unused scaffolding. They are copied into dist/ and shipped, so they are dead weight in the artifact, but nothing renders them.

Why file it anyway: it is a trap, and it caught me. avatars/avatar-1.png is the obvious thing to reach for as a placeholder or a test fixture — I used it as a fixture while verifying the admin photo thumbnails, spent a while establishing that my markup was fine, and only found the cause by running file on the asset. The next person doing the same will lose the same time. A broken file that looks present is worse than a missing one.

Acceptance criteria

  • Every .png under public/images/ passes file -b … | grep '^PNG image data'
  • Unreferenced scaffolding assets are deleted rather than replaced, unless something is actually going to use them (the five avatars are the only plausible keepers — a real placeholder set would be useful)
  • dist/ no longer ships undecodable images
  • Optional: a check in gate.ts or scripts/validate-release.ts asserting that every shipped image decodes, so a corrupt binary cannot enter the artifact unnoticed again

Found while implementing the admin photo thumbnails for #51.

10 days ago

Done in 15b25bd2.

  • Every .png under public/images/ passes file -b … | grep '^PNG image data'
  • The unreferenced scaffolding is deleted rather than replaced — all fifteen were stock Stacks assets, sample screenshots and five avatars nothing used. The avatars went too; a placeholder set is worth adding deliberately with valid files rather than keeping broken ones on the chance someone wants them.
  • dist/ no longer ships undecodable images — and a rebuild prunes deleted assets, which the gate confirmed by failing on the stale artifact before the rebuild and passing after.
  • Gate check 23: "all N shipped images decode" — every shipped png/jpeg/gif/webp/ico must carry a valid signature.

The check uses magic bytes rather than a real decode, deliberately: this class of damage is a mangled header, and a dependency-free check over every image in milliseconds is worth more here than a thorough one that needs a decoder. Falsified by stripping the same 0d out of logo.png — the check named the file, and passed again once restored.

Also worth recording for whoever adds images next: the failure is invisible from the outside. fetch() returns 200 with content-type: image/png while <img> fires error, so it reads as a loading bug. The gate now catches it at the point it enters the artifact.

Sign in to comment on this issue.