⚠️ Breaking changes — review before upgrading
- A
min_release_age-only proxy policy is now rejected at startup: enablingmin_release_ageon a proxy requires an active quarantine (orserver.trust_upstream_dateswhere a real upstream date is available) (#742).- Minting an
admintoken viaPOST /api/tokensnow requires the account to be listed inauth.admin_users(NORA_AUTH_ADMIN_USERS), which is empty by default. Set it before upgrading if you rely on this route (GHSA-78cx-cfhm-rgmx).
Security
- First-seen digest-quarantine generalized to every proxy registry — the unspoofable first-seen cooldown (previously Docker-only) now guards all 11 proxy registries (npm, PyPI, Cargo, Go, Maven, RubyGems, NuGet, Conan, pub.dev, Terraform, Ansible).
min_release_ageon a proxy path now defers to quarantine, because upstream publish dates are unsigned and several registries expose none. Breaking: enablingmin_release_ageon an enabled proxy now requires an active quarantine (orserver.trust_upstream_dateswhere a real upstream date is available); a min-age-only proxy policy is rejected at startup (#741, #742). - Release-age freshness honored with
trust_upstream_dates(#748) — under #742,min_release_agedefers to quarantine, which holds on NORA's own clock, so a provably-old artifact was held as "new to this mirror" regardless of its release date. Whenserver.trust_upstream_datesis set and the registry supplies a date, the quarantine now seeds first-seen from the trusted upstream release date — an artifact older than the TTL matures immediately and is served, while a fresh one is still held. Wired for every dated proxy registry: PyPI (PEP 691 / PEP 700upload-time), npm, Cargo, Go, NuGet, Conan, pub.dev, Maven (Central search API), RubyGems (v1 versions API), Ansible (Galaxycreated_at) and Terraform (registry.terraform.io/v2published-at— the standard provider protocol carries no date). Each upstream-date path is gated ontrust_upstream_dates(spoofable, opt-in); hosted artifacts use cached-metadata mtime. The date is obtained on the artifact download path itself — Cargo self-primesmetadata.jsonthere (acargo buildresolves via the sparse index and never hits/api/v1/crates/{name}, so the date would otherwise never be cached), mirroring PyPI's date self-prime. Docker stays on NORA's own clock (digest-addressed, no trusted date). Internal-namespace coordinates are never sent to a hardcoded public date source (Maven Central search, registry.terraform.io/v2) — the date query is skipped for internal namespaces (#68/#733). Note: Maven, RubyGems and Terraform query their date source per download request (not cached like PyPI/Cargo/Ansible); this fires only undertrust_upstream_datesand is timeout-bounded and fail-safe — caching is a tracked follow-up. - Docker digest-quarantine bypass fixed (GHSA-4j4m-fchf-gr9r) — layer/config blobs were served on every path (
Rangeand full cache-hit, proxy-stored, proxy temp-file) and viaHEADwithout a quarantine check, and a local push could pre-mature a future upstream digest through the shared ledger key. Blob serves are now gated, andrecord_trustedwas removed so the ledger records only proxy-fetched content (CWE-693, CWE-345). - Token-management broken access control fixed (GHSA-78cx-cfhm-rgmx) — the token-management endpoints (
/ui/tokens,/api/ui/tokens, and the public/api/tokens/revoke) authenticated the caller but never authorized them, so any write-capable bearer/OIDC identity could enumerate and revoke other users' tokens (including admin and service tokens), and a read identity could enumerate them. List and revoke are now owner-scoped — a non-admin acts only on its own tokens, while admins still manage all — and a non-owned id returns404(not403) so a caller cannot probe which ids exist (CWE-862). - Admin-token self-escalation blocked (GHSA-78cx-cfhm-rgmx) — the public
POST /api/tokensroute minted whatever role was requested, so any htpasswd account could self-mint anadmintoken. Breaking: anadmintoken may now be minted via this route only by an account listed inauth.admin_users(NORA_AUTH_ADMIN_USERS), which is empty by default; read and write tokens are unaffected. If you rely on this route to create admin tokens, setNORA_AUTH_ADMIN_USERS=<your-admin-user>before upgrading (CWE-862).
Added
- Admin storage reindex —
POST /api/v1/admin/reindex(admin-role token only) refreshes the in-memory indexes from storage so the UI reflects artifacts copied in out-of-band (rsync, Unison, BTRFS send/receive, S3 sync) without a container restart or a dummy client pull. Optional?registry=<name>scopes the rebuild to one registry (unknown names return400); the rebuild runs in the background and the call returns202 Accepted. Repeated calls are debounced (429+Retry-After). The index is process-local, so under a multi-replica deployment the call refreshes only the replica that served it — reindex each replica or roll the deployment (#735). server.trust_upstream_dates— opt-in flag that letsmin_release_ageuse a real upstream publish date where one is cached (e.g. npmtime), as an enhancement to — not a substitute for — quarantine (#729).- npm
/-/whoamiendpoint — token-based identity sonpm whoamiresolves against a NORA token (#720). auth.admin_users(NORA_AUTH_ADMIN_USERS) — a comma-separated list of htpasswd usernames permitted to mintadmin-role tokens viaPOST /api/tokens; the bootstrap for admin designation (GHSA-78cx-cfhm-rgmx).
Fixed
- Index rebuild no longer caches a failed storage scan as a fresh empty result — if the storage listing errored mid-rebuild, the index was cached empty and clean, so the UI could report zero artifacts on healthy data until the next write. A failed scan now leaves the index dirty and retries on the next read (#735).
- Partial
config.toml— missing[server],[storage], or fields likehost/portno longer prevent startup; serde defaults are applied for all unset values. - Container image no longer overrides
config.toml— the image shipped config values (NORA_PUBLIC_URL,NORA_PORT,NORA_STORAGE_PATH,NORA_AUTH_TOKEN_STORAGE) as bakedENV, which silently won over a user-providedconfig.toml(env has the highest precedence inConfig::load). Defaults now ship as a file (/etc/nora/config.toml, loaded viaNORA_CONFIG_PATH); a bind-mountedconfig.tomltakes full effect. OnlyNORA_HOSTstays inENVso binding survives a partial mounted config and the container stays reachable (#719). - Namespace isolation now covers every proxy registry's metadata path —
internal_namespaces(the dependency-confusion defense, always active) previously gated only the download/tarball path, so a metadata / index / version-list / search request for an internal-namespace package leaked its name upstream on every proxy registry except npm. The guard now runs on the metadata path of PyPI, Cargo, Maven, Go, NuGet, Conan, pub.dev, Terraform, Ansible and RubyGems — and on the NuGet/Conan search query — serving any locally-published or cached copy first and blocking only the genuine upstream fetch (no leak, and no false 403 on a locally-published internal package). The npm TTL-stale metadata refetch is also guarded, closing a residual of #725 (contrib-kit#68). - Locally-published internal packages are served instead of being blocked —
internal_namespacesis documented as "never proxied upstream", butcheck_downloadran the always-on namespace filter before the local serve, so a mixed proxy+host instance returned 403 for its own internal packages on every download path (npm, PyPI, Cargo, Maven, Conan, RubyGems, NuGet, pub.dev, Go, Ansible, Docker, raw) and on the NuGetregistration_index, pub.devpackage_listingand RubyGemscompact_indexmetadata paths. An internal name now serves any local/cached copy first and blocks only the genuine upstream fetch; an internal name with no local copy is still blocked and never proxied. Non-internal behavior is unchanged (#733). - Enforce mode requires at least one active control —
curation.mode = enforceno longer hard-requiresallowlist_path; a blocklist-only, min-release-age-only, or quarantine-only policy is valid, and enforce is rejected only when no control of any kind is configured (#740). - Basic-auth accepts an API token as the password — clients sending an API token over HTTP Basic auth (
user:<token>) are now authenticated, matching the token-in-header behavior (#737). - Crash durability — the parent directory is
fsync'd after the atomic rename, so a published artifact survives a power loss immediately after write (#723). - npm scoped-package publish — scoped attachment filenames (
@scope/name) are normalized, so the tarball is stored and served under the correct key (#724). - npm
whoamiresponse — serialized viaserde_jsoninstead offormat!, avoiding malformed output on unusual usernames (#722).
Security acknowledgments
- GHSA-78cx-cfhm-rgmx — token-management broken access control and admin-token self-escalation (CWE-862). Reported by @endscene665 via responsible disclosure. Thank you.
- GHSA-4j4m-fchf-gr9r — Docker digest-quarantine bypass (CWE-345 / CWE-693). Found internally during the quarantine-generalization audit.
Install
# x86_64
curl -LO https://github.com/getnora-io/nora/releases/download/v0.9.5/nora-linux-amd64
chmod +x nora-linux-amd64
sudo mv nora-linux-amd64 /usr/local/bin/nora
# ARM64 (Apple Silicon, Graviton, Ampere)
curl -LO https://github.com/getnora-io/nora/releases/download/v0.9.5/nora-linux-arm64
chmod +x nora-linux-arm64
sudo mv nora-linux-arm64 /usr/local/bin/noraDocker
docker pull getnora/nora:0.9.5| Variant | Image | Platforms |
|---|---|---|
| Alpine (default) | getnora/nora:0.9.5 |
amd64, arm64 |
| RED OS | getnora/nora:0.9.5-redos |
amd64 |
| Astra Linux SE | getnora/nora:0.9.5-astra |
amd64 |
| GHCR | ghcr.io/getnora-io/nora:0.9.5 |
amd64, arm64 |
DEB / RPM
# Debian / Ubuntu / Astra Linux (amd64)
curl -LO https://github.com/getnora-io/nora/releases/download/v0.9.5/nora-amd64.deb
sudo dpkg -i nora-amd64.deb
# RHEL / Fedora / RED OS (amd64)
curl -LO https://github.com/getnora-io/nora/releases/download/v0.9.5/nora-amd64.rpm
sudo rpm -i nora-amd64.rpmChangelog
See CHANGELOG.md