README du dépôt
exif-scrub
English · Français
Your photos say more about you than you think. Where they were taken, to within a few metres. Which camera, down to its serial number. When, to the second. This shows you exactly what is in there, then takes it out — without re-encoding a single pixel.
→ Drop a photo in, right here
No install, no upload, no account. The page is static and has no server to send anything to.
$ npx github:CedricPoint/exif-scrub holiday.jpg
holiday.jpg JPEG · 2.4 MB
● Where the photo was taken 48.8582, 2.2945, 324 m up
To within a few metres. On a photo taken at home, that is your address.
https://www.openstreetmap.org/?mlat=48.8582&mlon=2.2945#map=17/48.8582/2.2945
● Your particular camera, not just the model BodySerialNumber: F2LW4015HG7K
Every photo you publish can be tied to every other photo from the same body.
● The camera or phone Apple iPhone 15 Pro · iPhone 15 Pro back triple camera 6.765mm f/1.78
● When it was taken 2026:07:14 21:08:33
● A private block from the manufacturer MakerNote, 512 bytes
● An embedded preview a second, smaller copy of the image
Editors have been known to leave the old preview behind — the one from before you cropped.
carried in APP1 (exif) — 1.4 kB
remove it with exif-scrub holiday.jpg --write
Taking it out
exif-scrub photo.jpg --write # writes photo.clean.jpg beside it
exif-scrub *.jpg --in-place # overwrite, for a whole folder
exif-scrub photo.jpg --out ./safe # collect the copies somewhere
exif-scrub photo.jpg --json # the whole report, for a script
The pixels are never touched. A JPEG is a handful of marked segments followed by the compressed image; cleaning it copies the file across without the segments that hold metadata. Nothing is decoded, nothing is re-encoded, no quality is lost. The test suite asserts it the strict way: scrub a photo that had metadata injected, and you get back the original file byte for byte.
That is the difference from "re-saving it in an editor", which quietly costs you a generation of quality every time.
What it keeps, on purpose
| kept | removed |
|---|---|
| the colour profile (ICC), the Adobe colour block, the JFIF header | EXIF, GPS, XMP, IPTC, maker notes, comments |
| the orientation flag — dropping it turns photos sideways | the embedded preview thumbnail |
in PNG: iCCP, gAMA, sRGB | in PNG: tEXt, zTXt, iTXt, eXIf, tIME |
A colour profile is not metadata about you, it is how the picture is meant to
look. --color drops it too, when you want the smallest possible file and do
not care.
Nothing leaves your machine
- No network code exists in what ships. Not disabled, not opt-out: absent.
A CI job greps
src/,web/and the page forfetch,XMLHttpRequest,sendBeacon,WebSocketand hard-coded URLs, and fails the build if one turns up. There are no dependencies to sneak one in either, and CI checks that too. - The browser page is the same code, imported straight from
src/. There is no separate "web version" that could behave differently. - The only outbound click is the map link, which hands the coordinates to OpenStreetMap, because that is what you asked it to do.
As a library
import { inspect, scrub } from 'exif-scrub';
const report = inspect(bytes); // { findings, coordinates, carriers, … }
if (report.findings.length) {
const { bytes: clean } = scrub(bytes);
}
Works unchanged in Node and in a browser: it takes a Uint8Array and returns
one, and touches nothing else.
Reading hostile files
A photo is an untrusted file, and EXIF is a structure made almost entirely of offsets that point wherever the file says. So the reader bounds-checks every read, caps the number of entries in a directory, refuses absurd lengths, and keeps a set of visited offsets so a directory that points at itself cannot loop. A corrupt or malicious file produces a partial answer and a warning — never an exception, never a hang. There are tests that truncate a valid block at every offset, point a directory at itself, and claim four billion values for one tag.
Tests
31, over real files: the JPEG and PNG fixtures were encoded by a browser, complete with the colour profile a browser attaches, and the EXIF is written by a separate writer in the test folder — so the reader is checked against bytes it did not produce.
npm test
npm run check:offline
License
MIT