SealedRun

Changelog

Mirrors CHANGELOG.md in the repository. Releases are tagged and published from GitHub Actions.

# Changelog

All notable changes are documented here. The format follows Keep a Changelog; the project
follows Semantic Versioning once 1.0.0 is reached.

## [0.1.1] - 2026-09-21

First public release. Security hardening before publication. The project and the record format are now named
SealedRun (previously AFR).

### Security

- **Trust anchor.** A bundle signed end to end by a newly minted Principal used to verify exactly like a genuine one, and the report did not even name the Principal. `SPEC.md` 13.2 now has a trust anchor step and `TRUST.md` no longer treats the keys inside a bundle as a root of trust. `verify_bundle` / `verifyBundle` take the trusted principal ids and fail with check `trust` on anyone else; `BundleReport` carries `principal_id`, the exporter `agent_id` and `principal_trusted`. The recorder has `SEALEDRUN_TRUSTED_PRINCIPALS`; the Inspector shows the principal, has a box for trusted ids and says "integrity only, signer not authenticated" instead of "verified" when none matched. **API change:** `BundleReport` has three new required fields.
- Verifiers (Python and TypeScript) reject every Delegation whose `principal_id` is a DID: 0.1 has no DID resolver, so such an identifier was never bound to the signing keys. `SPEC.md` and `TRUST.md` say so.
- Bundle verification checks every delegation in the bundle and requires its `principal_id` to equal the manifest `principal_id`; the principal countersignature is checked against the exporter's delegation in both implementations.
- Bundle readers enforce decompression limits (64 MiB per entry, 512 MiB total, 10 000 entries) and report malformed archives with a fixed message instead of exception text.
- Anchors: `receipt.digest` is now required and must equal `anchored_hash`, and an `anchors/<record_id>.json` file must be the receipt of that anchor record, in both verifiers. Until now neither implementation looked at a receipt at all. The witness proof itself is still not verified offline: run reports carry `anchors_witness_verified` (0 in 0.1), the Inspector says so, and `TRUST.md` states the limitation.
- Schemas bound every `uniqueItems` array with `maxItems` (`delegations` and `runs` 1024, `data_labels` 64), `manifest.json` is capped at 4 MiB, and the Python verifier stops at the first schema error: a few-kilobyte upload could previously block the recorder for minutes in a quadratic uniqueness check.
- `ed25519` public keys must be canonical points of prime order, and `R` must be canonical and not the identity, in both verifiers. Previously a key holder could publish a torsion-shifted key, or sign with a mixed-order `R`, and get bundles that `@sealedrun/core` accepted and the Python verifier rejected.
- `es256` signatures are low-S only: the Python signer normalises `s` and both verifiers reject high-S. Previously about half of the `aat-compat-1` objects signed in Python were rejected by `@sealedrun/core`.
- `@sealedrun/core` validates the structure of manifests, delegations and records before use.
- Recorder listens on `127.0.0.1` by default; compose publishes the port to localhost only. Optional `SEALEDRUN_API_TOKEN` protects every API route except `/api/health` (Bearer).
- Recorder refuses state-changing requests whose `Sec-Fetch-Site` is not `same-origin`/`same-site`; without that header a valid bearer token is required. `TrustedHostMiddleware` limits the accepted Host names (`SEALEDRUN_ALLOWED_HOSTS`). The token is accepted as Bearer only and no `WWW-Authenticate: Basic` challenge is sent, so a browser holds no credential to replay; the Inspector asks for the token and sends it itself.
- Payload downloads are served as attachments with `nosniff`, a sandbox CSP and a media type allow-list.
- No default PostgreSQL password. Docker base images and GitHub Actions are pinned by digest; Dependabot keeps them current; workflow token is read-only by default.

### Changed

- Inspector redesigned: Tailwind CSS 4, self-hosted IBM Plex, a verdict block that separates "verified" from "intact, signer not confirmed", a welcome state that explains what to do with a "Try the example bundle" link, the run shown as a linked chain of steps, a proper token form, phone layout and keyboard focus.
- Renamed everything from AFR to SealedRun: packages `sealedrun`, `sealedrun-recorder`, `@sealedrun/core`, env prefix `SEALEDRUN_`, field `spec_version`, extension namespace `sealedrun.*`, signing domains `sealedrun/*/v1`. Vectors and examples regenerated.
- List endpoints are paginated (`limit`, `offset`); the bundle archive column is loaded only on download.
- JSON Schemas ship inside the `sealedrun` wheel.
- Public Python API carries PEP 257 docstrings and exported TypeScript symbols carry TSDoc; both are enforced by the linters (ruff `D`, `eslint-plugin-jsdoc`, `eslint-plugin-tsdoc`).

### Fixed

- The `sealedrun` wheel could not be built from its own sdist: the schema directory was a forced include that only exists in the repository. A build hook now handles both cases.

### Added

- `SECURITY.md`, `CODE_OF_CONDUCT.md`, `.github/dependabot.yml`.
- Release workflow: a `v*` tag builds the packages, installs them into a clean environment to verify the example bundle, and publishes to PyPI and npm through trusted publishing (OIDC, no stored tokens) behind a manually approved environment.

## [0.1.0] - 2026-09-17

Stage 0: specification, reference implementations, inspector, Docker image.

### Added

- Monorepo skeleton: Python workspace (`sealedrun`, `sealedrun-recorder`), pnpm workspace (`@sealedrun/core`, `@sealedrun/web`), GitHub Actions CI with GHCR image publishing.
- Licensing: Apache-2.0 for code and schemas, CC-BY-4.0 for the specification text, CLA.
- `SPEC.md`: SealedRun 0.1 draft (entities, RFC 8785 canonicalization, hash chain, hybrid Ed25519 + ML-DSA-65 signatures, delegation, tombstones, anchors, bundle format, mappings to SEP-3004, IETF AAT, OTel GenAI, EU AI Act Art. 12/19/26).
- `TRUST.md`: threat model and claims a bundle does and does not support.
- JSON Schema (draft 2020-12) in `spec/schema/`: `common.json`, `record.json`, `delegation.json`, `bundle.json`, `extensions/sealedrun.json`; `sealedrun.schema` loader with tests.
- Python reference library `sealedrun`: RFC 8785 canonicalization, SHA-256/384 hash chain, hybrid key sets (Ed25519 + ML-DSA-65, ES256 + ML-DSA-65, Ed25519 + ML-DSA-87), domain-separated signing, delegations, `RunWriter`, run verifier with exact failure location, bundle writer/reader/verifier.
- Test vectors in `spec/vectors/` (keys, RFC 8785 set, delegation, valid run, nine negative chains, four bundles) generated by `sealedrun.vectors`; worked examples in `spec/examples/`.
- TypeScript library `@sealedrun/core`: independent canonicalization, hashing, hybrid signature verification, run and bundle verification; passes all `spec/vectors/` (27 tests).
- `sealedrun-recorder` service (FastAPI + SQLAlchemy, SQLite by default, PostgreSQL via `SEALEDRUN_DATABASE_URL`): bundle upload with verification and import, verify-only endpoint, runs/records/payload API, serves the web UI when built.
- Web inspector (`apps/web`, Next.js static export): drag-and-drop bundle verification in the browser, step feed with target, location, labels, policy decision and payload preview, browsing of runs stored in the recorder.
- Docker: multi-stage `Dockerfile` (Node builds the UI, uv builds the Python env, slim runtime as non-root user with healthcheck, about 230 MB), `docker-compose.yml` with SQLite default and a `postgres` profile, `.env.example`.