v5.2.0-beta.4
Pre-releaseThis is a 5.2 beta. It is not recommended for production use.
Security
Schema-unqualified SQL bypassed table permission checks. The SQL authorization layer derived the affected schema/table set from the AST's databaseid. When a statement omitted the schema qualifier that field was empty, so nothing was recorded in the affected-attribute map — and hasPermissions iterating an empty map authorizes by vacuous truth. Meanwhile the v2 engine's binder resolved the same bare name to a concrete database and executed against it. Two independent name resolutions, one of which silently authorized nothing. Authorization now runs against the table the engine actually resolves, and is checked per table reference rather than once per statement.
The same series closes the surrounding gaps in the collectors: GROUP BY / HAVING columns are now recorded in the affected-attribute map, a derived JOIN source (which carries no join.table) is reported rather than skipped, and nested or compound queries the collectors were never able to record — UNION, EXCEPT, INTERSECT, PIVOT, UNPIVOT — are refused outright instead of passing through unchecked.
Operation-scoped authorization contract preserved. Two changes restore the pre-5.2 behavior rather than silently changing it:
- Read (#1915):
allowReadis evaluated once per operation, and the gate now runs before collection query planning, scans, or subscription audit setup — so an unauthorized caller can no longer trigger expensive work before being rejected. Applications that genuinely want row-level narrowing get explicitrowFilter(record, context)andeventFilter(event, context)predicates instead, which carry through filtered HNSW traversal, OR/range filtering, source-revalidated reads, subscription snapshots, replay, live events, and reload snapshots. - Write (#1842): one operation/collection verdict in default instance mode. For built-in
loadAsInstance = falsetable handlers, each operation is gated by the permission hook that actually matches it — an array PUT callsallowUpdateonce with the original batch before it starts, rather than per element.
Data integrity — LMDB→RocksDB migration
Migrated records lost their version and record prototype (#2014, fixes #2012). Every record written by storage.migrateOnStart since #1307 was stored without its [8-byte version][flags word] metadata prefix. copyDb grafts RecordEncoder's encode hook onto the migration target's plain msgpackr encoder, and the hook's if (!this.useVersions) opt-out read useVersions off that foreign encoder — undefined — so every migrated record took the non-versioned plain-encode path.
Downstream, prefix-less records decode without the metadata wrapper, so PrimaryRocksDatabase.getEntry skipped the structPrototype repair and point reads returned prototype-less plain objects: relationship getters, toJSON and getUpdatedTime all unreachable. Record versions were silently dropped, which also affects cache admission, ifVersion/CAS, and replication version comparison.
The fix writes the prefix correctly, adds a read-side repair for databases already migrated, stages the migration and renames it into place only after verification, and exports verifyMigratedDatabase(databasePath) so an existing installation can be checked. Verification sweeps every generation and exempts genuinely version-less records by key rather than by sniffing bytes.
If you have run migrateOnStart on any 5.2 alpha or beta, run verifyMigratedDatabase before relying on versions; a no-op rewrite pass is required to restore versions on already-migrated records.
TLS & certificates
- MQTT's raw-socket listener now has its own TLS usage type, so it no longer shares certificate selection with the HTTP listeners (#1999, #2003).
- The MQTT secure-port UDS metadata published an empty certificate list, which made a fronting SNI proxy fall back to serving the node certificate on 8883 (#2010).
- A listener's TLS selector is no longer stranded when the system database has not finished loading, and the previous
hdb_certificatesubscription is properly ended on a table swap — previously a swap could leave a stale subscription feeding the selector. - The zero-certificates retry is keyed off the current pass rather than the persistent default context, so one empty pass can no longer poison later ones.
HTTP & networking
- WebSocket upgrades were silently dropped on per-worker UDS mirror listeners (#2015). With
tls.unixDomainSocketsenabled, the per-worker UDS mirror is a separatehttp.Serverthat never received the'upgrade'listeneronWebSocket()attaches to the port-keyed server, so Node destroyed every WebSocket handshake on it with a zero-byte close — no response, no log. The same fix stopsenableProxyProtocol()'s data interception from outliving the PROXY header decision: it was still forwarding post-upgrade frames to a freed HTTP parser that the parser pool can reissue to another connection, which produced verified cross-connection corruption. - TLS facts forwarded by a fronting proxy via PROXY v2 TLVs are now exposed as
request.connectionInfo(#1985). - The operations API fails soft on a domain socket bind failure and warns on path-length overflow instead of failing to start (#1907).
Storage & audit log
- The interrupted-drop retry is now bounded to one actionable error and scoped to a per-drop generation, keyed by physical store rather than database alias. A genuinely failed store drop is no longer reported as complete, reconcile sweeps every generation, and budget cleanup stays O(1) in the common case (#1957).
- Audit cleanup has a real completion signal and a sane backoff, and a cleanup pass no longer escapes as an unhandled rejection (#1963).
- LMDB audit entries now store the real prior version — the primary entry's own
localTimerather than its origin version (#1988).
Components & resources
- Concurrent component installs no longer corrupt dependencies. Lock reclamation is race-free, liveness checks are bounded, unconfirmed liveness can no longer renew the lock-wait deadline forever, and timed-out component preparation is handled explicitly (#1991).
- Bare collection POST restores the v4
super.postcreate behavior, normalized before authorization (#1956).
CLI & operations
- Token environment variables for CI/CD authentication, and
harper login --for-cito print CI credentials on stdout. Env tokens are gated on a remote target and userinfo is stripped from--for-cioutput (#1876). checkOverloaded()now logs once when it first starts rejecting writes, so a rejecting node is visible in the log instead of silently shedding load (#2007).
Also in this release
Migration to @harperfast/code-guidelines (#1992), a resource test teardown race fix (#1971), and TLS/SQL regression test coverage and deflaking across the changes above.