[1.3.0] - 2026-09-06
Added
- PyPI Simple JSON carries PEP 700 fields (#896) — the PEP 691 JSON response (
application/vnd.pypi.simple.v1+json) now also emitsmeta.api-version: "1.1", a project-levelversions[]list, and per-fileupload-time(RFC 3339) andsize(bytes), so tools like Renovate can compute a minimum release age without fetching every file.sizeis emitted for locally stored artifacts andupload-timefrom the cached upstream dates on the proxy path — each is included where known. - 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
sha256object metadata, atomically with the object, and read back on GET/HEAD, so buffered reads verify at rest and raw files getETag,If-None-Match(304) andIf-Matchconditional 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-pinrewrites the object on an object store, since object metadata cannot be changed in place. - Raw upload integrity via
Repr-Digest(RFC 9530) — a rawPUTmay declareRepr-Digest: sha-256=:BASE64:; NORA verifies the received body against it before committing, so a corrupted or truncated upload is rejected with400instead of being pinned. The pin itself is always the server-computed hash; the header only gates the commit. ARepr-Digestwithout a sha-256 entry is rejected rather than silently skipped. - npm serves the abbreviated packument to installers —
npm installasks forapplication/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-versiondescription,scriptsandgitHead. 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: Acceptgoes with it, because metadata isCache-Control: publicand the body now varies by a request header. An unparsable body is served unchanged rather than turned into an error (#957).
Changed
- 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_bodysink. A blob tampered on disk aborts the body mid-stream (the client gets a broken transfer, never the tampered bytes under a clean200) instead of streaming out unverified; explicit partial-content range serves take a separate open-world sink. - Registry dispatch is keyed on the
RegistryTypeenum (#369) — dispatch across config, retention, metrics and the UI is now an exhaustivematch RegistryTypegenerated 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.
Security
- 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 becauselist()/list_with_meta()excluded only the pin sidecar:backupwrote it into the tar at0644,migrate --to s3copied 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 throughlist(), so there is no runtime impact. Provision the key out-of-band.
Fixed
- npm rebuilds a missing packument instead of answering 404 — a hosted package whose derived
metadata.jsonwas absent returned404while every published version was still sitting in storage. The reassembly already existed (regenerate_packument, which listsversions/,dist-tags/andpkg.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 whenversions/is non-empty, serves the result and re-materializes the packument so the cost is paid once — under the samepublish_lockas 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. Newnora_packument_rebuilt_total{registry}: a non-zero rate means storage was written or restored outside NORA (#956).
Install
# x86_64
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.0/nora-linux-amd64
chmod +x nora-linux-amd64 && sudo mv nora-linux-amd64 /usr/local/bin/nora
# ARM64
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.0/nora-linux-arm64
chmod +x nora-linux-arm64 && sudo mv nora-linux-arm64 /usr/local/bin/noraDocker
docker pull ghcr.io/getnora-io/nora:1.3.0 # or getnora/nora:1.3.0| Variant | Image | Platforms |
|---|---|---|
| Alpine (default) | getnora/nora:1.3.0 / ghcr.io/getnora-io/nora:1.3.0 |
amd64, arm64 |
| RED OS | getnora/nora:1.3.0-redos |
amd64 |
| Astra Linux SE | getnora/nora:1.3.0-astra |
amd64 |
DEB / RPM
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.0/nora-amd64.deb && sudo dpkg -i nora-amd64.deb
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.0/nora-amd64.rpm && sudo rpm -i nora-amd64.rpmFull changelog: CHANGELOG.md