Skip to content

Releases

v2.4.7

Choose a tag to compare

@github-actions github-actions released this 11 Nov 00:59

HexDB 2.4.7 fixes two problems found after 2.4.6: uploads could lose their content despite replication.min_acks, and hexes started at the same moment could both lead.

Fixed

  • min_acks covers uploaded files' content. A replica acknowledged an upload once it had applied the file's document, but it copies the file's content afterwards, in the background. If the Overseer failed between the two, an upload acknowledged as replicated could lose its content. A replica now also tracks how far it holds the content of the files it applied, and reports it to the Overseer. A write to a files tessellation counts a replica only once that position has reached it, so with min_acks set, an acknowledged upload's content is on that many replicas.
  • Hexes started together settle on one Overseer. At startup, of two hexes claiming the Overseer role, the one whose discovery listener started first keeps it. The times were compared in milliseconds with ties broken by ID, so two hexes that started listening in the same millisecond could both keep the role until the next discovery round. Hexes now report the time to the nanosecond.

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last. Replicas running 2.4.6 or earlier don't report a content position, so until they're upgraded they count as acknowledging an upload the way they did before. Coming from 2.4.5 or earlier, read the 2.4.6 notes first: they include security fixes.

Full Changelog: v2.4.6...v2.4.7

v2.4.6

Choose a tag to compare

@github-actions github-actions released this 10 Nov 22:37

HexDB 2.4.6 is a security and reliability release. It fixes the 14 findings of an independent code review of 2.4.5, plus two problems found while checking them. Upgrade soon if you use row filters, hidden or masked fields, tokenization, min_acks or file quotas.

Security

  • Detokenizing follows the caller's access. POST /tessellations/{name}/detokenize checked the permission and the field, but not the caller's row filter or a hidden field around the vault field. A caller with detokenize could open a token copied from a row they can't read. Now a token opens only if the caller may read its field (that field and every field around it, unmasked) and, with a row filter, only if a document they can read holds it at that field. Any other token gives null.
  • Protections reach inside arrays. hide, mask, sealed fields and tokenize on a path such as items.cost didn't apply to the elements of an items array. They now apply to every element.
  • Combined roles never show what both hide. With two restricted roles, a field one role hid as part of a parent (card) and the other hid by itself (card.number) came through in the clear. A field is now visible only if some role shows it and every field around it.
  • Large integers compare exactly. Numbers were compared as doubles, so integers beyond 2^53 that differ (9007199254740993 and 9007199254740992) compared equal. That affected filters, sorting, indexes and row filters, which could let a row filter match another tenant's ID. Numbers now compare exactly everywhere. Field indexes are rebuilt once at startup (new snapshot format).
  • Idempotent replays use today's access. A retry with the same Idempotency-Key returned the stored answer even if the caller had since lost access to the document, or fields had become hidden or masked. A replay is now refused with 403 when the caller can't read the document any more, and hidden and masked fields follow their current roles.

Fixed

  • Writes to vault-tokenized fields work under row filters. The vault entry a write adds was counted as one of the caller's documents: a one-document bulk insert returned two IDs, and an insert by a role with a row filter (a tenant filter, say) was refused because the vault entry has no tenant. The caller's documents no longer include vault entries, which still commit atomically with them.
  • Retries wait for replicas. With replication.min_acks, a retry of a write that had timed out waiting for replicas answered success at once. It now waits for the replicas like the original write.
  • The change feed can't get stuck. A write whose request was cancelled while its commit was queued could leave the change feed waiting forever, which stopped replication. A commit that's accepted now reaches the feed whether or not its request is still there.
  • Failover picks the replica with the newest writes. In a lattice without slicing, an election ranked replicas by preference and hardware only, so a replica missing recent writes could take over and lose them. The data now decides first: only the hexes furthest along the history most of them follow can take over, then preference, slices and hardware choose among them.
  • Replication never splits a transaction. A page of changes could end in the middle of a transaction, so a replica (or a hex taking over at that moment) could expose half of it. Pages now end only between commits.
  • A busy peer list doesn't count as a quorum. With replication.quorum, a check that found the peer list locked let the write through. It now uses the last count instead.
  • Documents with very many index keys stay findable. A document with more than 1,000 keys in one index (a long array, or arrays multiplied in a composite index) was indexed by its first 1,000 only, so lookups could miss it. Such a document is now checked on every lookup instead, and a unique index refuses it.
  • Restored documents keep their vault values. Vault cleanup deleted the values of tokens held only by trashed documents, so a restored document came back with tokens that open nothing. Vault entries now stay until the last document that could come back with them is purged or expires from the trash. A restore whose vault values are gone anyway is refused with 409.
  • Key rotation keeps tokens. The tokenization key was derived from the current lattice key, so rotating storage.encryption_key or network.lattice_secret changed every new token and broke lookups by value on existing ones. The key is now fixed when the data is created, kept in the catalog and in backups, and shared with every hex that joins.
  • Concurrent uploads respect quotas. Uploads committing at the same time each saw the same room left, so together they could go far past a user's or tessellation's quota. Each upload now reserves its size from the quota check until its commit.
  • Hexes started together settle on one Overseer: a hex that started later could keep its own claim while an earlier one never learned of it. And two first deletes at the same moment no longer fail by creating the trash's internal indexes twice.

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last. Each hex rebuilds its field indexes once on its first start (the index snapshot format changed). An existing lattice keeps the tokenization key it uses today, so tokens written since the last key rotation keep matching. Coming from 2.4.3 or earlier, read the 2.4.4 notes first: its storage format can't be read by earlier versions.

Full Changelog: v2.4.5...v2.4.6

v2.4.5

Choose a tag to compare

@github-actions github-actions released this 10 Nov 02:27

HexDB 2.4.5 is a small follow-up to 2.4.4: tessellation names that clash with API routes are refused, and a hex that can't undo deletes says so.

Fixed

  • Route names can't be new tessellation names. A tessellation named updates, a2a or ready could be created, but the API route of the same name hid it over REST. Those names are now refused for new tessellations, like queues, shares, mcp and quarantine since 2.0. A tessellation an earlier version created under one of them stays usable through SQL, GraphQL and transactions. A test now checks every route against the reserved names, so a new route can't be missed.

Added

  • A warning when deletes can't be undone. With storage.trash_days = 0 (or files.trash_days = 0), deleted documents (or files) aren't kept, so their deletes can't be restored. The hex now says so in its startup log, in /status (storage.trash_warning) and on the admin UI's Deletes page.

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last. Coming from 2.4.3 or earlier, read the 2.4.4 notes first: its storage format can't be read by earlier versions.

Full Changelog: v2.4.4...v2.4.5

v2.4.4

Choose a tag to compare

@github-actions github-actions released this 10 Nov 01:45

HexDB 2.4.4 fails over in about 15 to 20 seconds instead of half a minute, adds an IP blocklist and per-tessellation file quotas, signs the answers a replica passes on, and makes small documents several times smaller on disk and aggregations and reads faster.

Added

  • IP blocklist. Administrators refuse an IPv4 or IPv6 address, or a CIDR range, on every hex: POST /blocklist {"address": "203.0.113.0/24", "note": "...", "expires_in_hours": 24}, GET /blocklist, DELETE /blocklist/{id}, or the admin UI's Blocklist page. A blocked client gets 403 blocked before authentication (trusted proxies apply when finding its address). Entries replicate, can expire, and are audited (security.block, security.unblock). /health, /ready and hex-to-hex routes are never blocked, and an entry covering your own address is refused. blocklist is now a reserved tessellation name.
  • Per-tessellation quotas. A files tessellation can have limits of its own, whoever uploads (administrators too): PUT /tessellations/{name}/quota {"mb": 500, "files": 10000}, null to remove them. Everything it holds counts, earlier versions and trashed files included; past a limit uploads get 507 quota_exceeded. GET /tessellations/{name} shows quota and quota_used. Counting a user's quota now uses an index on uploaded_by, created the first time, instead of a scan.

Changed

  • Failover in about 15 to 20 seconds. Discovery runs every 5 seconds by default (was 10), and a lost Overseer is replaced after three missed rounds. A peer isn't counted lost while another hex that answered this round still sees it, so a replica that's starved of CPU or cut off on its own can't elect itself and split the lattice. Lattices that set network.discovery_interval_seconds keep their value.
  • Smaller files on disk (SSTable format 6). Small JSON documents compressed one at a time barely shrink. A file of at least 64 documents averaging under 8 KB now gets a zstd dictionary trained from its own documents, stored encrypted in the file, and used only when it saves more than twice its size. Order-like documents of about 250 bytes went from 177 bytes each to 40. Older files are still read, and compaction rewrites them in the new format.
  • Faster reads. A stored document is decoded straight into typed fields without building an intermediate JSON tree: about half the work (a 5 MB document: 199 to 95 ms). Every read benefits, including queries and aggregations that read from disk.
  • Faster aggregations. Documents are read in batches and parsed on every core, the document cache shares documents instead of copying them, and only the fields an aggregation uses are converted. A GROUP BY over 100,000 documents: 320 to 210 ms warm, 690 to 580 ms cold.
  • Signed forwarded answers. The Overseer signs its JSON answers to requests a replica forwards (writes, and a sliced replica's reads). The replica checks the signature before passing an answer on and answers 502 if it doesn't verify, so an answer changed in transit between hexes is refused. Downloads and live streams still pass through as they come and rely on TLS between hexes. During a rolling upgrade, a replica insists on a signature only from an Overseer that announces it signs.
  • Hidden deletes in the change feed. A reader whose role has a row filter now sees a delete event only if the deleted document was within the filter, so delete events no longer reveal that hidden documents existed. Readers without a row filter see every delete, as before.
  • Sealed fields in memory. When a document a request read is freed, the decrypted values of its sealed (masked) fields are overwritten. This only runs while some role masks a field.

Fixed

  • Hexes started together start with one Overseer. Hexes started at the same moment (a new lattice, or one restarted whole) could each start as Overseer until the next discovery round. A hex with network.peers set that finds no one now looks once more after its storage is open and it answers probes itself, so at least one of them finds the other. A hex started alone with peers configured takes at most 3 more seconds to start.
  • Keys used only on replicas during a lattice-secret rotation. An API key verified with a previous lattice key is re-hashed with the current one, but only the Overseer could store the new hash, so a key used only for reads on replicas stopped working once the old secret was removed. A replica now asks the Overseer to re-hash it.
  • Same-origin checks behind a router. When a router serves a hex under a public name (as hosted lattices are), the cookie CSRF check, CORS and live streams compared the browser's origin with the hex's internal host. The X-Forwarded-Host a trusted proxy sends now counts; from anyone else the header is dropped.

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last.

SSTable format 6 can't be read by earlier versions. Once a 2.4.4 hex flushes or compacts, its files are in the new format, so going back to 2.4.3 or earlier means restoring a backup taken before the upgrade. Take one first if you might need to downgrade.

Full Changelog: v2.4.3...v2.4.4

v2.4.3

Choose a tag to compare

@github-actions github-actions released this 09 Nov 18:03

HexDB 2.4.3 makes parent links work like foreign keys when you ask them to, closes a race that could leave a child behind when its parent was deleted, and brings documents, transactions and schemas to the CLI with idempotent, retry-safe writes.

Added

  • Enforced parent links. A parent link can say "enforce": true: "customer_id": {"type": "string", "parent": {"tessellation": "customers", "on_delete": "cascade", "enforce": true}}. A write that sets or changes the link is refused with 422 parent_missing if the parent doesn't exist or the same request deletes it. The check is part of the commit (the parent's version is checked under the commit lock), so a job that checks a customer and then writes a message while the customer is being deleted gets the error instead of leaving a message behind. A null link isn't checked, and documents written before the link was enforced aren't checked until their link changes.
  • hexdb doc, hexdb tx and hexdb schema. hexdb doc get|insert|replace|patch|upsert|update: insert takes a document or an array (one atomic write), replace and patch take --if-version for optimistic locking (doc get shows the version), and update by filter needs --filter or --all and, like hexdb rm, says how many documents it would change and asks (--yes in scripts). hexdb tx runs transactions; hexdb schema show|check|set manages schemas and their parent links.
  • Every CLI write is idempotent. Each carries an Idempotency-Key (--idempotency-key, or a new one per command) and is sent again with it when the connection drops, times out or gets 503, so it's never applied twice. Running a command again with the same key prints the first result and writes nothing.
  • MANUAL: "Writing safely", a one-table summary of how inserts, updates, upserts, deletes and transactions stay atomic and safe to retry; "Enforced links"; and a command-line reference for documents.

Fixed

  • A delete never leaves behind a child written while it ran. A child committed between a parent delete's cascade lookup and its commit could be left without its parent. Deletes now look their parents' children up again under the commit lock and, if one appeared, plan again so the cascade takes it too. This holds for every parent link, enforced or not.
  • The CLI on Windows overflowed its 1 MB main-thread stack with the larger command set; commands now run on a thread with 8 MB.

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last. Nothing changes for existing links until you add "enforce": true to them in a new schema version.

Full Changelog: v2.4.2...v2.4.3

v2.4.2

Choose a tag to compare

@github-actions github-actions released this 07 Nov 19:43

HexDB 2.4.2 makes deletes safe: every delete can be undone, a delete or update without a filter no longer runs by accident, and documents can belong to other documents so deleting the owner takes (or refuses to leave) what belongs to it.

Breaking change

  • POST /{tessellation}/_update with no filter, or {}, is refused with 400 filter_required. Add "all": true to the body if you mean every document. The same goes for GraphQL updateDocuments (all: true) and the drivers' updateWhere (an all option). Requests with a filter are unchanged.

Added

  • A trash and a delete history for every delete. A deleted document goes to the trash for storage.trash_days (30), and each request's delete (the documents it named or matched and everything that went with them) is one delete batch. GET /deletes lists the history, POST /deletes/{batch}/restore puts a whole batch back in one commit, and DELETE /deletes/{batch} purges it for good. GET /{tessellation}/_trash and its restore and purge endpoints work for document tessellations as they do for files. Masked fields stay sealed and tokens stay tokens in the trash, and both the trash and the history replicate, so a hex that takes over can restore them. DELETE /{tessellation}/{id} names its batch in X-HexDB-Delete-Batch.
  • Delete by filter, with guardrails. POST /{tessellation}/_delete deletes the documents matching a filter in one atomic batch. It needs a filter or "all": true. dry_run shows what would go, and expect refuses (409 count_changed) unless exactly that many documents would go, checked again inside the commit. GraphQL deleteDocuments, a delete_where transaction operation, and deleteWhere in the Node, Python and .NET drivers.
  • Parent links and cascading deletes. A schema field can name the document that owns this one: "order_id": {"type": "string", "parent": {"tessellation": "orders", "on_delete": "cascade"}}. When the parent is deleted, cascade deletes the child too, restrict refuses the delete (409 has_children), and detach sets the link to null. Files can belong to documents the same way. A link marks ownership only: no joins, and writes don't check that the parent exists. Link fields are indexed, a cascade that reaches a document the caller can't change is refused as a whole, and a delete removes at most 100,000 documents and files (413 too_many).
  • Folder deletes. DELETE /{tessellation}/_folders/{folder}?recursive=true deletes a folder of files and everything below it as one restorable batch.
  • CLI. hexdb rm deletes by ID, filter or folder: it always shows what would go and asks (you type the tessellation's name for everything or more than 100 documents), needs --yes without a terminal, and prints how to undo it. hexdb deletes list|show|restore|purge and hexdb trash.
  • Admin UI. Delete matching on the Documents page, with a preview and Undo; a Deletes page with the history, Restore and Purge; Delete folder on the Files page; parent links in the schema editor.
  • Audit. Deletes of more than one document (by filter, folders, cascades, transactions), restores and purges are in the audit trail (data.delete.*, data.restore, data.purge).
  • deletes is now a reserved tessellation name.

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last. Before upgrading, check that nothing sends _update (or updateDocuments, or updateWhere) without a filter; give those calls all: true. Deleted documents take disk space while they're in the trash; set storage.trash_days lower (or 0 to keep none) if that matters.

Full Changelog: v2.4.1...v2.4.2

HexDB for VS Code 0.2.2

Choose a tag to compare

@github-actions github-actions released this 06 Nov 21:15

HexDB for VS Code 0.2.2. See extensions/vscode/CHANGELOG.md.

HexDB for VS Code 0.2.1

Choose a tag to compare

@github-actions github-actions released this 06 Nov 19:11

HexDB for VS Code 0.2.1. See extensions/vscode/CHANGELOG.md.

v2.4.1

Choose a tag to compare

@github-actions github-actions released this 05 Nov 22:56

HexDB 2.4.1 fixes an election rule that could lose writes when a hex set to prefer the Overseer role came back to a lattice keeping full copies.

Fixed

  • A sitting Overseer keeps the role. The election narrowed its candidates to hexes with identity.role = "overseer" before it honoured a sitting Overseer. So when such a hex came back after handing its role over (with full copies, slicing = false), it took the role at once, and the Overseer that had led meanwhile then copied the returning hex's data, dropping any writes it had accepted while that hex was away. Now a sitting Overseer keeps the role whatever the others prefer.
  • A preferred hex gets the role back by handover. The hex that prefers to lead follows like any replica until it has every change; then the sitting Overseer hands the role to it, the same way an Overseer hands over before stopping (writes are held for a moment; one caught mid-commit gets 503 with a retry hint), and carries on as a replica. If the handover can't finish, it keeps the role and tries again five minutes later. Each handover is in the audit trail (lattice.handover).

Upgrading

Restart the hexes one at a time, as usual: replicas first, the Overseer last. Sliced lattices (the default) weren't affected: their Overseer keeps its role across a restart instead of handing it over.

Full Changelog: v2.4.0...v2.4.1

v2.4.0

Choose a tag to compare

@github-actions github-actions released this 05 Nov 20:19

HexDB 2.4.0 scans every uploaded file for viruses and malware before it's stored, and fixes the last causes of a rare full resync after an Overseer handover.

Added

  • Malware scanning, on by default. Every upload is scanned after its content reaches disk and before its document commits, so a file that doesn't pass never becomes a document and never reaches another hex. Two engines, both free and open source, with no accounts:
    • ClamAV, through its daemon (clamd) over TCP or a Unix socket. HexDB never links ClamAV, so its GPL licence stays separate. files.scan.clamd = "auto" (the default) looks for a clamd on the same machine at startup; give an address (tcp://clamav:3310) to use another, or off.
    • YARA-X, built in, with HexDB's own rules (starting with the EICAR test file). files.scan.yara_rules adds a directory of your own rules or a community set such as YARA Forge (see the MANUAL for the download). A rule file that doesn't compile is left out and named in a warning; it doesn't stop HexDB.
  • Quarantine. A file that matches is kept on the Overseer only, in the local-only _quarantine tessellation: it isn't in its tessellation, can't be downloaded, and never replicates. The upload gets 422 file_quarantined naming what matched. Administrators review it on the new Quarantine page (under Security), or with GET /quarantine, POST /quarantine/{id}/release (store a false positive as a normal file) and DELETE /quarantine/{id}. Every action is audited.
  • Rescans. When ClamAV's signatures or the YARA rules change, the Overseer scans every stored file's current version again in the background, at most once every files.scan.rescan_hours (24), and quarantines what new signatures find. POST /quarantine/rescan (or "Rescan now") starts one at once. Progress is in /status (storage.scan.rescan).
  • Fail closed. While a configured ClamAV can't answer, uploads are refused with 503 scanner_unavailable and Retry-After; nothing unscanned is stored.
  • Turning it off and on. The lattice setting files.scanning (Settings > Files) turns scanning off or on for the whole lattice without a restart. files.scan.enabled = false turns it off on one hex. files.uploads = false refuses new uploads on a hex while stored files stay readable.
  • Each file's verdict is in its document (scan: status, detections, engines, scanned_at), in the file details in the admin UI, and the dashboard has a Malware scanning card.
  • ClamAV in Docker Compose and the Helm chart, connected to HexDB by default (clamav.enabled in the chart). The shipped deploy/clamav/clamd.conf raises ClamAV's limits to match HexDB's (2000 MB) and reports files it can't fully check instead of passing them.

Upgrading

  • Scanning starts as soon as a hex runs 2.4.0. Without a ClamAV, uploads are scanned by the built-in YARA rules alone, and HexDB warns about it in its log, /status and the dashboard. See "Setting up ClamAV" in the MANUAL.
  • ClamAV needs 3 to 4 GB of memory (about 1.2 GB for its signatures, twice that briefly while it loads new ones). ConcurrentDatabaseReload no keeps it near 1.3 GB, at the cost of a pause of about a minute while it reloads.
  • The first rescan after upgrading scans every stored file once, one at a time in the background.
  • YARA-X needs a 64-bit platform. Every release build is 64-bit; a 32-bit build from source turns it off with --no-default-features --features video-thumbnails and relies on ClamAV.

Fixed

  • No full resync after a handover. Two rare races could leave a replica one change ahead of a new Overseer, which then made it copy everything again: a write that passed the writable check just before an Overseer began stepping down could still be published after the step-down compared replicas, and a long poll for changes accepted before a handover could answer with the old Overseer's first write as a replica. Neither happens now.

Dependencies

  • Updated zip, tonic, lopdf and the admin UI's packages. Two advisories in YARA-X's dependencies are explained in .cargo/audit.toml: rsa (Marvin, RUSTSEC-2023-0071; YARA-X only verifies signatures, which isn't affected) and bincode (unmaintained).

What's Changed

  • Bump @types/node from 26.6.5 to 26.6.6 in /drivers/node by @dependabot[bot] in #68
  • Bump lopdf from 0.45.0 to 0.46.0 by @dependabot[bot] in #73
  • Bump the admin-ui group across 1 directory with 5 updates by @dependabot[bot] in #69
  • Bump the rust group across 1 directory with 4 updates by @dependabot[bot] in #72
  • Bump cm6-graphql from 0.2.2 to 1.0.0 in /hexdb_admin by @dependabot[bot] in #70

Full Changelog: v2.3.3...v2.4.0