Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ All notable changes will be documented here. The project intends to follow seman

### Added

- Bounded WebP RIFF/chunk parsing with declared-size, padding, VP8X, and chunk-count validation.
- WebP EXIF, XMP, and ICCP container inspection with image/alpha/animation distinction.
- Deterministic WebP Privacy Clean with RIFF-size and VP8X metadata-flag repair.
- WebP metadata verification, ICC/unknown preservation, and canonical malformed/padding coverage.
- Deterministic JPEG Privacy Clean for whole EXIF, XMP, Photoshop/IPTC, and COM segments.
- Checked single-allocation JPEG reconstruction preserving ICC, unknown APP, structural, scan, and trailing bytes.
- Structured JPEG presence/absence verification and typed unsupported/incomplete-input errors.
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ Current implementation:
- iterative IFD0, ExifIFD, GPSIFD, and next-IFD traversal with cycle and depth protection;
- common TIFF, EXIF, and GPS field decoding with exact rational values;
- deterministic whole-segment JPEG Privacy Clean with byte-preserving reconstruction;
- structured JPEG verification for observable container presence or absence.
- structured JPEG verification for observable container presence or absence;
- bounded WebP RIFF/chunk inspection with EXIF, XMP, and ICCP container detection;
- deterministic WebP Privacy Clean with RIFF-size and VP8X metadata-flag repair;
- structured WebP verification for EXIF, XMP, and ICC presence.

Not implemented: MakerNote or thumbnail decoding, XMP/IPTC/ICC payload parsing, PNG/WebP container parsing or cleaning, and PNG/WebP verification.
Not implemented: MakerNote or thumbnail decoding, XMP/IPTC/ICC payload parsing, WebP EXIF field decoding, and PNG container parsing, cleaning, or verification.

## Format status

JPEG reports can be `container-inspected`, `container-partial`, or `metadata-partial`. `metadata-partial` means supported TIFF/EXIF fields were attempted while the wider metadata space remains intentionally incomplete. PNG and WebP remain `format-only`. See [format support](docs/format-support.md).
JPEG reports can be `container-inspected`, `container-partial`, or `metadata-partial`. `metadata-partial` means supported TIFF/EXIF fields were attempted while the wider metadata space remains intentionally incomplete. WebP reports are `container-inspected` or `container-partial` and expose metadata containers only. PNG remains `format-only`. See [format support](docs/format-support.md).

## Installation

Expand All @@ -40,13 +43,13 @@ import {

GPS rational components remain exact numerator/denominator pairs; decimal coordinates are not derived. Unknown TIFF tags and MakerNote are represented structurally without dumping or recursively parsing their payloads.

`cleanMetadata` supports JPEG. Its default policy removes complete EXIF, standard/extended XMP, Photoshop/IPTC, and COM segments while preserving ICC, JFIF/JFXX, Adobe APP14, unknown APP segments, structural data, scan bytes, and trailing bytes. It returns a separate output, container-level change evidence, and an inspection report of that output.
`cleanMetadata` supports JPEG and WebP. JPEG Privacy Clean removes EXIF, standard/extended XMP, Photoshop/IPTC, and COM segments while preserving ICC, JFIF/JFXX, Adobe APP14, unknown APP segments, structural data, scan bytes, and trailing bytes. WebP Privacy Clean removes EXIF and XMP chunks while preserving ICCP, VP8/VP8L, VP8X, ALPH, ANIM/ANMF, unknown chunks, and trailing bytes; it repairs RIFF size and VP8X metadata flags.

`verifyMetadata` supports JPEG expectations of `absent`, `present`, or `ignore` for EXIF, XMP, IPTC, comments, and ICC. The default checks the four privacy-clean removal targets. A single-file verification can observe presence or absence; it cannot prove that bytes came from an original file.
`verifyMetadata` supports `absent`, `present`, or `ignore` expectations. JPEG defaults check EXIF, XMP, IPTC, and comments; WebP defaults check EXIF and XMP. Single-file verification observes presence or absence and cannot prove that bytes came from an original file.

## Security philosophy

Every byte is untrusted. All offsets are interpreted within bounded views, traversal is iterative and limited, repeated IFD offsets are rejected, and malformed entries recover without unchecked access. Unknown JPEG APP structures remain unknown and are preserved by cleaning. See the [security model](docs/security-model.md), [architecture](docs/architecture.md), and [cleaning policy](docs/cleaning-policy.md).
Every byte is untrusted. All offsets are interpreted within bounded views, traversal is iterative and limited, repeated IFD offsets are rejected, and malformed entries recover without unchecked access. Unknown JPEG APP segments and WebP chunks remain unknown and are preserved by cleaning. See the [security model](docs/security-model.md), [architecture](docs/architecture.md), and [cleaning policy](docs/cleaning-policy.md).

## Non-goals

Expand Down
29 changes: 22 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

```text
JPEG APP1 Exif\0\0 ─┐
PNG eXIf (future) ├──→ bounded TIFF/EXIF core
WebP EXIF (future) ─┘ ↓
normalized entries
PNG eXIf (future) ┴──→ bounded TIFF/EXIF core → normalized entries
WebP EXIF → normalized container entry only
```

The TIFF decoder receives only the TIFF byte view after the six-byte EXIF identifier. It has no JPEG marker or absolute file-offset knowledge. Every TIFF offset is relative to byte zero of that view. Integration relocates decoded source offsets and diagnostics only after parsing.
JPEG integration passes the TIFF decoder only the byte view after the six-byte EXIF identifier. The decoder has no JPEG marker or absolute file-offset knowledge. Every TIFF offset is relative to byte zero of that view. Integration relocates decoded source offsets and diagnostics only after parsing.

## TIFF core

Expand All @@ -27,9 +26,9 @@ Unknown tags retain namespace, tag number, TIFF type, count, entry offset, and s

## Inspection status

- `format-only`: signature detection only; currently PNG, WebP, and unknown input.
- `container-inspected`: JPEG reached EOI and no EXIF decode was attempted.
- `container-partial`: JPEG traversal stopped on corruption, truncation, or a limit.
- `format-only`: signature detection only; currently PNG and unknown input.
- `container-inspected`: JPEG or WebP container traversal completed without deep metadata decoding.
- `container-partial`: JPEG or WebP traversal stopped on corruption, truncation, structural invalidity, or a limit.
- `metadata-partial`: JPEG container traversal completed and common TIFF/EXIF decoding was attempted; XMP/IPTC/ICC and unknown fields remain incomplete.
- `metadata-inspected`: reserved for future broader decoders.

Expand All @@ -46,3 +45,19 @@ input JPEG
```

The parser remains the structural source of truth. Internal rewrite ranges include marker fill bytes associated with a removed marker while public source offsets retain their existing meaning. Cleaning does not invoke TIFF decoding on the source: a structurally bounded EXIF APP1 can be removed even if its TIFF body is malformed. The post-write inspection and verifier use the normal inspection layer.

## WebP clean and verify flow

```text
WebP bytes
→ bounded RIFF/WebP parser and FourCC classification
→ direct EXIF/XMP/ICC policy
→ retained chunks
→ minimal VP8X metadata-bit patch
→ RIFF size patch
→ one output allocation and ordered chunk copies
→ inspectMetadata(output)
→ structured verification checks
```

Chunk payloads remain bounded views and image, alpha, and animation bytes are opaque. The cleaner does not synthesize VP8X; a valid retained VP8X has only its ICC, EXIF, and XMP bits aligned with actual retained chunks. Bytes outside the declared RIFF container are copied as uninterpreted trailing data.
29 changes: 23 additions & 6 deletions docs/cleaning-policy.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Cleaning Policy

JPEG Privacy Clean removes complete recognized metadata containers. It never rewrites TIFF/EXIF fields, XMP XML, IPTC blocks, comments, or ICC payloads.
Privacy Clean removes complete recognized metadata containers and never decodes or re-encodes image payloads.

## JPEG

| JPEG structure | Default action |
| -------------------------------------- | -------------- |
| EXIF APP1 | Remove |
| Standard XMP APP1 | Remove |
| Extended XMP APP1 | Remove |
| Standard/extended XMP APP1 | Remove |
| Photoshop/IPTC APP13 | Remove |
| COM | Remove |
| ICC APP2 | Preserve |
Expand All @@ -16,8 +17,24 @@ JPEG Privacy Clean removes complete recognized metadata containers. It never rew
| Structural markers and image/scan data | Preserve |
| Data after EOI | Preserve |

Every recognized instance is handled independently and retained content keeps its original order and bytes. Unknown APP removal is intentionally unavailable in Sprint 4. Callers may override the four removal booleans and ICC preservation; `preserveColorProfiles` remains a deprecated alias for `preserveIcc`.
JPEG v0.1 removes the entire EXIF APP1, including malformed TIFF bodies whose JPEG segment boundary is valid. Selective GPS/tag rewriting and TIFF reserialization are deferred.

## WebP

| WebP chunk or data | Default action |
| --------------------------------- | ---------------------------------- |
| EXIF | Remove |
| XMP | Remove |
| ICCP | Preserve |
| VP8 / VP8L | Preserve |
| VP8X | Preserve; align ICC/EXIF/XMP flags |
| ALPH | Preserve |
| ANIM / ANMF | Preserve |
| Unknown chunks | Preserve |
| Data after declared RIFF boundary | Preserve |

WebP cleaning removes every targeted physical chunk including odd-byte padding, repairs the RIFF size, and patches only the three VP8X metadata feature bits. Alpha, animation, reserved, and other VP8X bits remain unchanged. No VP8X is synthesized. Structurally bounded malformed metadata payloads remain removable.

v0.1 removes the entire EXIF APP1, including malformed TIFF bodies whose JPEG segment boundary is valid. Selective GPS or tag rewriting and TIFF reserialization are deferred.
The shared policy fields `removeExif`, `removeXmp`, and `preserveIcc` apply to both formats. JPEG-only `removeIptc` and `removeComments` have no WebP effect. `preserveColorProfiles` remains a deprecated alias for `preserveIcc`. Unknown removal is intentionally unavailable.

`cleanMetadata` returns a new `Uint8Array`, container-level removed/preserved records, diagnostics, and an inspection report of the produced JPEG. A structurally incomplete JPEG is rejected before allocation. PNG, WebP, and unknown inputs return a typed unsupported-format error.
`cleanMetadata` returns a new `Uint8Array`, container-level change evidence, diagnostics, and an inspection report of its output. Structurally incomplete input is rejected before output allocation. PNG and unknown formats return a typed unsupported-format error.
42 changes: 19 additions & 23 deletions docs/format-support.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
# Format Support

| Capability | JPEG | PNG | WebP |
| --------------------------- | ---------------------- | -------------- | -------------- |
| Signature detection | Supported | Supported | Supported |
| Bounded container traversal | Supported | Not yet | Not yet |
| EXIF container detection | Supported | Not yet | Not yet |
| TIFF header and IFD0 | Supported through JPEG | Not integrated | Not integrated |
| ExifIFD and GPSIFD | Supported through JPEG | Not integrated | Not integrated |
| Common EXIF/GPS fields | Supported subset | Not integrated | Not integrated |
| MakerNote decoding | Not supported | Not supported | Not supported |
| XMP payload decoding | Not yet | Not yet | Not yet |
| IPTC/ICC payload decoding | Not yet | Not yet | Not yet |
| Whole-container cleaning | Supported | Not yet | Not yet |
| Structured verification | Supported | Not yet | Not yet |
| Capability | JPEG | WebP | PNG |
| ----------------------------- | ---------------- | -------------- | -------------- |
| Signature detection | Supported | Supported | Supported |
| Bounded container traversal | Supported | Supported | Not yet |
| EXIF container detection | Supported | Supported | Not yet |
| XMP/ICC container detection | Supported | Supported | Not yet |
| TIFF/EXIF field decoding | Supported subset | Container only | Not integrated |
| MakerNote decoding | Not supported | Not supported | Not supported |
| XMP/IPTC/ICC payload decoding | Not supported | Not supported | Not supported |
| Whole-container cleaning | Supported | Supported | Not yet |
| Structured verification | Supported | Supported | Not yet |

## TIFF/EXIF subset

Both `II` and `MM` byte orders are supported. Traversal covers IFD0, ExifIFDPointer, GPSInfoIFDPointer, and next-IFD links with table, entry, depth, offset, and cycle checks.
JPEG integrates the shared little- and big-endian TIFF decoder. Traversal covers IFD0, ExifIFDPointer, GPSInfoIFDPointer, and next-IFD links with table, entry, depth, offset, and cycle checks.

Decoded IFD0 tags: ImageDescription, Make, Model, Orientation, Software, DateTime, Artist, and Copyright.

Decoded ExifIFD tags: ExposureTime, FNumber, PhotographicSensitivity, ExifVersion, DateTimeOriginal, DateTimeDigitized, FocalLength, PixelXDimension, PixelYDimension, and FocalLengthIn35mmFilm. MakerNote is named and retained as opaque structure.
Decoded ExifIFD tags: ExposureTime, FNumber, PhotographicSensitivity, ExifVersion, DateTimeOriginal, DateTimeDigitized, FocalLength, PixelXDimension, PixelYDimension, and FocalLengthIn35mmFilm. MakerNote remains opaque.

Decoded GPS tags: GPSVersionID, GPSLatitudeRef, GPSLatitude, GPSLongitudeRef, GPSLongitude, GPSAltitudeRef, GPSAltitude, GPSTimeStamp, and GPSDateStamp. Coordinates remain exact raw rational components plus reference fields; decimal coordinates are not derived.
Decoded GPS tags include version, latitude/longitude components and references, altitude, time, and date. Coordinates remain exact rational components; decimal coordinates are not derived.

Unknown tags remain structurally represented without speculative meaning or large binary values.
WebP EXIF is intentionally container detection only. Its bounded chunk payload is not passed to TIFF decoding because Sprint 5 does not guess a prefix or offset base.

## Remaining container support
## Container cleaning and verification

JPEG marker and scan traversal remains supported. XMP, ICC, and Photoshop/IPTC signatures are container-detected only. PNG requires its complete signature and WebP requires `RIFF....WEBP`; their chunks and metadata are not parsed yet.
JPEG Privacy Clean removes EXIF, standard/extended XMP, Photoshop/IPTC, and comments while preserving ICC, application/rendering structures, scan data, unknown APP segments, and trailing bytes.

## JPEG cleaning and verification
WebP Privacy Clean removes EXIF and XMP chunks while preserving ICCP, VP8/VP8L, VP8X, ALPH, ANIM/ANMF, unknown chunks, original padding on retained chunks, and trailing bytes. It repairs RIFF size and retained VP8X metadata flags.

JPEG Privacy Clean removes recognized EXIF, standard/extended XMP, Photoshop/IPTC, and comment segments. ICC, JFIF/JFXX, Adobe APP14, unknown APP segments, structural markers, all scan data, and trailing bytes are retained. Structurally incomplete JPEGs are rejected; malformed TIFF inside a bounded removable EXIF segment does not block cleaning.

Verification reports observable container presence or absence for EXIF, XMP, IPTC, comments, and ICC. It does not decode XMP/IPTC/ICC payloads or prove preservation from an original input.
Verification reports observable supported metadata-container presence or absence. It does not decode XMP/IPTC/ICC, prove byte provenance, or prove complete removal of personal information. PNG remains format detection only.
10 changes: 9 additions & 1 deletion docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Binary metadata parsing processes attacker-controlled structures, sizes, offsets
4. Core functions make no network requests and access no filesystem or DOM APIs.
5. Image pixel payloads are never decoded.
6. Unknown metadata is not deleted or assigned speculative meaning.
7. JPEG cleaning preserves ICC, unknown APP, and rendering/container segments by default.
7. JPEG and WebP cleaning preserve ICC, unknown structures, and image/rendering structures by default.
8. Cleaner output is re-inspected before it is returned.
9. Metadata absence never proves an image contains no private information.
10. Steganography detection, malware scanning, visual redaction, and pixel privacy analysis are outside scope.
Expand Down Expand Up @@ -48,3 +48,11 @@ Cleaning proceeds only after bounded traversal reaches EOI. Truncated lengths, i
Removal uses checked, non-overlapping parser ranges. Output length is a safe integer no larger than input length, one output buffer is allocated, and retained ranges are copied in original order. Entropy-coded bytes, restart markers, retained marker fill, structural segments, and bytes after EOI are neither decoded nor regenerated. Exact `Uint8Array` views are honored and caller input is never mutated.

The default policy preserves every ICC and unknown APP segment. Verification proves only the requested observable container state supported by inspection. It does not prove provenance, byte preservation without an original, absence of unknown metadata, or absence of personal information in pixels or unsupported structures.

## WebP parsing and cleaning properties

The parser validates the 12-byte RIFF/WebP header, checked declared RIFF boundary, complete eight-byte chunk headers, payload lengths, odd-byte padding, VP8X length/uniqueness, and `maxChunks`. Every chunk loop either advances by its validated physical length or terminates. Trailing bytes outside the declared RIFF boundary are warned about, not parsed.

VP8, VP8L, VP8X, ALPH, ANIM, ANMF, ICCP, and unknown chunk payloads remain opaque. Privacy Clean removes whole EXIF and XMP chunks, including their padding. ICCP and unknown chunks remain by default. Reconstruction allocates one output buffer, copies retained physical chunks in order, patches only VP8X ICC/EXIF/XMP flag bits, repairs the little-endian RIFF size, and preserves trailing bytes outside that size.

Unsafe RIFF/chunk boundaries, missing padding, invalid or duplicate VP8X, and chunk-limit failures produce `IncompleteWebPError` before output. Malformed EXIF/XMP payloads do not block safe whole-chunk removal. WebP verification observes supported chunk presence only and makes no claim about provenance, unknown metadata, pixels, or complete personal-information removal.
11 changes: 11 additions & 0 deletions src/core/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ export type DiagnosticCode =
| "JPEG_MISSING_EOI"
| "JPEG_SEGMENT_LIMIT_EXCEEDED"
| "JPEG_TRAILING_DATA"
| "WEBP_INVALID_RIFF_HEADER"
| "WEBP_INVALID_RIFF_SIZE"
| "WEBP_TRUNCATED_RIFF"
| "WEBP_TRUNCATED_CHUNK_HEADER"
| "WEBP_TRUNCATED_CHUNK"
| "WEBP_CHUNK_LIMIT_EXCEEDED"
| "WEBP_INVALID_PADDING"
| "WEBP_INVALID_VP8X"
| "WEBP_DUPLICATE_VP8X"
| "WEBP_INCONSISTENT_FEATURE_FLAGS"
| "WEBP_TRAILING_DATA"
| "TIFF_TRUNCATED_HEADER"
| "TIFF_INVALID_BYTE_ORDER"
| "TIFF_INVALID_MAGIC"
Expand Down
Loading
Loading