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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes will be documented here. The project intends to follow seman

## Unreleased

## 0.1.1 - 2026-08-27

### Fixed

- Preserve exactly one valid JPEG IFD0 EXIF Orientation value from 1 through 8 by rewriting it into a minimal endian-preserving EXIF APP1 segment while removing all other EXIF fields.
- Treat canonical Orientation-only JPEG EXIF as allowed rendering metadata during verification, while rejecting Orientation combined with any other EXIF or GPS metadata.
- Keep JPEG scan data, ICC, unknown application segments, structural bytes, and trailing data byte-identical during Orientation-preserving cleaning.
- Fail safely by removing EXIF rather than guessing when Orientation is malformed, out of range, duplicated, conflicting, incomplete, or exceeds configured parsing limits.

## 0.1.0 - 2026-08-26

### Added
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Current implementation:

- bounded binary input and endian-aware read core;
- JPEG inspection, common TIFF/EXIF decoding, whole-segment Privacy Clean, and verification;
- JPEG inspection, common TIFF/EXIF decoding, Orientation-preserving Privacy Clean, and verification;
- WebP RIFF/chunk inspection, EXIF/XMP/ICC container detection, Privacy Clean, and verification;
- PNG chunk inspection with text, XMP, EXIF, ICC, timestamp, rendering, and APNG classification;
- shared TIFF/EXIF decoding for JPEG EXIF and PNG `eXIf` payloads;
Expand Down Expand Up @@ -40,7 +40,7 @@ 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.

`DEFAULT_CLEANING_POLICY` is the authoritative semantic default: remove recognized EXIF, XMP, IPTC, comments, ordinary text, and standalone timestamps; preserve ICC, unknown, rendering, and image data. Each format maps only applicable concepts to physical containers. The deprecated `preserveColorProfiles` alias is used only when explicit `preserveIcc` is absent.
`DEFAULT_CLEANING_POLICY` is the authoritative semantic default: remove recognized private EXIF, XMP, IPTC, comments, ordinary text, and standalone timestamps; preserve ICC, unknown, rendering, and image data. For JPEG only, a single valid IFD0 Orientation value from 1 through 8 is rewritten into canonical Orientation-only EXIF so display rotation survives cleaning; all other EXIF fields are removed. Each format maps only applicable concepts to physical containers. The deprecated `preserveColorProfiles` alias is used only when explicit `preserveIcc` is absent.

`verifyMetadata` supports `absent`, `present`, or `ignore` expectations. Concepts not implemented for a format produce no check rather than implying an exhaustive search. Verification fails closed if metadata reporting reaches its configured entry limit. Single-file verification observes supported container presence or absence and cannot prove provenance or pixel privacy.

Expand All @@ -58,7 +58,7 @@ The library does not perform image decoding or encoding, visual redaction, pixel

## Secure Tools ecosystem

This is an independent open-source library in the broader Secure Tools ecosystem. Future integration will verify the release version, license, and SHA-256 checksum, then vendor the pinned browser artifact on the same origin rather than coupling application code to this repository.
This is an independent open-source library in the broader Secure Tools ecosystem. Secure Tools should upgrade its vendored browser artifact to `v0.1.1` after that release is published, verify the version, license, and SHA-256 checksum, and continue serving the pinned artifact from the same origin rather than coupling application code to this repository.

## License

Expand Down
40 changes: 20 additions & 20 deletions docs/cleaning-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ Privacy Clean applies one normalized semantic policy, then maps it directly to e

## Semantic defaults

| Policy field | Default | Meaning |
| -------------------- | ------- | ----------------------------------------------- |
| `removeExif` | `true` | Remove recognized EXIF containers |
| `removeXmp` | `true` | Remove recognized XMP containers |
| `removeIptc` | `true` | Remove recognized IPTC containers |
| `removeComments` | `true` | Remove recognized comment containers |
| `removeTextMetadata` | `true` | Remove recognized ordinary text metadata |
| `removeTimestamps` | `true` | Remove recognized standalone timestamp metadata |
| `preserveIcc` | `true` | Preserve recognized ICC containers |

Unknown, rendering, and image data are preserved. `preserveColorProfiles` remains a deprecated alias for `preserveIcc`: explicit `preserveIcc` wins, otherwise the alias is used, otherwise the default applies. The exported `DEFAULT_CLEANING_POLICY` is authoritative; legacy format-named defaults reference the same frozen object.
| Policy field | Default | Meaning |
| -------------------- | ------- | -------------------------------------------------- |
| `removeExif` | `true` | Remove private EXIF; retain valid JPEG Orientation |
| `removeXmp` | `true` | Remove recognized XMP containers |
| `removeIptc` | `true` | Remove recognized IPTC containers |
| `removeComments` | `true` | Remove recognized comment containers |
| `removeTextMetadata` | `true` | Remove recognized ordinary text metadata |
| `removeTimestamps` | `true` | Remove recognized standalone timestamp metadata |
| `preserveIcc` | `true` | Preserve recognized ICC containers |

Unknown, rendering, and image data are preserved. JPEG Orientation is rendering metadata: when the input has exactly one fully decoded IFD0 Orientation with TIFF type SHORT, count 1, and value 1 through 8, cleaning rewrites it into a canonical minimal EXIF APP1 segment. No pixel decode, rotation, or re-encoding occurs. Missing, malformed, out-of-range, incomplete, or ambiguous Orientation is not guessed and the EXIF container is removed. `preserveColorProfiles` remains a deprecated alias for `preserveIcc`: explicit `preserveIcc` wins, otherwise the alias is used, otherwise the default applies. The exported `DEFAULT_CLEANING_POLICY` is authoritative; legacy format-named defaults reference the same frozen object.

## JPEG mapping

| Semantic field | Physical mapping |
| ---------------- | ------------------------------ |
| `removeExif` | EXIF APP1 |
| `removeXmp` | Standard and extended XMP APP1 |
| `removeIptc` | Photoshop/IPTC APP13 |
| `removeComments` | COM |
| `preserveIcc` | ICC APP2 |
| Semantic field | Physical mapping |
| ---------------- | -------------------------------------------- |
| `removeExif` | EXIF APP1 except canonical valid Orientation |
| `removeXmp` | Standard and extended XMP APP1 |
| `removeIptc` | Photoshop/IPTC APP13 |
| `removeComments` | COM |
| `preserveIcc` | ICC APP2 |

JFIF/JFXX, Adobe APP14, unknown APP segments, structural markers, scan data, and data after EOI are preserved.
JFIF/JFXX, Adobe APP14, unknown APP segments, structural markers, scan data, and data after EOI are preserved byte-for-byte. A rewritten EXIF segment contains only the TIFF header, one IFD0 Orientation entry, and a zero next-IFD pointer; all device, software, timestamp, location, identity, description, rights, MakerNote, thumbnail, and unknown EXIF fields are removed.

## WebP mapping

Expand All @@ -50,4 +50,4 @@ Other semantic fields are not applicable. VP8/VP8L, ALPH, ANIM/ANMF, unknown chu

Rendering/color chunks, IDAT, APNG structure, unknown ancillary and critical chunks, retained CRCs, and data after IEND are preserved. Compressed text and ICC payloads are never decompressed.

One removed physical container produces one source-ordered change record. `cleanMetadata` always returns a distinct output view, change evidence, bounded diagnostics, and one re-inspection report. Valid outer boundaries permit whole-container removal even when inner metadata is malformed; unsafe container boundaries produce a typed incomplete-format error without output.
One removed or rewritten physical container produces one source-ordered removal record. Rewritten JPEG Orientation also produces a preservation record. Canonical Orientation-only EXIF is considered absent for the JPEG `exif: "absent"` privacy expectation; Orientation combined with any other EXIF or GPS entry remains present and fails verification. `cleanMetadata` always returns a distinct output view, change evidence, bounded diagnostics, and one re-inspection report. Valid outer boundaries permit whole-container removal even when inner metadata is malformed; unsafe container boundaries produce a typed incomplete-format error without output.
4 changes: 2 additions & 2 deletions docs/format-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ The shared TIFF subset covers common IFD0, ExifIFD, GPSIFD, and next-IFD entries

## Container cleaning and verification

JPEG removes EXIF, XMP, Photoshop/IPTC, and comments. WebP removes EXIF and XMP, repairs RIFF size, and aligns retained VP8X flags. PNG removes `eXIf`, XMP and ordinary text chunks, and `tIME`; it preserves ICC, rendering/color, IDAT, APNG, unknown, critical, CRC, and trailing bytes by default.
JPEG removes private EXIF, XMP, Photoshop/IPTC, and comments. Exactly one valid IFD0 Orientation value from 1 through 8 is retained in a canonical minimal EXIF APP1 segment to preserve rendering; malformed or ambiguous Orientation is removed rather than guessed. WebP removes EXIF and XMP, repairs RIFF size, and aligns retained VP8X flags. PNG removes `eXIf`, XMP and ordinary text chunks, and `tIME`; it preserves ICC, rendering/color, IDAT, APNG, unknown, critical, CRC, and trailing bytes by default.

Verification reports observable supported metadata-container presence or absence. Expectations for concepts not implemented by a format are omitted as not applicable; they do not generate synthetic passing checks. If `maxMetadataEntries` truncates reporting, the report carries `metadataTruncated: true` and verification returns `valid: false` with no checks. It does not decode XMP/IPTC/ICC or compressed PNG text, prove byte provenance, or prove complete removal of personal information.
Verification reports observable supported metadata-container presence or absence. Canonical JPEG Orientation-only EXIF satisfies the default privacy expectation, but Orientation plus any other EXIF or GPS entry does not. Expectations for concepts not implemented by a format are omitted as not applicable; they do not generate synthetic passing checks. If `maxMetadataEntries` truncates reporting, the report carries `metadataTruncated: true` and verification returns `valid: false` with no checks. It does not decode XMP/IPTC/ICC or compressed PNG text, prove byte provenance, or prove complete removal of personal information.
6 changes: 3 additions & 3 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release process

This document defines release validation and publication. `v0.1.0` is published as an immutable GitHub release from commit `352258ec413a838dfe8b9146370505f125b5ae10`; it is not published to npm.
This document defines release validation and publication. `v0.1.0` is published as an immutable GitHub release from commit `352258ec413a838dfe8b9146370505f125b5ae10`; it is not published to npm. `v0.1.1` is the current patch release candidate and must not be tagged or published until its final main-branch commit and artifacts pass this process.

## Release validation

Expand All @@ -14,8 +14,8 @@ The command performs a clean install; formatting, lint, type, unit/property, bou

Outputs are written to ignored `release/`:

- `secure-metadata-0.1.0.tgz` — npm package;
- `secure-metadata-0.1.0.browser.js` — standalone browser ESM artifact;
- `secure-metadata-0.1.1.tgz` — npm package;
- `secure-metadata-0.1.1.browser.js` — standalone browser ESM artifact;
- `SHA256SUMS` — version, source commit, filenames, and SHA-256 hashes.

Verify a transferred artifact set with `npm run release:verify`. `npm run package:audit` separately verifies the exact npm payload and imports the packed package through both public entry points.
Expand Down
6 changes: 3 additions & 3 deletions docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ All binary input is attacker-controlled. The cross-format guarantees are:
4. no recursion over untrusted container structures;
5. no pixel decoding or image re-encoding;
6. no compressed metadata inflation;
7. safe whole-container removal when outer boundaries are trustworthy;
7. safe whole-container removal when outer boundaries are trustworthy, with a bounded canonical rewrite only for one valid JPEG Orientation;
8. unknown metadata preservation by default;
9. ICC and rendering/color preservation by default;
10. deterministic, source-ordered output;
Expand All @@ -19,11 +19,11 @@ All binary input is attacker-controlled. The cross-format guarantees are:

## Format-specific reconstruction

JPEG requires trustworthy traversal through EOI and preserves retained marker, fill, scan, restart, and trailing bytes. WebP requires a complete RIFF/chunk boundary, preserves padding and trailing data, repairs RIFF size, and changes only applicable VP8X metadata flags. PNG requires a complete IEND boundary, preserves trailing data, and copies every retained length/type/data/CRC byte unchanged. Each reconstruction uses one final output allocation and honors the caller's exact `Uint8Array` view.
JPEG requires trustworthy traversal through EOI and preserves retained marker, fill, scan, restart, and trailing bytes. When exactly one EXIF APP1 contains a fully decoded IFD0 Orientation SHORT/count-1 value from 1 through 8, the cleaner replaces that segment with a minimal TIFF structure preserving the original byte order and value. It never rotates or re-encodes pixels. Multiple, malformed, incomplete, limited, or conflicting candidates are removed rather than interpreted. WebP requires a complete RIFF/chunk boundary, preserves padding and trailing data, repairs RIFF size, and changes only applicable VP8X metadata flags. PNG requires a complete IEND boundary, preserves trailing data, and copies every retained length/type/data/CRC byte unchanged. Each reconstruction uses one final output allocation and honors the caller's exact `Uint8Array` view.

Malformed inner EXIF/TIFF or textual payloads do not block removal of their bounded JPEG segment, WebP chunk, or PNG chunk. Unsafe outer boundaries produce `IncompleteJpegError`, `IncompleteWebPError`, or `IncompletePngError` without partial output.

Verification reports only supported `present` or `absent` observations. Not-applicable format concepts produce no check. Truncated metadata reporting is recorded independently of diagnostic output, produces no checks, and fails verification. The library does not establish authenticity, provenance, absence of proprietary metadata, visible-person privacy, steganography safety, malware safety, or complete metadata absence.
Verification reports only supported `present` or `absent` observations. Canonical Orientation-only JPEG EXIF is an explicit rendering exception to the default EXIF privacy-absence check; any additional EXIF or GPS entry makes EXIF present. Not-applicable format concepts produce no check. Truncated metadata reporting is recorded independently of diagnostic output, produces no checks, and fails verification. The library does not establish authenticity, provenance, absence of proprietary metadata, visible-person privacy, steganography safety, malware safety, or complete metadata absence.

Core production code has zero runtime dependencies and no network, analytics, telemetry, filesystem, DOM, Node `Buffer`, or required platform-global behavior.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "secure-metadata",
"version": "0.1.0",
"version": "0.1.1",
"description": "Deterministic, security-conscious metadata tooling for binary image formats.",
"license": "MIT",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion scripts/release/audit-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { npmCommand, run } from "./shared.mjs";

const packageJson = JSON.parse(await readFile("package.json", "utf8"));
assert.equal(packageJson.version, "0.1.0");
assert.equal(packageJson.version, "0.1.1");
assert.equal(packageJson.license, "MIT");
assert.equal(
packageJson.repository.url,
Expand Down
2 changes: 1 addition & 1 deletion scripts/release/check-version.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { readFile } from "node:fs/promises";
const packageJson = JSON.parse(await readFile("package.json", "utf8"));
const lock = JSON.parse(await readFile("package-lock.json", "utf8"));

assert.equal(packageJson.version, "0.1.0");
assert.equal(packageJson.version, "0.1.1");
assert.equal(lock.version, packageJson.version);
assert.equal(lock.packages[""].version, packageJson.version);

Expand Down
Loading
Loading