[1.3.4] - 2026-10-12
Added
- Unknown config keys are reported (#1061) — a typo such as
[curation] mdoe = "enforce"used to be ignored without a word. NORA now logs every unknown key with its full path at start and on SIGHUP reload, including inside proxy entries, andNORA_CONFIG_STRICT=truerefuses them (exit 78; a reload keeps the running configuration). - npm: several upstreams routed by scope (#1062) —
npm.proxiessends each listed scope only to its own upstream, with its own Basic or Bearer credential (auth_bearer_env), and everything else to the default one; a private scope never reaches the public registry, not in metadata, tarballs, revalidation or audit requests. Cached objects record which upstream delivered them, and an owned scope is never served from a copy another upstream delivered. Rolling back to 1.3.3 or earlier withnpm.proxiesstill in the config sends the private scopes to the default upstream, because those releases ignore the key: removenpm.proxiesand setNORA_CURATION_INTERNAL_NS=@scope/*for each private scope first, so the older release never asks an upstream for those names (they are then served only from what is already stored).
Changed
- Library API:
nora_registry::validation::ValidationErroris#[non_exhaustive]and gainsReservedPath(#1060) — a storage key whose first segment is.nora-stagingis refused with the new variant. Code that matches the enum exhaustively needs a wildcard arm once; later variants will not break it again. The registry binary is unaffected. - npm: an unreachable upstream answers 502, and an audit NORA cannot check is refused (#1062) — a cold miss on a dead upstream answered 404, which clients report as a missing package; it is now 502. With owned scopes or an internal namespace, an audit body NORA cannot strip of those names gets a 4xx with the reason instead of being forwarded, gzip bulk bodies are decoded and checked, and pnpm's
/-/npm/v1/security/auditsis answered. Without any upstream configured, an audit still answers200 {}, which clients show as "0 vulnerabilities": a registry that only hosts packages has no advisories to check them against. - A failed upstream is answered 502, not 404, in Maven, Docker, Cargo, PyPI and Terraform (#1073) — when an artifact was not cached and every upstream failed (5xx, refused, timed out), these formats answered 404, which clients take as "does not exist": Maven records it in the local repository and keeps failing after the upstream is back until the update interval passes or
-Uis used. They now answer 502, as npm, Go, RubyGems, Conan, Ansible, CPAN and pub already did; a 404 still means an upstream answered that the artifact is not there (with several upstreams, one such answer is enough). NuGet, Debian and RPM keep 404, because their clients retry a 5xx several times per package. An installation that is offline on purpose should turn its default upstreams off, so that a miss stays a 404:proxies = []for Maven,upstreams = []for Docker, andNORA_CARGO_PROXY,NORA_PYPI_PROXYandNORA_TF_PROXYset to an empty value (in the config file a missingproxymeans the public default).
Fixed
- A dead upstream no longer opens the circuit breaker of a healthy one (#1059) — PyPI shared one breaker across all its indexes, so a dead secondary index cut off
pypi.orgtoo; PyPI and Docker now keep one breaker per configured upstream (nora_circuit_breaker_*{registry="<format>:<url>"}; the Grafana panel andMONITORING.mdfollow),overrides.dockeris applied instead of ignored, and every exit of an upstream fetch returns the half-open probe — a revalidation whose body broke off, and five Docker exits, left the breaker answering 503 until stall recovery. - No temp files left behind by a shutdown or a crash (#1060) — a SIGTERM during cache fills left
<key>.tmp.<pid>.<seq>files beside keys for good, and a write could truncate a stored key that happened to have its temp name. Temp files are now owned by a guard and created exclusively; local writes are staged in<root>/.nora-staging/<instance>/, which the next start removes for instances that are gone (a second instance on the same volume keeps its own); and streamedrawuploads cut off by a crash are swept fromtmp/raw-uploadsafter 4 hours idle. - A local-storage listing no longer reads the whole store (#1070) — listing a prefix on the local backend walked every directory under the storage root and filtered the keys afterwards, so its cost grew with the size of the store: on a store of 100,000 files an npm packument cache miss took about 240 ms, and a path the upstream answers 404 (a miss on every request) timed out under 32 concurrent clients. The walk now starts at the directory the prefix names and returns the same keys as before for everything NORA stores.
- A local-storage path that is a directory is no longer served as a file (#1072) — on the local backend a key whose path is a directory (
raw/dirwhileraw/dir/f.txtis stored, or a directory left empty by deletes) was opened as a file:HEADanswered 200 with the directory's size,GETbroke off after the headers,DELETEanswered 500; a path below a stored file answered 500 too. Such keys now answer as missing ones, as on S3. A rawPUTthe local store cannot hold because the name is taken answers 409 instead of 500. - A download refused by curation is recorded in the audit log (#1074) — a blocklist hit, an allowlist miss, namespace isolation or a failed integrity check answered 403 and counted in
nora_curation_decisions_total, but left no trace inaudit.jsonl. Each refusal now leaves onecuration_blockentry naming the caller,package@version, the registry andrule: reason.
Security
- The curation allowlist checks GitLab-style scoped npm tarballs (#1058) — a scoped tarball named in full (
@vendor/a/-/@vendor/a-1.0.0.tgz, as GitLab's npm registry serves it) yielded no version, and the allowlist lets a request without a version through as a metadata request, so an unlisted version of a package proxied from such a registry was served. Both filename forms are now read, and an unlisted version is blocked. - No upstream URL credentials in logs, metric labels or 503 bodies (#1059) — an upstream configured as
https://user:pass@hosthad its password logged at DEBUG, INFO and ERROR, and Docker's put it in a metric label and in the 503 body. Breaker keys drop the userinfo, and every log layer (stdout andNORA_LOG_FILE) masks it as***@. - The curation allowlist checks npm tarballs under a version directory (#1066) — Font Awesome's npm registry serves
<name>/-/<version>/<name>-<version>.tgz. That form yielded no version, and the allowlist lets a request without a version through as a metadata request, so an unlisted version of a package proxied from such a registry was served. The form is now read when the directory names the file's version, and an unlisted version is blocked. - The curation allowlist refuses downloads whose version NORA cannot read (#1067) — the allowlist let a request without a version through as a metadata request, and a download whose file name NORA could not parse arrived without one. So an unlisted version was served whenever its name took an unfamiliar form (npm, PyPI, CPAN). Under an allowlist in enforce mode such a download now gets 403, with a reason naming the package; audit mode logs it. Metadata, indexes and raw files are unaffected.
- npm tarballs from GitHub Packages are checked and cached as tarballs (#1069) — GitHub Packages serves tarballs at
download/<@scope/name>/<version>/<sha1>, a path without/-/, so NORA took it for a packument: curation did not run (an unlisted version was served under an allowlist), and the tarball was cached as metadata. Such a path is now that version's tarball: curation sees its version, and it is stored and served like any other tarball. Old entries undernpm/download/are no longer used and can be deleted.
Install
# x86_64
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.4/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/v1.3.4/nora-linux-arm64
chmod +x nora-linux-arm64
sudo mv nora-linux-arm64 /usr/local/bin/noraDocker
docker pull getnora/nora:1.3.4| Variant | Image | Platforms |
|---|---|---|
| Alpine (default) | getnora/nora:1.3.4
| amd64, arm64 |
| RED OS | getnora/nora:1.3.4-redos
| amd64 |
| Astra Linux SE | getnora/nora:1.3.4-astra
| amd64 |
| GHCR | ghcr.io/getnora-io/nora:1.3.4
| amd64, arm64 |
DEB / RPM
# Debian / Ubuntu / Astra Linux (amd64)
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.4/nora-amd64.deb
sudo dpkg -i nora-amd64.deb
# RHEL / Fedora / RED OS (amd64)
curl -LO https://github.com/getnora-io/nora/releases/download/v1.3.4/nora-amd64.rpm
sudo rpm -i nora-amd64.rpmChangelog
See CHANGELOG.md