Skip to content

v0.9.5

Latest

Choose a tag to compare

@devitway devitway released this 20 Jun 12:15
v0.9.5
ec94079

⚠️ Breaking changes — review before upgrading

  • A min_release_age-only proxy policy is now rejected at startup: enabling min_release_age on a proxy requires an active quarantine (or server.trust_upstream_dates where a real upstream date is available) (#742).
  • Minting an admin token via POST /api/tokens now requires the account to be listed in auth.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_age on a proxy path now defers to quarantine, because upstream publish dates are unsigned and several registries expose none. Breaking: enabling min_release_age on an enabled proxy now requires an active quarantine (or server.trust_upstream_dates where 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_age defers 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. When server.trust_upstream_dates is 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 700 upload-time), npm, Cargo, Go, NuGet, Conan, pub.dev, Maven (Central search API), RubyGems (v1 versions API), Ansible (Galaxy created_at) and Terraform (registry.terraform.io /v2 published-at — the standard provider protocol carries no date). Each upstream-date path is gated on trust_upstream_dates (spoofable, opt-in); hosted artifacts use cached-metadata mtime. The date is obtained on the artifact download path itself — Cargo self-primes metadata.json there (a cargo build resolves 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 under trust_upstream_dates and 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 (Range and full cache-hit, proxy-stored, proxy temp-file) and via HEAD without a quarantine check, and a local push could pre-mature a future upstream digest through the shared ledger key. Blob serves are now gated, and record_trusted was 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 returns 404 (not 403) so a caller cannot probe which ids exist (CWE-862).
  • Admin-token self-escalation blocked (GHSA-78cx-cfhm-rgmx) — the public POST /api/tokens route minted whatever role was requested, so any htpasswd account could self-mint an admin token. Breaking: an admin token may now be minted via this route only by an account listed in auth.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, set NORA_AUTH_ADMIN_USERS=<your-admin-user> before upgrading (CWE-862).

Added

  • Admin storage reindexPOST /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 return 400); the rebuild runs in the background and the call returns 202 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 lets min_release_age use a real upstream publish date where one is cached (e.g. npm time), as an enhancement to — not a substitute for — quarantine (#729).
  • npm /-/whoami endpoint — token-based identity so npm whoami resolves against a NORA token (#720).
  • auth.admin_users (NORA_AUTH_ADMIN_USERS) — a comma-separated list of htpasswd usernames permitted to mint admin-role tokens via POST /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 like host/port no 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 baked ENV, which silently won over a user-provided config.toml (env has the highest precedence in Config::load). Defaults now ship as a file (/etc/nora/config.toml, loaded via NORA_CONFIG_PATH); a bind-mounted config.toml takes full effect. Only NORA_HOST stays in ENV so binding survives a partial mounted config and the container stays reachable (#719).
  • Namespace isolation now covers every proxy registry's metadata pathinternal_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 blockedinternal_namespaces is documented as "never proxied upstream", but check_download ran 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 NuGet registration_index, pub.dev package_listing and RubyGems compact_index metadata 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 controlcuration.mode = enforce no longer hard-requires allowlist_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 whoami response — serialized via serde_json instead of format!, 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/nora

Docker

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.rpm

Changelog

See CHANGELOG.md