Audit fixes: sorted fingerprints, lzma removal, sdist-true CI - 0.5.0.0 - #12
Merged
Conversation
C++ Nix computes and verifies the narinfo fingerprint over a StorePathSet - references sorted by basename, deduplicated - so a narinfo signed in its file order carries a signature every real Nix client rejects, while nova-cache's own verify (recomputing from the same unsorted list) passes and masks the divergence. fingerprint now sorts and deduplicates before rendering, and the new tests pin an unsorted and a duplicated reference list to the sorted fingerprint. Workflows run with a read-only GITHUB_TOKEN: no job writes through it (checkout, hlint/ormolu annotations, cache, and the SSH/API-key deploy path are all read-only against the API).
….5.0.0 NovaCache.Compression had no consumer - nothing in the library, the server, or downstream nova-nix imports it - while its default-on manual flag made every Hackage install of this package (and every downstream) require system liblzma dev files, which plain Windows and minimal Linux machines lack. cabal install failed for users while CI stayed green, because CI builds inside the repo where the dev-only constraint pins the flag off. A capability lands with its consumer: xz support returns as a size-bounded decoder suitable for untrusted cache data when substitution from xz-serving caches arrives. Also in the release: the sdist ships NOTICE (Apache-2.0 section 4(d) attribution - the file existed in the repo but not in the tarball), and the server executable drops its unused crypton dependency (its only ByteArray use, constEq, comes from ram). Removing an exposed module is a PVP major bump: 0.5.0.0.
…e sdist The build matrix compiled only the library - the server executable (where all handler logic lives) was never built by CI on any OS, and test suites compiled without -Werror. Every job now runs cabal build all -f server --enable-tests --ghc-options=-Werror, and the compression-flag matrix plumbing (liblzma install, per-OS flag conditionals) goes away with the flag. A new sdist job unpacks the cabal sdist outside the repo and builds it - the tarball users install, not the git tree CI otherwise checks - so files missing from the sdist or dev-only project settings fail the pipeline instead of surfacing at install time.
…ptional Validation now enforces the wire format's per-field spellings: the narinfo StorePath is absolute and References are bare basenames. The other spellings validated - and were then signed - as narinfos every real Nix client rejects at parse time, and a bare StorePath also derived an empty store dir inside the signed fingerprint. parseStorePathBaseName and parseAbsoluteStorePath expose the per-field parsers. Store-path names are ASCII-only (Unicode isAlphaNum admitted names Nix rejects) and capped at 211 characters, both as upstream. NAR parsing rejects backslash in entry names - a directory separator on Windows, this library's primary consumer, so "..\out.exe" is as much a traversal vector as one with '/' - and rejects nonzero string padding, which upstream's reader refuses. Key parsing rejects an empty name or empty key material at load time: an empty-named key signs every narinfo with :sig lines no named trust anchor can ever match, surfacing as silent signature rejection far from the misconfiguration. Narinfo parsing now mirrors upstream's optionality: only StorePath, URL, NarHash, and NarSize are required; Compression defaults to bzip2, and FileHash/FileSize are Maybe (a breaking record change, covered by the 0.5.0.0 major bump). Valid narinfos from foreign caches no longer fail to parse over absent optional fields. Tests pin the lot: the empty-string SHA-256 nix-base32 known-answer vector (a wrong alphabet or bit order is invisible to roundtrip-only tests), non-canonical base32 padding rejection, cross-key and renamed-trust-anchor signature rejection, malformed signature lines, unsafe/duplicate/out-of-order NAR entry names via hand-encoded wire bytes, trailing-byte and nonzero-padding rejection, NAR roundtrip edges, CRLF narinfo parsing, CA field roundtrip, and the four-field minimal narinfo. FileStore tests use fresh unique temp dirs instead of a fixed machine-global path that poisoned later runs and raced checkouts.
Contributor
Author
|
Extended with the library strictness batch per review discussion: wire-format spellings enforced per field (absolute StorePath, basename references), ASCII-only names with the 211-char cap, NAR backslash-name and nonzero-padding rejection, empty key name/material rejection, upstream-optional narinfo fields (Compression defaults bzip2; FileHash/FileSize now Maybe - breaking, covered by the major bump), plus the known-answer and negative-case test batch (nix-base32 empty-string vector, cross-key/renamed-anchor signature rejection, hand-encoded hostile NAR wire bytes, CRLF/CA narinfo coverage, unique FileStore temp dirs). Full gate green: build all -f server --enable-tests -Werror, all 8 test groups, ormolu, hlint, cabal check, sdist. |
createDirectoryIfMissing lost its last caller when the FileStore test dir moved to unique probed names; -Werror rightly makes the redundant import fatal.
Routing, write auth, body limits, and the narinfo validation/signing pipeline move into the library; the executable keeps configuration, key loading, and its own landing page (embedders supply theirs). Hardening riding along: /narinfo-hashes is authenticated and no-store, HEAD is served wherever GET is, NAR transfer streams via the new FileStore primitives, an empty-after-normalize CACHE_API_KEY refuses startup, and the bind host is configurable (--host / HOST, default unchanged).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
nova-cache's batch from the July 2026 codebase audit, prepared as the 0.5.0.0 release.
Signing correctness (the audit's one nova-cache HIGH)
C++ Nix computes and verifies narinfo fingerprints over a sorted, deduplicated store-path set. Signing references in the narinfo's file order produced signatures every real Nix client rejects - while nova-cache's own
verify, recomputing from the same unsorted order, passed and masked the divergence.fingerprintnow sorts by basename and deduplicates before rendering; tests pin unsorted and duplicated reference lists to the sorted fingerprint.lzma removal
NovaCache.Compressionhad no consumer anywhere (library, server, or downstream nova-nix), while its default-on manual flag made every Hackage install require system liblzma dev files -cabal installfailed on plain Windows and minimal Linux machines while CI stayed green, because CI builds inside the repo where a dev-only constraint pins the flag off. The module, flag, dependency, and its test suite are removed; xz support returns with its first real consumer as a size-bounded decoder suitable for untrusted cache data. Removing an exposed module is a PVP major bump, hence 0.5.0.0.Server hardening
The whole HTTP surface, previously zero-test executable code, moves into the library as
NovaCache.Server(a WAIApplication) with a WAI test group covering every route, auth path, limit, and traversal case. Deployment branding stays out of the library - embedders supply their own root page; the bundled executable keeps the cache.novavero.ai landing page in its own module.GET /narinfo-hashesrequires the write key and isCache-Control: no-store- it enumerates the whole store (the public protocol deliberately never offers that) and lists a directory per hit. The push client gains the header in the queued nova-nix follow-up PR.responseFile. A multi-GB NAR previously occupied that much RAM per request in both directions.HEADis answered whereverGETis (narinfo existence probes previously got 404).CACHE_API_KEYthat normalizes to empty (an empty armed key would authenticate an empty bearer token) and on a configured signing key that fails to load (previously a warning, then running unsigned).--host/HOST); the default stays all interfaces so existing deployments do not silently rebind.Packaging and CI
NOTICE(Apache-2.0 section 4(d) attribution; the file existed in the repo but not in the released tarball).-Werroron every OS (previously the server, which holds all handler logic, was never built by CI at all).latest.cryptondependency; workflows run with a read-onlyGITHUB_TOKEN.Verification
cabal build all -f server --enable-tests --ghc-options=-Werrorclean, all 9 test groups pass (including the new Server WAI group), ormolu and hlint clean,cabal checkclean, and the 0.5.0.0 sdist builds withNOTICE,NovaCache.Server, and the exe'sLandingPagepresent and no compression remnants.After this merges and releases, nova-nix follows up by dropping its
nova-cache -compressionconstraint and adding its own sdist CI job (that constraint names a flag that no longer exists, so the two must move together).