Skip to content

Commit f87485f

Browse files
authored
chore: release v1.3.0
Bump 1.2.2 -> 1.3.0 (Cargo.toml, openapi.rs, Cargo.lock; prod compose default). Cut CHANGELOG [1.3.0] with #896 (PEP 700), #849 (streaming integrity typestate), #369 (RegistryType dispatch), #891 (signing-key not enumerated), plus the S3/GCS pins, Repr-Digest raw integrity, and npm abbreviated/rebuild work.
1 parent f9a745a commit f87485f

5 files changed

Lines changed: 16 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# Changelog
22
## [Unreleased]
33

4+
## [1.3.0] - 2026-09-06
5+
46
### Added
7+
- **PyPI Simple JSON carries PEP 700 fields (#896)** — the PEP 691 JSON response (`application/vnd.pypi.simple.v1+json`) now also emits `meta.api-version: "1.1"`, a project-level `versions[]` list, and per-file `upload-time` (RFC 3339) and `size` (bytes), so tools like Renovate can compute a minimum release age without fetching every file. `size` is emitted for locally stored artifacts and `upload-time` from the cached upstream dates on the proxy path — each is included where known.
58
- **Hash pins on S3/GCS via object metadata** — the SHA-256 integrity pin is no longer a local-filesystem-only feature. On object-store backends it is written as the user-defined `sha256` object metadata, atomically with the object, and read back on GET/HEAD, so buffered reads verify at rest and raw files get `ETag`, `If-None-Match` (304) and `If-Match` conditional overwrite on every backend. Pins are now a backend concern: the local backend keeps its NDJSON sidecar (same path and format, no migration), the object-store backend keeps object metadata, and the storage wrapper only validates keys and runs the fail-closed verify gate. Objects written before the upgrade carry no metadata and stay open-world until they are rewritten; `nora re-pin` rewrites the object on an object store, since object metadata cannot be changed in place.
69
- **Raw upload integrity via `Repr-Digest` (RFC 9530)** — a raw `PUT` may declare `Repr-Digest: sha-256=:BASE64:`; NORA verifies the received body against it before committing, so a corrupted or truncated upload is rejected with `400` instead of being pinned. The pin itself is always the server-computed hash; the header only gates the commit. A `Repr-Digest` without a sha-256 entry is rejected rather than silently skipped.
710
- **npm serves the abbreviated packument to installers**`npm install` asks for `application/vnd.npm.install-v1+json`, and NORA ignored it and returned the full document to every client. The packument path now projects to npm's abbreviated shape, keeping the per-version fields an installer actually resolves on (`dependencies`, `os`, `cpu`, `engines`, `peerDependenciesMeta`, `dist`, `deprecated`) and dropping readme, maintainers, repository, per-version `description`, `scripts` and `gitHead`. Measured against a live upstream with every version preserved: lodash 247 652 → 71 989 B (−70.9%), express 804 975 → 344 703 B (−57.2%). The short form is derived locally rather than requested upstream, so exactly one canonical object stays cached per package and a short document can never displace the full one; `Vary: Accept` goes with it, because metadata is `Cache-Control: public` and the body now varies by a request header. An unparsable body is served unchanged rather than turned into an error (#957).
811

12+
### Changed
13+
- **Compile-time integrity witness on the streaming serve path (#849)** — the streaming artifact serve now routes through a sealed sole-sink whose only constructor takes an EOF-verifying stream, so handing a raw reader to the response body on an integrity path is a compile error — the type-level match of the buffered `verified_body` sink. A blob tampered on disk aborts the body mid-stream (the client gets a broken transfer, never the tampered bytes under a clean `200`) instead of streaming out unverified; explicit partial-content range serves take a separate open-world sink.
14+
- **Registry dispatch is keyed on the `RegistryType` enum (#369)** — dispatch across config, retention, metrics and the UI is now an exhaustive `match RegistryType` generated from one list, instead of scattered string comparisons. Adding a format is a single line and can no longer silently miss a call site (it becomes a compile error). No behavioral change.
15+
16+
### Security
17+
- **The repository signing key is never enumerated by storage `list()` (#891)** — the OpenPGP signing key at `<storage.path>/.signing/nora.key` (persisted owner-only, `0600`) was swept into every enumeration-based operation because `list()`/`list_with_meta()` excluded only the pin sidecar: `backup` wrote it into the tar at `0644`, `migrate --to s3` copied it into the bucket as a plaintext object, and GC/retention and the browse UI treated it as an artifact. Both backends now exclude the `.signing/` prefix from enumeration, so the key can neither be exfiltrated (tar / object) nor deleted; it is loaded via direct filesystem I/O and never through `list()`, so there is no runtime impact. Provision the key out-of-band.
18+
919
### Fixed
1020
- **npm rebuilds a missing packument instead of answering 404** — a hosted package whose derived `metadata.json` was absent returned `404` while every published version was still sitting in storage. The reassembly already existed (`regenerate_packument`, which lists `versions/`, `dist-tags/` and `pkg.json`) but only the publish path reached it, so a read fell through to the upstream proxy and 404'd for a package that exists only in this registry. The read path now rebuilds when `versions/` is non-empty, serves the result and re-materializes the packument so the cost is paid once — under the same `publish_lock` as publish, so a fleet stampeding one package rebuilds it once rather than once per request, and before the namespace guard, because serving locally-owned bytes is always allowed while that guard exists to stop the upstream fetch. A name with nothing behind it still returns 404. New `nora_packument_rebuilt_total{registry}`: a non-zero rate means storage was written or restored outside NORA (#956).
1121

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ members = [
66
]
77

88
[workspace.package]
9-
version = "1.2.2"
9+
version = "1.3.0"
1010
edition = "2021"
1111
rust-version = "1.88"
1212
license = "MIT"

deploy/docker-compose.prod.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Production Docker Compose for NORA Registry
2-
# Usage: NORA_VERSION=1.0.0 docker compose -f deploy/docker-compose.prod.yml up -d
2+
# Usage: NORA_VERSION=1.3.0 docker compose -f deploy/docker-compose.prod.yml up -d
33
#
44
# Required env vars (set in .env or shell):
5-
# NORA_VERSION — image tag (default: 1.0.0)
5+
# NORA_VERSION — image tag (default: 1.3.0)
66
# NORA_PUBLIC_URL — external URL for download links
77
# NORA_AUTH_ENABLED — enable authentication (default: true)
88

99
services:
1010
nora:
11-
image: ghcr.io/getnora-io/nora:${NORA_VERSION:-1.0.0}
11+
image: ghcr.io/getnora-io/nora:${NORA_VERSION:-1.3.0}
1212
restart: unless-stopped
1313
read_only: true
1414
tmpfs:

nora-registry/src/openapi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::AppState;
2121
#[openapi(
2222
info(
2323
title = "Nora",
24-
version = "1.2.2",
24+
version = "1.3.0",
2525
description = "Multi-protocol package registry supporting Docker, Maven, npm, Cargo, PyPI, Go, Raw, RubyGems, Terraform, Ansible, NuGet, pub.dev, Conan, RPM, and Debian",
2626
license(name = "MIT"),
2727
contact(name = "The NORA Authors", url = "https://getnora.dev")

0 commit comments

Comments
 (0)