ScanMetadata is an open-source, privacy-first web utility designed to read, analyze, and losslessly strip EXIF metadata from photos directly inside the browser.
Traditional metadata removers require users to upload confidential images to a remote backend server, introducing severe privacy and data retention risks. The typical client-side workaround—rendering photos onto an HTML5 <canvas>—destroys original image quality through forced lossy re-compression.
ScanMetadata solves this problem by executing raw binary slicing using JavaScript's native ArrayBuffer and DataView APIs. No server uploads, zero quality degradation, and complete client-side sandboxing.
Try the production build online: https://scanmetadata.com/
- 100% Client-Side Execution: File inspection and data removal happen entirely within browser memory. No images, coordinates, or device logs are transmitted to any server.
- Lossless Binary Stripping: Unlike canvas-based solutions, ScanMetadata bypasses re-encoding. Quantization tables, color spaces, and DCT coefficients remain untouched—only metadata segments are removed.
- Granular EXIF Extraction: Inspects deep metadata structures, including GPS location data (latitude, longitude, altitude), camera make/model, shutter speed, ISO, aperture, focal length, lens serials, and original capture timestamps.
- Native Little & Big Endian Support: Accurately decodes both Intel (
II/0x4949) and Motorola (MM/0x4D4D) TIFF byte alignment headers without byte misinterpretation. - Zero Dependencies: Pure vanilla JavaScript implementations for binary reading and marker parsing.
JPEG files are structured into sequential binary markers, each prefixed with a two-byte delimiter 0xFF followed by a marker byte.
[SOI: 0xFFD8] ──> [APP1 / EXIF: 0xFFE1] ──> [DQT / SOF / DHT] ──> [SOS: 0xFFDA (Pixels)] ──> [EOI: 0xFFD9]
│
└── Sliced out losslessly in memory
The parser reads the first 128KB of the file via the HTML5 File API and checks for the mandatory JPEG Start of Image (SOI) marker 0xFFD8.
The engine traverses sequential markers (APP0 0xFFE0, APP1 0xFFE1, DQT 0xFFDB, etc.) by reading the 16-bit Big-Endian length descriptor immediately following each marker until reaching Start of Scan (SOS 0xFFDA).
When scrubbing metadata, rather than decoding pixel matrices, ScanMetadata creates a new Blob composed of discrete ArrayBuffer slices that omit the 0xFFE1 (APP1) payload entirely. The pixel data stream remains bit-for-bit identical to the original capture.
To strip EXIF markers from an image buffer losslessly without canvas re-encoding:
export async function sanitizeJpeg(file) {
const buffer = await file.arrayBuffer();
const view = new DataView(buffer);
// Validate SOI marker (0xFFD8)
if (view.getUint16(0, false) !== 0xffd8) {
return file; // Unsupported format, preserve original
}
const cleanChunks = [buffer.slice(0, 2)]; // Keep SOI
let offset = 2;
let cursor = 2;
while (offset < buffer.byteLength - 4) {
const marker = view.getUint16(offset, false);
// Stop traversal at Start of Scan (raw pixel stream)
if (marker === 0xffda || marker === 0xffd9) break;
const segmentLength = view.getUint16(offset + 2, false);
const totalBytes = 2 + segmentLength;
// Check for APP1 EXIF segment (0xFFE1)
if (marker === 0xffe1) {
if (offset > cursor) {
cleanChunks.push(buffer.slice(cursor, offset));
}
cursor = offset + totalBytes; // Skip over the EXIF block
}
offset += totalBytes;
}
if (cursor < buffer.byteLength) {
cleanChunks.push(buffer.slice(cursor));
}
return new Blob(cleanChunks, { type: file.type || "image/jpeg" });
}| Format | Parsing Support | Lossless Stripping |
|---|---|---|
| JPEG / JPG | Full EXIF, GPS, TIFF, JFIF | Supported (ArrayBuffer Slicing) |
| PNG | Textual chunks (tEXt, zTXt, iTXt) |
Planned |
| WebP | EXIF / XMP chunk detection | Planned |
ScanMetadata does not operate analytics trackers, telemetry collectors, or remote storage databases. Every computation is isolated within the user's browser sandbox.
Production Deployment: ScanMetadata Web App
This project is licensed under the MIT License.