Threat model
What LUKSbox defends against, and what it explicitly doesn't.
The threat model below is paired with the security architecture page (trust boundaries, on-disk graph, unlock + crash-safety pipelines, with diagrams) and the tests page (regression suite, fuzz harnesses, audit rounds, contribution paths).
Defended
| Threat | Defence |
|---|---|
| Offline analysis of a stolen vault file | AEAD on every chunk; header HMAC; passphrase keyslot brute-forcing rate-limited by Argon2id (256 MiB / 540 ms / unlock by default) |
| Tampering of any header byte | HMAC-SHA256 over the entire 8 KiB header under an MVK-derived subkey |
| Tampering of any chunk byte | Per-chunk AEAD with AAD = file_id || chunk_idx || generation |
| Replay of older chunks within a file | The AAD's generation counter increments on every overwrite |
| Cross-file chunk substitution | The AAD's file_id differs per file, so a chunk from file A doesn't decrypt in file B |
| Whole-vault rollback | The .anchor sidecar carries an MVK-keyed HMAC over a monotonic generation counter; rolling back the vault to an older snapshot trips the anchor check |
| Wrong unlock material | All FIDO2 slots, passphrase slots, and hybrid slots fail with UnlockFailed (no oracle distinguishing "wrong passphrase" from "wrong device" from "tampered slot") |
| Post-quantum "harvest now, decrypt later" attacks | Hybrid-PQ keyslots add ML-KEM-768 / 1024 on top of the classical KEK derivation |
| Rogue / MITM FIDO2 device returning attacker-chosen values | FFI-boundary length cap + downstream Keyslot::new_* cap + AEAD wrap rejection of wrong KEK (verified in tests/rogue_authenticator.rs) |
| Vault-internal symlinks pointing outside | Impossible by construction: VFS has no Symlink kind, FUSE rejects symlink()/link(), WinFsp ignores reparse points, validate_metadata_tree enforces only File / Directory inodes |
| Symlink-target overwrite during plaintext extraction | secure_create_or_truncate opens with O_NOFOLLOW; pre-existing symlink destinations (e.g. attacker-staged /tmp/output -> /etc/passwd) fail with ELOOP instead of redirecting the write |
| Concurrent open from a second process | Exclusive flock on the vault handle; second opener gets VaultLocked before it can read a stale header |
| Path substitution between open and lock | Post-lock verify_path_inode rejects rename-during-open swaps with Error::PathSubstituted |
| Power loss during atomic sidecar replacement | Temp-file fsync, atomic rename, then parent-directory fsync on Unix (fsync on dir handle) and Windows (FILE_FLAG_BACKUP_SEMANTICS + FlushFileBuffers) |
| Malicious authenticated metadata (chunk-id wraparound, dangling inodes) | Vfs::open validates the entire decrypted metadata tree (root, parent/child consistency, chunk offset bounds, generation sanity, free-list overlap with live chunks) before any VFS operation can use it |
| TPM keyslot taken to a different machine | tpm2-sealed + tpm2-pin + tpm2-fido2 slots are bound to the local chip's endorsement seed; another machine cannot unseal even with the PIN |
Not defended
| Threat | Why not |
|---|---|
| Attacker-controlled host while the vault is open | Memory snapshots, kernel exploits, ptrace, debugging - all bypass any host-side protection |
| Coerced user (rubber-hose attack) | No "duress passphrase" / hidden-volume yet. v0.2 ships deniable mode (the vault file itself is indistinguishable from random output, so the user can deny it is a vault at all); two-passphrase hidden / nested volumes are planned for v1.0 |
| Compromise of the FIDO2 device's secure element | Vendor-side problem; out of LUKSbox's control |
| Quantum recovery of FIDO2 hmac-secret from sniffed USB-HID traffic | Defended only when paired with a hybrid-PQ keyslot - pure FIDO2 vaults are vulnerable to this in the CRQC era |
| Storage-layer corruption / hardware bit-flips | AEAD will reject corrupted chunks; that's data loss, not a confidentiality break |
| Plausible-deniability hidden volumes | Not yet. Planned for v1.0. The v0.2 deniable mode is a partial answer (the vault itself is unidentifiable) but does not provide the VeraCrypt-style "two passphrases unlock two distinct vaults inside one file" property |
| Operating-system-level attackers (e.g. EDR running as your user) | Out of scope for any user-space encryption tool |
| Filesystem snapshots taken while the vault is unlocked | APFS / btrfs / ZFS / LVM snapshots are content-encrypted on disk (FUSE never materializes plaintext), but a full-VM / hibernate / vmss snapshot captures the MVK from RAM. Mitigation is operational: disable hibernation on hosts holding live vaults, verify swap is encrypted (default on macOS, manual on Linux), prefer FileVault on macOS so swap inherits volume encryption. |
| Kernel-resident attacker (rootkit, kernel exploit) | LUKSbox is userspace; the kernel is in its trusted computing base. A malicious kernel module can read process memory, intercept FUSE traffic before decryption, and substitute on-disk bytes between read and AEAD-verify. Same statement applies to a hostile hypervisor on virtualized hosts. |
| Hardware tampering beyond the FIDO2 device | Cold-boot RAM attacks (de-energizing DRAM seconds after power-off), JTAG / debug-port access, DMA over Thunderbolt with IOMMU disabled, supply-chain-implanted SPI flash. None of these are software-mitigable. Defences are physical: full-memory encryption (Intel TME / AMD SME), IOMMU enforcement, lockscreens that zeroize keys on session lock (roadmap). |
| Evil-maid attack on the LUKSbox binary itself | An attacker with brief physical access to an unattended unlocked machine can replace luksbox / luksbox-gui with a trojan that exfiltrates the next passphrase typed into it. The defence is full-disk encryption with pre-boot authentication: FileVault on macOS, BitLocker on Windows, LUKS on Linux. LUKSbox cannot verify its own binary against a tamper-resistant root of trust at runtime. |
| Supply-chain attack on the LUKSbox build pipeline or dependencies | A malicious Cargo dep (RustSec-disclosed or zero-day), a compromised crates.io account, or a tampered release artifact would compromise the resulting binary. We pin via Cargo.lock, run cargo audit in CI, and ship Sigstore attestations on every release, but a determined supply-chain attacker is out of scope for source-level review. Users at high risk should build from source against a verified Cargo.lock on a system they fully control. |
Adversary capability matrix
| Adversary | Has the disk? | Has the device? | Has the passphrase? | Has the .kyber seed? | Outcome |
|---|---|---|---|---|---|
| Lost laptop | Yes | No | No | No | Vault unreadable. Argon2id rate-limit makes passphrase guessing infeasible if entropy >= 60 bits |
| Lost YubiKey | No | Yes | Maybe | No | Without the disk, nothing to attack |
| Stolen YubiKey + stolen laptop | Yes | Yes | No | No | If FIDO2 PIN not exhausted: opens. Defence is the device's PIN counter (typically 8 attempts then wipe) |
| Insider with disk + passphrase guess | Yes | No | Trying | No | Argon2id rate-limit + per-vault salt; brute-force depends on passphrase entropy |
| State-level CRQC + recorded USB traffic | Yes | No | No | No | Pure FIDO2: breaks. Hybrid-PQ-FIDO2: holds (also needs the .kyber seed) |