ScourMetadata Scrubber
What it does for you
In-browser, lossless metadata scrubber for JPEG, PNG and WebP with an EXIF/GPS/C2PA inspector and zip export, written byte by byte with no libraries.

Screenshots

In more detail
Drop in photos and Scour strips out the hidden data they carry: where they were taken, when, which camera, who edited them. It runs in your browser, so the photos never get uploaded, and it doesn't re-encode the image, so not a single pixel changes. You can scrub a batch and download them one by one or as a zip.
Honest limits
No HEIC or AVIF support, and that's what most phones produce now. Unknown JPEG segments and WebP chunks are treated as metadata and dropped, which is an assumption. ICC color profiles are dropped by default with a toggle to keep them. Tested with generated fixtures and canvas samples, not a big pile of real phone photos.
How I built it
Built on the bulk one-shot night by Claude Code, about 0.4 hours, from the shared brief. I set the scope: lossless, byte level, no libraries, three formats (JPEG, PNG, WebP), awareness of C2PA provenance data, and zip export. I also set the order: tests first.
Claude Code wrote the JPEG segment walker, the PNG and WebP chunk walkers, an EXIF parser for both byte orders, CRC32, a hand-written zip writer and reader, and 175 tests. It also made the demo honest. The browser draws three scenes on a canvas, then the same tested core injects GPS, XMP, IPTC, a C2PA stub, and trailing data into the real encoded files.
The first screenshots showed an empty app. canvas.toBlob never resolved under headless Chrome's virtual time, so the samples switched to a synchronous path. The screenshot loop also caught a hidden attribute losing to a display: grid rule, a drop zone rendering inline, and a class name clash between the GPS pill and the GPS card. Three test assertions were wrong on the first run too, including one that exposed a real gap: the PNG walker quietly accepted a truncated chunk header. Now it throws.
What I learned
- A JPEG scan has no length field. After the start-of-scan marker you have to scan the bytes for an
0xFFthat isn't followed by0x00or a restart marker. Doing it that way also handles progressive files and exposes data hidden after the end-of-image marker. - C2PA lives somewhere different in each format: APP11 in JPEG, a
caBXchunk in PNG, aC2PAchunk in WebP. A scrubber that only knows EXIF leaves it in place. - Removing WebP metadata is a three-part edit: drop the chunk, clear its flag bit, rewrite the file size. Miss one and strict decoders reject the file.
- Stripping the orientation tag visibly changes how a photo displays even though no pixel changed. So the tool warns instead of pretending.
- The best test for a binary scrubber is writing the builders too: inject every kind of metadata into a clean file, scrub it, and require a byte-for-byte match with the clean file.
What carried forward
The inject-then-scrub test is the same move as #11's decoder: write the inverse, then test the round trip. #13 hit the same [hidden] bug that night, and both builds landed on the same global fix. Same tested-core pattern too, with scrub.js shared by the page and verify.mjs.
If I took it further
I'd add a lossless "bake the orientation" option for JPEG so stripping the tag never changes how the photo looks, and extend the walkers to HEIC and AVIF.
#017 Built with Claude Code September 19, 2026 175 automated checks