Changelog

Changelog

Per-version release notes.

The canonical changelog lives in CHANGELOG.md in the source repository. This page mirrors the highlights for the last few releases.

v0.4.0 - 2026-07-04

First stable release of the v0.4.0 line. It brings macOS Secure Enclave keyslots up to parity with the TPM and FIDO2 backends, extends the mount-time symlink and inode guards to every frontend, and closes out the late-June audit passes. On-disk formats are unchanged from the v0.3 line: existing vaults, sidecars, and headers open without migration.

macOS Secure Enclave (SEP) keyslots

Bind a keyslot to a Mac's Secure Enclave on Apple Silicon and T2 machines, via CryptoKit ECDH in derive mode. 13 keyslot kinds cover plain, Touch ID, fusions with FIDO2 and a passphrase, and ML-KEM-768 and ML-KEM-1024 hybrids. The per-slot SEP material lives in an HMAC-covered in-header region, so SEP vaults need no external sidecar. As with TPM slots, keep a backup passphrase slot: a wiped or replaced enclave makes a SEP slot unopenable.

Mountpoint hardening across every frontend

The symlink probe (O_DIRECTORY | O_NOFOLLOW) and the inode re-probe taken immediately before the mount syscall used to live only in the CLI mount subcommand. They moved into a shared mount module below every frontend, so TUI-wizard mounts and the GUI's in-process mounts now inherit the same protection, including inside the forked child on the daemonized FUSE path.

Post-quantum sidecars are vault-bound, and panic destroys the recovery mirrors

Hybrid post-quantum sidecars now carry a 32-byte binding to their vault on every non-deniable create, enroll, and unlock path, so a swapped-in foreign .kyber sidecar is refused with a clear message instead of a confusing decryption failure. Deniable vaults keep the unbound format on purpose, since a binding would link the sidecar to the vault for anyone holding both files. Separately, every panic action now also scrubs the crash-recovery mirrors (header-bak, meta-bak), so a destroyed vault can no longer be rolled back from its own backups.

Other hardening

v0.3.1 - 2026-06-17

Hardening and usability fixes, with a focus on running unprivileged on QubesOS AppVMs and closing several file-handling race conditions. Existing vaults open unchanged; there is no on-disk format change.

QubesOS AppVMs now work unprivileged

LUKSbox ran fine on stock Debian but failed on a QubesOS AppVM unless you used sudo: the GUI aborted with NoGlutinConfigs and adding a keyslot died trying to allocate the 256 MiB Argon2id buffer. Root cause was the process-wide memory lock. mlockall armed MCL_FUTURE (lock every future allocation), and on a host with a finite RLIMIT_MEMLOCK ceiling (a QubesOS AppVM caps it at 128 MiB) any allocation past that ceiling then failed with ENOMEM instead of being allowed to swap. That bricked both the Argon2id buffer and Mesa's software-renderer framebuffers.

The result: both the GUI and keyslot creation work on a stock QubesOS AppVM with no sudo and no template changes. Key material itself stays unswappable via memfd_secret (Linux 5.14+) or a per-allocation mlock on the heap fallback, independent of the MCL_FUTURE change. See the Linux install guide for the dpkg -i vs apt install ./ dependency note on minimal templates.

Stronger memory locking on constrained hosts, old kernels, and macOS

Two narrow race conditions in the file-handling layer were closed:

Security: Windows extraction no longer redirectable through an intermediate junction

The Windows branch of the secure extract helper (behind luksbox get, the wizard/GUI extract, and recovery extraction) did create + truncate on the caller-supplied path and only checked for a reparse point on the final component, after truncating. An attacker-controlled intermediate junction (for example C:\tmp\extract pointing at C:\Windows\System32) could silently redirect the plaintext write, an arbitrary-overwrite primitive under an elevated run; even a final-component reparse point had its target truncated before the check could refuse it. This closes the Windows-side gap left after the Unix fix in v0.2.x.

The Windows path now refuses to extract through any reparse point (junction, symlink, or mount point) anywhere in the path. It takes the lexically-absolute target, opens it without truncating, refuses a reparse-point or directory final component, then requires the handle's GetFinalPathNameByHandleW result to equal the literal target; any junction crossed on the way makes them differ, so the write is refused with no destructive truncation. Regression tests: crates/luksbox-core/tests/windows_extract_junction.rs.

Wizard and TUI keyslot flow

GUI no longer carries a mounted vault into a new open/create

Switching to another vault in the GUI while one was mounted left the previous vault's "Vault mounted" panel (with its "Unmount" button) showing over the new vault, and an unclean unmount could leave that state stuck until the application was restarted. The confirmation that gates a vault switch was checking only for an open (unmounted) vault; a mounted vault has its filesystem handed to the mount thread, so that check missed it. The GUI now recognizes a live mount when you switch, shows a confirmation popup ("Unmount current vault first?"), and on confirm unmounts cleanly before continuing, so the new vault always opens with a fresh view.

v0.3.0 - 2026-06-15

Cross-platform FIDO2 keyslots and an in-place migration path.

FIDO2 keyslots are no longer platform-locked

A FIDO2 keyslot enrolled on Linux or macOS could not unlock the same vault on Windows, and vice versa. The two backends drive the authenticator's CTAP2 hmac-secret extension through different stacks: libfido2 (Linux/macOS) forwards the 32-byte salt to the device unchanged, while Windows' webauthn.dll is not a passthrough, it applies the W3C WebAuthn-PRF derivation T(x) = SHA-256("WebAuthn PRF"\0 || x) to the salt before the device sees it. Same key, same on-disk salt, two different HMAC outputs, two different key-encryption keys, unlock failure on the other platform.

v0.3.0 fixes this at the slot-format level. Both backends now converge on the device computing HMAC-SHA256(secret, T(salt)): libfido2 applies T locally before talking to the device, and webauthn.dll is left to apply the identical T itself. New FIDO2 slots are cross-platform by construction.

User-verification tip: webauthn.dll always performs user verification, while libfido2 only does so when a PIN is supplied, and the two states select different per-credential secrets. Enroll FIDO2 slots with a PIN so every platform derives from the same secret.

Migrate existing FIDO2 slots in place

Because T is one-way, a FIDO2 slot whose on-disk convention predates this fix cannot be reproduced through webauthn.dll and will not open on Windows. Bring such slots forward with:

luksbox migrate-fido2-slot <vault> --slot N

The migration is in-place (no vault rewrite), idempotent, and crash-safe. It currently covers wrap-style Fido2HmacSecret slots; direct MVK-derived and fused TPM/hybrid-PQ FIDO2 variants are out of scope for v0.3.0 and the command rejects them with guidance. See the migrate-fido2-slot CLI page. luksbox info now reports each FIDO2 slot's platform-compat status.

Fixed: luksbox mount default mode refused to start

The default daemonizing form luksbox mount <vault> <mountpoint> failed on Linux with refusing to daemonize from a multithreaded process once the deferred-flush timer landed in v0.2.x: the mount spawned the background flush-timer thread before the fork, and the pre-fork single-threaded check then refused. --foreground was unaffected. The flush timer is now created in the child after the fork, so the default background mount works again.

Also in this release

v0.2.2 - 2026-06-02

Critical durability fix on top of v0.2.1 plus a large performance improvement on big-vault mounts. Closes a real-user data-loss class where v0.2.1's mirror protocol could commit "intended new state" pointers to disk before the chunk-list blocks those pointers referenced were durable, so a crash in a narrow post-mirror-fsync, pre-live-fsync window left a vault whose recovery target pointed at slots that still held pre-flush bytes and failed AEAD on reopen.

Existing v0.2.1 vaults open unchanged; the fix is entirely on the write path. No on-disk format change -- same LBM5 + LUKSBOX2 + sidecar mirror envelope. Vaults that already lost chunk-list blocks to this bug are NOT recovered by upgrading; this release only prevents future occurrences and ships a tolerant recovery mode so the healthy files in a damaged vault can still be copied out.

Performance: 3-minute rm is now milliseconds

A real-user report on a 10k-file vault: a single rm of one tiny file took ~3 minutes; nemo and bash were both unresponsive while the vault was mounted. Root cause: every FUSE metadata op (create, mkdir, unlink, rmdir, rename, setattr, symlink, link, FUSE flush / release) was driving a fully synchronous Vfs::flush that rewrote the entire 64 MiB metadata mirror and live region AND re-spilled every spilled inode's chunk-list chain. Two layers of fix:

Durability model matches ext4 / btrfs / xfs commit intervals: a crash without an explicit fsync can lose up to 30 seconds of metadata changes. The on-disk encrypted bytes that ARE flushed retain the v0.2.2 mirror-protocol durability fence (chunk-list blocks are durable before the mirror commits), so the post-flush state is always crash-consistent. Keyslot revocation is unaffected (revoke() calls persist_header independently of vfs.flush).

luksbox mount --sync (also accepted on deniable-mount) restores the pre-v0.2.2 eager-flush semantics for users whose threat model requires every operation to be durable on return. The wizard TUI surfaces this as an "Eager flush?" prompt before mount; the GUI Browser view exposes it as an "Eager flush (--sync)" checkbox next to the Mount as volume button. Default off in all three.

Platform coverage: the deferred-flush optimisation now runs on every mount backend in v0.2.2 -- Linux libfuse3, macOS macFUSE, macOS FUSE-T, and Windows WinFsp. Each backend hosts its own luksbox-flush-timer thread with identical 30 s cadence, identical --sync opt-out, and identical final-flush guarantee on unmount. The shared interval constant lives in luksbox_mount::LAZY_FLUSH_INTERVAL_SECS. Earlier v0.2.2 drafts shipped Layer 1 for libfuse3 / macFUSE only; the FUSE-T and WinFsp adapters now match.

Fixed: v0.2.1 durability hole (CVE-class data-loss)

Vfs::flush's spill stage writes external chunk-list blocks through the page cache, then called Container::write_metadata, which fsyncs .lbx.meta-bak BEFORE overwriting the live metadata region. A crash between those two fsyncs (power loss, OOM kill, kernel panic, host hypervisor death) could leave .lbx.meta-bak durably committed to the NEW state with fresh chunks_external head/generation tuples, while the .lbx chunk-list-block slots those tuples reference still held bytes from a previous flush. On reopen, mirror recovery picked the NEW pointers as authoritative; the reader's AEAD then verified every targeted chunk-list block under the NEW generation in AAD, but the bytes on disk were written under the OLD generation. Result: crypto: AEAD failure on every chunk-list block that hadn't been fsync'd before the crash, cascading to metadata blob deserialization failed from the VFS layer at open time.

Real-world incident: a v0.2.1 user lost 26 contiguous-in- generation chunk-list blocks in a single flush after a crash, while data chunks and the metadata blob itself were intact.

Fix: new Container::sync_data_area() method (one self.file.sync_all()), called from Vfs::flush between the spill stage and write_metadata. The mirror's "reader can trust these pointers" invariant is now backed by durable bytes. Deniable vaults are unaffected (they explicitly opt out of the sidecar-mirror protocol). v0.2.0 and earlier vaults are also unaffected (no mirror protocol there at all).

Regression-tested three ways:

Fixed: FIDO2-direct vaults rejected by --fido2

luksbox check --fido2 (and the GUI's "Open" path) refused fido2-direct vaults with no FIDO2 keyslots in this container. The flag-driven unlock path iterated only SlotKind::Fido2HmacSecret slots and skipped SlotKind::Fido2DerivedMvk, even though the format layer's UnlockMaterial::Fido2 arm has handled both kinds since v0.2.0. Fixed to accept both. Subcommands that don't get --fido2 / --tpm2 / --tpm2-fido2 / --pq-hybrid on a vault with zero passphrase keyslots now also error with a clear vault has no passphrase keyslot; rerun with --fido2 instead of silently prompting for a passphrase that no slot will ever accept.

Fixed: GUI YubiKey "Touch your YubiKey" overlay was un-cancellable

Real-user report: during a YubiKey unlock the overlay caught the button press intermittently, sometimes after 5-6 attempts, with no way to abort and retry. v0.2.2 adds a Cancel button to the overlay and binds Esc to the same handler. The overlay redraws every frame; either input cleanly aborts the pending FIDO2 op so the user can retry without restarting the GUI.

Added: tolerant recovery for already-broken v0.2.1 vaults

For vaults that already lost chunk-list blocks to the v0.2.1 durability hole (or any other corruption class that makes chunk-list-block AEAD fail), two opt-in recovery primitives:

Used in production by the incident-affected user to recover 1885 healthy files from a 10k-file vault and identify all 26 broken paths as reproducible pentest scan output.

GUI improvements

Notes

v0.2.1 - 2026-05-22

Durability + correctness release on top of v0.2.0. Headline fix: crash-safe header and metadata writes via sidecar mirrors so a mid-write force-quit no longer destroys the vault (the user-reported v0.2.0 data-loss class). Also restores the executable bit when git clone-ing into a mounted vault, raises the metadata cap, surfaces capacity warnings in CLI and GUI, and fixes the macFUSE Apple build.

Existing v0.2.0 vaults auto-upgrade to the new envelope on the first flush. The upgrade is one-way; pre-v0.2.1 LUKSbox binaries can no longer open the vault after that point. Set LUKSBOX_FORMAT_V2=1 in the environment at create time if you need the old envelope for sharing with a pre-v0.2.1 install.

Headline: crash-safe header + metadata writes (LBM5 + LUKSBOX2)

Fixes the real-user data-loss case from v0.2.0 where a vault that hit ENOSPC mid-copy at ~13 GB with ~5000 small files became permanently unopenable after force-quit (blob deserialization failed on unlock; or, in a separate failure mode where the header was the corrupt region, no keyslot accepted the provided unlock material).

Two root causes:

The fix adds A/B durability for both critical regions plus a higher metadata cap, all cross-platform (Linux, macOS, Windows):

Security: intended-state mirror protocol (closes revoke-bypass)

An initial draft of the durability fix kept "previous-good" copies in the mirrors. An independent review and an end-to-end exploit script demonstrated that an attacker who corrupts the live header could force the recovery path, and the mirror's stale keyslot table would accept a previously-revoked credential. Confirmed auth-bypass.

Fixed by switching to intended-state mirror semantics: the mirror is committed to the NEW bytes BEFORE overwriting live, so the mirror always reflects what the user currently wants as the authoritative state, never a historical snapshot. After a revoke, the mirror reflects the post-revoke keyslot table. Even forcing the recovery path cannot resurrect the revoked credential. Regression-tested by v2_corrupted_live_after_revoke_does_not_unlock_via_mirror and by the new v2_mirror_recovery libFuzzer target.

Other security hardening in the durability path:

Fixed: git clone preserves the executable bit

Files materialised by git clone into a mounted LUKSbox vault no longer lose the executable bit on scripts and binaries. The FUSE create(O_CREAT, mode) callback now honors the requested mode all the way through to the persisted inode field, instead of defaulting every newly-created file to 0o644 regardless of what the caller passed.

Ground-truth verified end-to-end: git clone https://github.com/PentHertz/RF-Swift.git into a mounted v0.2.1 vault preserves -rwxrwxr-x on the shell scripts; unmount + remount confirms the bits round-trip through LBM5 disk persistence.

Added: metadata-budget capacity notification

Vaults holding very large inode counts (hundreds of thousands of files) can approach the 64 MiB metadata region cap before they exhaust host disk space. v0.2.1 emits a soft notification BEFORE the hard ENOSPC:

Notifications fire at most once per level per session. Vfs::metadata_budget_status() is exposed as a public API for external tooling that wants to build dashboards or pre-flight checks.

Added: tested-boundary advisory (~30 GiB)

v0.2.1 has been ground-truth validated end-to-end (real FUSE mount, write, force-quit, reopen, verify) up to roughly 30 GiB of stored content with several thousand files. The format is engineered for larger vaults but usage beyond that boundary has not yet been explicitly tested.

LUKSbox surfaces a one-shot heads-up at two moments:

The advisory is informational, not a hard limit. The boundary will be raised in subsequent releases as validated usage data accumulates.

Fixed: macFUSE Apple build (cfg-gated renameat2 flags)

Linux-only renameat2(2) flag constants (RENAME_EXCHANGE, RENAME_WHITEOUT, RENAME_NOREPLACE) are exposed by the fuser crate's RenameFlags only under #[cfg(target_os = "linux")], so the macFUSE build broke when the shared fuse.rs file referenced them unconditionally. The rename handler now cfg-gates the entire flag check; on macOS / Windows the renameat2 flags don't exist in the FUSE protocol and the check is moot. Linux behavior unchanged.

Install note: Trixie deb + plain su root

dpkg -i luksbox_*.deb aborts on Debian 13 with dpkg: error: 2 expected programs not found in PATH or not executable (ldconfig, start-stop-daemon) when run from a shell where root's PATH lacks /sbin. This happens when you switch to root via su root rather than su -. Dpkg's pre-flight check fails BEFORE any maintainer script runs, so we cannot fix it inside the package. Workarounds:

The dist/install.sh driver uses sudo and is not affected.

UI: "v3 metadata format" label clarified

The GUI checkbox and TUI wizard prompt now refer to the v0.2.1 envelope (LBM5 + LUKSBOX2 + sidecar mirrors) explicitly, instead of the misleading "v3" label inherited from when v3 just meant LBM3 / external chunk lists. The boolean field name (use_v3_format) is kept for API stability across the GUI -> ops boundary.

v0.2.0 - 2026-05-20

Bigger feature drop on top of v0.1.1: FUSE-T on macOS, deniable- header v2 mode, private mountpoints, on-disk metadata format v3 (new default), deniable MVK rotation that actually re-encrypts chunks, plus the usual hardening pass. The metadata-format change is forward-incompatible -- new vaults default to v3 (LBM\x03 magic) and cannot be opened by pre-v0.2.0 LUKSbox installs. Pass --format v2 (or untick the box in the GUI) when creating a vault you need to share with an older install. v0.1.x vaults open unchanged. Deniable mode is a format that did not exist before v0.2.0 and gained v3 support in this release as well.

Added: on-disk metadata format v3

Fixed: filesystem-boundary hardening (security audit follow-up)

A round of fixes for an external audit report covering TOCTOU / symlink / path-substitution surfaces. All fixes preserve existing vault contents; the changes harden write paths and the deniable open path. Disclose new findings to [email protected].

Other

Bigger feature drop on top of v0.1.1: FUSE-T on macOS, deniable- header v2 mode, private mountpoints, plus the usual hardening pass. No breaking format changes for standard vaults, every v0.1.x vault opens unchanged. Deniable mode is a NEW format (v2) that did not exist before v0.2.0.

Added, macOS FUSE-T backend

Added, deniable-header mode (v2)

Added, mount UX

Added, packaging

Added, fuzz coverage

Improved, deniable open UX

Changed, security hardening

Security review

Security audit, Round 13 - findings shipped fixes same revision

Internal Round-13 sweep across local filesystem boundary races, header durability, sidecar DoS surfaces, and remaining secret-copy hygiene. 2 HIGH, 5 MEDIUM, 2 LOW, 1 INFO. No CRITICAL. ALL 9 findings fixed in the same revision. Full per-finding report + Fix-status table at docs/SECURITY_AUDIT_ROUND_13.md.

HIGH fixes

MEDIUM fixes

LOW fixes

INFO R13-INFO-1: cargo audit advisory RUSTSEC-2025-0026 (registry 1.3.0 via winfsp_wrs_sys) remains accepted as documented in audit.toml.

New regression coverage: 4 test files, 14 deterministic tests (0 ignored). Run them with cargo test --test round13_findings -p luksbox-format (and similarly per-crate), or simply cargo test --workspace --exclude luksbox-gui.

Security audit, Round 12 - findings shipped fixes same revision

Four-axis adversarial sweep (FUSE-T subprocess + deniable v2 + filesystem TOCTOU + memory safety). All CRITICAL + HIGH + 5 of 7 MEDIUM fixed in the same revision. Full per-finding report

Headline fix - R12-01 (CRITICAL): deniable envelope discovery loop is now constant-time. try_open_envelope_v2 runs identical work per slot (always-allocate fixed scratch, always-memcpy via subtle::Choice-driven byte selection), and SlotPayload::decode runs ONCE on the constant-time-chosen slot's plaintext - so variable-length heap allocations are slot-position-independent. Pinned by the new dudect bench crates/luksbox-format/benches/dudect_deniable_envelope.rs.

HIGH fixes

MEDIUM fixes (R12-07 sandbox-subpath canonicalize; R12-09 extract-parent deny-list; R12-10 .rotating tmp O_EXCL|O_NOFOLLOW; R12-12 helper MVK stdin Zeroizing<[u8;32]>; R12-13 deniable cand_bytes Zeroizing).

LOW fixes (R12-16 vault-name : rejection + byte-cap; R12-18 TPM SensitiveData heap Zeroizing).

Empty-passphrase warnings (Round 12 follow-up) CLI's read_passphrase_confirmed now mirrors the wizard's and the GUI's empty-pass confirm modal (default no, with LUKSBOX_ACCEPT_EMPTY=1 escape hatch).

All other fixes

Round 12 closed cleanly. All 19 findings shipped fixes.

New test infrastructure shipped this round

v0.1.1 - 2026-05-08

First post-release iteration on top of v0.1.0. No breaking format changes - every v0.1.0 vault opens unchanged under v0.1.1.

Fixed

Added - forensic / partial-recovery CLI toolkit

Walkthrough on the Forensics page.

Added - release pipeline

Changed - security hardening

Non-breaking tightenings of the safe envelope. No vault or workflow that worked under v0.1.0 is affected.

Documentation

Known limitations

v0.1.0 - 2026-05-06

Initial public release. The core feature set was audit-tracked through 9 internal review rounds before the cut.

Pre-1.0 status

LUKSbox is currently pre-1.0 and the on-disk format may evolve under audit guidance. Migration tools are provided for any breaking format change, but the conservative move is to let the v1.0 cut settle before relying on it for archival storage.

When the v1.0 line is cut, format compatibility becomes a hard guarantee: any v1.x release will read any vault produced by any other v1.x release.

Release verification

Every published release ships a SHA-256 manifest and a Sigstore attestation. See Download / Verify your download for the verification commands.