v1.2.1 - Safeword
[v1.2.1] - Safeword - 7/31/2026
Maintenance and security release. closes #267, #268, #269, #270
Fixed
- Cypher
WHERE ... LIMITnow preserves predicate semantics on non-streaming
storage wrappers.
The MATCH fast path could mark a compilable predicate as already evaluated,
then fall back to a non-streaming node scan that never applied it. This made
predicates such asn.content CONTAINS "safe to delete"return unrelated
rows when aLIMITwas present. The fallback now applies the full WHERE
predicate before limiting results, restoring Neo4j's exact-substring
semantics for multi-wordCONTAINSand preventing unsafe false-positive
matches from reaching downstream mutations. - MCP
discoverresults now use one bounded relevance scale and are ordered
by the returnedsimilarityvalue.
Cross-chunk RRF previously controlled result order whilesimilarityexposed
a mixture of cosine and raw BM25 values, so an exact lexical match could carry
a score such as49.09below unrelated0.xresults. Vector-backed hits now
retain their cosine similarity through stage-2 reranking, lexical-only scores
are monotonically normalized to[0,1], and final results are sorted by that
same client-visible value. Themin_similaritytool schema now documents and
applies the normalized relevance contract. - New property-key dictionary entries are now durable before nodes and
relationships can reference them.
Property-key metadata was previously persisted in a best-effort transaction
after the entity commit, allowing a cleanly acknowledged write to leave an
unreopenable store if that metadata write failed. New dictionary tokens are
now persisted before entity records commit, persistence errors abort the
write, and failed allocations are staged again on retry. This prevents
startup failures such asproperty key id N not in dictionaryafter storing
a previously unseen property name. - Automatic snapshot and WAL recovery now works when the data directory is a
container bind-mount root.
Linux rejects attempts to rename a mount point withEBUSY, which previously
trapped standard-v nornicdb-data:/datadeployments in a restart loop after
recovery replay had already succeeded. Auto-recovery now falls back to moving
the corrupted store's children into a hidden forensic directory within the
mount, then rebuilds the recovered Badger store at the original mount root. - OAuth browser flows now reject unsafe callback URLs and issue secure
authentication cookies.
The local OAuth provider validates callback URLs both before rendering
consent and before redirecting, rejecting external hosts, non-HTTP(S)
schemes, embedded user information, and fragments. Browser authentication
cookies, including sessions created through Basic authentication, are now
markedSecure, and the OAuth test harness registers callback URLs
explicitly instead of accepting arbitrary redirect destinations. - Persistence operations are now confined to their configured filesystem
roots.
Search metadata and indexes, vector stores, build snapshots, Badger and JSON
backups, and APOC exports now use rooted filesystem capabilities instead of
resolving caller-influenced paths and then opening them by absolute path.
Non-canonical and traversal paths are rejected at the operation boundary,
database names must be a single path component before search artifacts are
created or removed, and atomic replacement and cleanup remain inside the
configured root. - Untrusted sizes and integer conversions can no longer wrap into unsafe
allocations.
Added checked conversion and allocation helpers across Cypher execution,
traversal, FastRP, search and vector persistence, storage, temporal indexes,
GraphQL, GPU backends, and protocol adapters. User-controlledLIMITand
dimension values are no longer used as unchecked capacity hints, serialized
vector sizes are validated before allocation, and lossy GPU/HNSW conversions
now fail instead of truncating or wrapping. - Credentials and other sensitive values are no longer exposed through
routine logs or command output.
Authentication, OAuth, Bolt, storage, search, replication, Heimdall, and the
macOS menu-bar paths now omit or redact passwords, API keys, JWTs, query
parameters, record contents, and other secret-bearing values. CLI startup
output also avoids echoing credentials supplied through flags or connection
strings. - Security-sensitive cache and composite-key hashes now use keyed SHA-256.
Authentication cache keys and storage composite-key indexes no longer rely
on weaker non-cryptographic hashing, reducing collision and
hash-manipulation risk while preserving deterministic lookup within a
running process. - Swagger UI bootstrap values are now rendered through safe structured
encoding.
Configured OpenAPI and OAuth values are no longer interpolated directly into
executable HTML/JavaScript, preventing crafted configuration values from
breaking out of their intended context. - The macOS file indexer now treats ignore rules as glob patterns without
compiling caller-provided regular expressions.
Direct glob matching removes regex-injection and pathological-expression
behavior while preserving the intended ignore-file semantics; related
menu-bar logging also no longer prints JWT or API-key details. - GitHub Actions workflows now use least-privilege token permissions.
Default workflow permissions are read-only, with cache write access granted
only to the image-build jobs that require it. - APOC remote URL loads are now denied by default, and internal Cypher rewrites now reuse the shared escaped-literal path.
apoc.load.json,apoc.load.jsonArray,apoc.load.csv, and
apoc.import.jsonno longer issue arbitrary outbound HTTP(S) fetches unless
operators explicitly enableallow_remote_url_accessor
NORNICDB_APOC_SECURITY_ALLOW_REMOTE_URL_ACCESS, and explicitly allow the
destination host. The remote fetch path now uses a hardened HTTP client with
redirects disabled and rejects hosts that resolve to loopback, private,
link-local, multicast, or unspecified addresses. Separately, the
WITH/subquery/CALL rewrite paths now route string and map literal rendering
through the shared escaped Cypher literal helpers instead of ad hoc quoting,
closing several executable query interpolation sinks. - Updating a relationship a peer transaction committed after this
transaction began is now a retryable transient conflict instead of a hard
"not found" error.
Inside an explicit transaction,MERGEresolves an existing relationship
through a latest-committed lookup, but the property update read the same
edge through the transaction's begin-time snapshot. When a peer session
MERGEd the same relationship with different property values and committed
after the transaction began, the edge was found but not updatable, and
MERGE ... SET(both the plain andUNWINDbatch forms) failed with
"not found", surfaced over Bolt as the non-retryable
Neo.ClientError.Statement.SyntaxError. Neo4j succeeds on this exact
interleaving (itsMERGEblocks on the relationship lock, re-reads, and
applies theSET), so drivers' managed-transaction retry never engages on
the NornicDB error. The storage layer now classifies a snapshot-invisible
but live edge as the existing conflict shape
(conflict: edge <id> changed after transaction start→
Neo.TransientError.Transaction.Outdated) already used for the same race
at commit time, so managed transactions (session.ExecuteWrite) retry on
a fresh snapshot and converge to one relationship with the retrying
writer's values. The reclassification is tombstone-aware: an edge the peer
actually deleted still reports "not found", and snapshot-isolated reads
are unchanged.
What's Changed
- fix(storage): classify post-snapshot edge updates as retryable conflicts by @linuxdynasty in #266
Full Changelog: v1.2.0...v1.2.1