v1.27.16
[1.27.16] — 2026-08-16
Release notes
Security fixes
- Auth degrades closed, never open. A poisoned token-store lock was an
empty set → "auth disabled" → allow-all; it is now fail-closed
500 auth_store_unavailable. A configured-but-empty token store (file or
env set, zero tokens) denied everything; it now returns 401 instead of
reading as "no auth". The JWT revocation check (v1.2.0) skipped itself on
ANY pool/SQL error (if let Ok(conn)+unwrap_or(false)); any store
failure now denies. The role-retrieval gate (v1.23.0) degraded to "no
narrowing" (read everything) on a pool/role-store error; it now degrades to
the empty permit (read nothing) with awarn!./auth/logoutis no longer
a public route: the presented access token is verified by the middleware
first — an unauthenticated "logout" could only ever succeed at revoking
nothing. - The multi-db domain registry is now registered-only and capped. In
BRAIN_MULTI_DB=true,pool_forNEVER opens a file for an unregistered
name (previously any probeable read createdbrain-<name>.dblazily —
unbounded disk fill).POST /domainsis the one creation path, bounded by
BRAIN_MAX_DOMAIN_DBS(default 256; 507insufficient_storagebeyond it);
every resolution read of an unknown name returns the probe-blind 404
domain_unknown(indistinguishable from an empty-but-real domain). The
clients-register boot seed keeps client domains resolvable if their file
vanished between boots (recreated on first access, still cap-bounded). - JWT principals are domain-scoped on reads.
/searchnow authorizes
against the domain it actually queries (was alwaysglobal)./get/{id}
and/multi-getbind the header'sX-Brain-Domainlabel in SQL — an id
can never cross domains in shim mode — re-authorize on the row's own
domain, and run the same record gate (v1.14 scopes + v1.23 roles) recall
enforces; foreign rows read as 404 / are dropped, never loud. Recall
federation and graph traversal drop foreign-domain targets before any
search runs; shim-mode graph edges scope by their chunk's provenance label
(an unlinked edge is invisible to scoped readers). - Trust labels are closed vocabularies at the write boundary.
/ingest
rejects an unknown/mixed-casememory_kind(400invalid_memory_kind—
no silent fallback tofact) and aconfidenceoutside0.0..=1.0(400
invalid_confidence— no silent clamping, a clamped lie hides the liar);
the proposal path (/proposals) enforces the same strict kind round-trip.
A JWT (agent) principal on/addmay only use the closedsource
vocabulary (ingest kinds + connector family kinds) —manual, the
origin:humanmarker, is excluded so a token-authenticated agent cannot
forge human authorship. The UMP L3 operator signing key now fails closed to
L2 on a group/world-readable seed file (same 0600 enforcement the other
secrets get). - The per-IP rate limiter actually was not per-IP. The serve wiring never
injected the peerSocketAddrextension, so every client shared ONE
"unknown" bucket — a global rate limit in practice. The server now serves
withinto_make_service_with_connect_info, buckets are keyed by remote
address (production-behavior pinned by a source-inspection test), and the
bounded key set (RATE_LIMIT_MAX_KEYS) evicts the oldest 25% rather than
growing unbounded.
Improvements
None.
Bug fixes
None.
Full Changelog: v1.27.15...v1.27.16