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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v4
Expand All @@ -25,6 +27,12 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Check formatting
run: npm run format:check

- name: Lint
run: npm run lint

Expand All @@ -39,3 +47,24 @@ jobs:

- name: Build
run: npm run build

- name: Browser smoke
run: npm run browser:smoke

- name: Audit package payload
run: npm run package:audit

- name: Audit licenses
run: npm run license:audit

- name: Audit vulnerabilities
run: npm audit --audit-level=high

- name: Verify version consistency
run: npm run version:check

- name: Verify reproducible artifacts
run: npm run release:repro

- name: Verify release hashes
run: npm run release:verify
52 changes: 52 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Publish npm

on:
push:
tags:
- "v*"

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
environment: npm
steps:
- name: Check out tagged revision
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Match package version to tag
run: npm run version:check -- --tag "$GITHUB_REF_NAME"

- name: Validate release candidate
run: |
npm run format:check
npm run lint
npm run typecheck
npm test
npm run fuzz:smoke
npm run build
npm run browser:smoke
npm run package:audit
npm run license:audit
npm audit --audit-level=high

- name: Publish with provenance
run: npm publish --provenance --access public
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ coverage/
*.log
.DS_Store

/release/
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes will be documented here. The project intends to follow seman

## Unreleased

## 0.1.0 - Release candidate

### Added

- Reproducible fast-check properties for arbitrary-byte inspection, parser mutations, subview isolation, limits, cleaners, and fail-closed verification.
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ JPEG supports bounded inspection, common TIFF/EXIF field decoding, cleaning, and

## Installation

The package is not published. Installation instructions will be added for the first pre-release.
After publication, install from npm:

npm install secure-metadata

Node.js 20 or newer is required. Browser consumers may import the secure-metadata/browser entry through a bundler, or deploy the versioned standalone browser artifact from the release candidate on the same origin. The library never loads code from a CDN.

## Public API

Expand All @@ -48,7 +52,7 @@ The deterministic corpus is supplemented by fixed-seed property tests and a fini

## Security philosophy

Every byte is untrusted. All offsets are interpreted within bounded views, traversal is iterative and limited, and malformed structures fail without unchecked access. PNG image data and compressed metadata are never inflated. Unknown JPEG APP segments, WebP chunks, and PNG ancillary chunks are preserved by default. See the [security model](docs/security-model.md), [architecture](docs/architecture.md), [testing model](docs/testing.md), and [cleaning policy](docs/cleaning-policy.md).
Every byte is untrusted. All offsets are interpreted within bounded views, traversal is iterative and limited, and malformed structures fail without unchecked access. PNG image data and compressed metadata are never inflated. Unknown JPEG APP segments, WebP chunks, and PNG ancillary chunks are preserved by default. See the [security model](docs/security-model.md), [architecture](docs/architecture.md), [testing model](docs/testing.md), [cleaning policy](docs/cleaning-policy.md), [v0.1 API contract](docs/api-contract.md), and [release process](docs/releasing.md).

## Non-goals

Expand Down
17 changes: 12 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

## Supported versions

`secure-metadata` is pre-release software and currently has no supported release line. A supported-version table will be added before the first public release.
| Version | Support |
| ------- | ---------------------------------- |
| 0.1.x | Supported after public publication |
| < 0.1 | Not supported |

The current repository may contain an unpublished release candidate. A candidate is not a supported npm release until publication completes.

## Reporting a vulnerability

Binary parser vulnerabilities should be coordinated privately before public disclosure. If GitHub private vulnerability reporting is enabled for this repository, use **Security → Report a vulnerability**. Do not include a malicious sample or parser details in a public issue.
Binary parser vulnerabilities should be coordinated privately before public disclosure. Use **Security → Report a vulnerability** in this GitHub repository when private vulnerability reporting is available. Do not include a malicious sample or parser details in a public issue.

If private vulnerability reporting is not available, there is not yet a dedicated reporting channel. Maintainers must configure one before the first public release; do not invent or guess a contact address.
If private vulnerability reporting is unavailable, do not guess a maintainer address or open a public report containing exploit details. Repository owners must configure a private channel before publication.

Please include the affected revision, impact, reproduction steps, and the smallest safe test case you can provide. Parser crashes, incorrect or out-of-bounds-style offset logic, unbounded traversal or allocation, and resource exhaustion are security-relevant.
Include the affected version and revision, impact, reproduction steps, and the smallest safe test case possible. Parser crashes, offset or bounds errors, unbounded traversal or allocation, resource exhaustion, cleaner verification failures, and unexpected network or filesystem behavior are security-relevant.

## Threat model

All input bytes are treated as malicious. The library is designed to inspect container and metadata structures without decoding pixels, touching the filesystem, or using the network. Hard limits and bounded reads are core defenses, while cleaner output must be independently parsed and verified.
All input bytes are malicious. The library inspects container and recognized metadata structures without decoding pixels, accessing the filesystem, or using the network. Bounded reads, hard traversal and allocation limits, deterministic reconstruction, and independent output verification are core defenses.

The library does not decode image pixels, detect steganography or malware, prove provenance, or establish that an image is private. Unknown containers and opaque compressed metadata can remain. See [the security model](docs/security-model.md) and [format support](docs/format-support.md).
21 changes: 21 additions & 0 deletions docs/api-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# v0.1 API contract

The `0.1.x` line freezes the package entry points `secure-metadata` and `secure-metadata/browser`. Both expose the same API; the browser entry points to the standalone ESM browser artifact and reuses the package declarations.

## Runtime exports

- operations: `inspectMetadata`, `cleanMetadata`, `verifyMetadata`;
- defaults: `DEFAULT_PARSE_LIMITS`, `DEFAULT_CLEANING_POLICY`, and the JPEG, WebP, and PNG cleaning and verification defaults;
- errors: `SecureMetadataError`, `BinaryBoundsError`, `IncompleteJpegError`, `IncompleteWebPError`, `IncompletePngError`, `InputLimitExceededError`, `InvalidParseLimitError`, and `UnsupportedFormatError`.

The exact 19-name runtime surface is enforced by `tests/release/public-api-contract.test.ts` and by installing the packed tarball into an isolated consumer.

## Type exports

The package exports the diagnostic, error-code, parsing-limit, binary-input, policy, result, report, metadata, rational, and verification types declared by `src/index.ts`. Type-only exports do not appear as JavaScript properties.

## Compatibility policy

Before `1.0.0`, minor releases may add API. Within `0.1.x`, removing or renaming an export, narrowing accepted inputs, changing documented result meaning, or changing default privacy policy requires a deliberate compatibility review and a version decision. Patch releases may fix incorrect behavior while preserving the documented contract.

This contract does not promise exhaustive metadata discovery. Unknown containers and opaque compressed payloads remain subject to the documented format and security limitations.
41 changes: 41 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Release process

This document defines the `v0.1.0` release-candidate process. It does not authorize publishing, tagging, or creating a GitHub release during development.

## Candidate validation

Start from a clean commit on the intended release revision, with Node.js 24 and Chromium installed for Playwright. Run:

```sh
npm run release:check
```

The command performs a clean install; formatting, lint, type, unit/property, bounded fuzz, build, real-browser, package, license, and vulnerability checks; validates version consistency; then builds the release artifacts twice in detached clean worktrees and compares every output byte.

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;
- `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.

The browser artifact is a same-origin deployment asset, not a CDN dependency. Pin it by filename and SHA-256, serve it with a JavaScript MIME type, and retain its source commit association from `SHA256SUMS`.

## Licensing

The published package has no runtime dependencies and the bundled JavaScript contains project source only. Dev tooling is audited by `npm run license:audit`; its accepted SPDX set is explicit in that script. No third-party NOTICE file is currently required. Re-run the audit and review bundled content whenever dependencies or build configuration change.

## Trusted Publishing

Before the first publication, an npm package owner must configure Trusted Publishing for this repository, the `publish.yml` workflow, and the `npm` GitHub environment. The workflow uses GitHub OIDC (`id-token: write`) and `npm publish --provenance`; it intentionally contains no long-lived npm token.

After merging an approved release commit:

1. confirm all required checks pass on the exact commit;
2. create the signed or annotated tag `v0.1.0` on that commit;
3. push the tag and review the publish workflow and npm provenance attestation;
4. create GitHub release notes from `CHANGELOG.md` and attach the independently verified files from `release/` if desired;
5. verify installation from npm and the same-origin browser artifact in a fresh consumer.

For subsequent releases, update `package.json` and both lockfile version fields together exactly once, and ensure the tag is exactly `v<package version>`.
52 changes: 50 additions & 2 deletions package-lock.json

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

30 changes: 27 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "secure-metadata",
"version": "0.0.0",
"version": "0.1.0",
"description": "Deterministic, security-conscious metadata tooling for binary image formats.",
"license": "MIT",
"type": "module",
Expand All @@ -11,6 +11,10 @@
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./browser": {
"types": "./dist/index.d.ts",
"import": "./dist/browser/secure-metadata.js"
}
},
"files": [
Expand All @@ -20,15 +24,23 @@
"CHANGELOG.md"
],
"scripts": {
"build": "tsup src/index.ts --format esm --dts --clean --sourcemap",
"build": "tsup && tsup --config tsup.browser.config.ts",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint .",
"test": "vitest run",
"test:watch": "vitest",
"fuzz:smoke": "npm run build --silent && node scripts/fuzz.mjs --seed 20260825 --runs 250 --max-bytes 512",
"fuzz": "npm run build --silent && node scripts/fuzz.mjs",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"browser:smoke": "node scripts/browser-smoke.mjs",
"package:audit": "node scripts/release/audit-package.mjs",
"license:audit": "node scripts/release/audit-licenses.mjs",
"version:check": "node scripts/release/check-version.mjs",
"release:build": "node scripts/release/build-artifacts.mjs",
"release:repro": "node scripts/release/check-reproducibility.mjs",
"release:verify": "node scripts/release/verify-hashes.mjs",
"release:check": "node scripts/release/check-rc.mjs"
},
"engines": {
"node": ">=20"
Expand All @@ -38,6 +50,7 @@
"@types/node": "^24.13.3",
"eslint": "^10.9.0",
"fast-check": "^4.9.0",
"playwright": "^1.62.1",
"prettier": "^3.9.6",
"tsup": "^8.5.1",
"typescript": "5.9.3",
Expand All @@ -46,5 +59,16 @@
},
"overrides": {
"esbuild": "0.28.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/SecureToolsProject/Secure_Metadata.git"
},
"homepage": "https://github.com/SecureToolsProject/Secure_Metadata#readme",
"bugs": {
"url": "https://github.com/SecureToolsProject/Secure_Metadata/issues"
},
"publishConfig": {
"access": "public"
}
}
Loading
Loading