Files are encrypted before they leave your device. Devices sync directly with each other, or through anything that can store bytes: an S3 bucket, a local folder, a directory carried by iCloud or Dropbox. None of them see file names or contents, and none of them need locks or atomic updates.
Written in Rust. CLI, FUSE mount, web UI, and C / Python / Go bindings.
i/d7a1…/000412-9f3c…-18204 i/2b90…/000098-71e0…-6112 p/3f/3f8a…-6291456 p/c2/c2d4…-5833728 n/0007-e41b…-2097152 r/d7a1…-epoch3
docs/ contract-2026.pdf 2 versions notes.md 2 conflicting heads photos/ 2026-09/IMG_4471.heic fetched on read projects/cipherfs/ README.md trash: old-drafts/ (14 files)
Left: object names, made of a device id, a sequence number, a length and a ciphertext hash. Right: the file tree a device shows after decrypting, with version counts, conflicts and the trash. There is no server between the two.
Content, file names and sensitive metadata are encrypted before they leave the device. Reads verify integrity; a wrong password, tampering or corruption fails loudly. Keys and plaintext never reach logs or stores.
Offline edits sync when connectivity returns. Interrupted transfers retry, and a repeated transfer never re-applies or resurrects an old version. Concurrent edits pick a current version by one rule; every other version is kept.
Small files stay cheap. Large files are chunked, streamed and fetched by byte range. The user never picks a mode, and crossing a size threshold never changes semantics.
Encryption, the file model, storage transactions and sync rules live in the core. CLI, mount, web UI and language bindings only adapt access. They do not keep their own database or invent rules.
Every device keeps the complete shared index. Content syncs to every device by default; a phone or a laptop short on space can be a partial device that keeps only pinned folders and what it recently read.
Devices connect peer to peer. Stores hold only ciphertext objects. History, trash and conflicts are views of one shared version chain, visible on every device. Each device has its own identity and can be revoked.
Every file has a stable identity; its path is just an attribute. Each device appends to its own signed log, and other devices verify membership, hash chain and causal dependencies before merging. A delete is a tombstone version, a move is a version with a new path, and a conflict is two or more heads with no causal order between them.
Directories and mounts show only the current version; no conflict copies are written. A version leaves shared history only through an explicit purge or the repository retention policy (365 days by default). It first spends 30 days as pending, during which any device can cancel.
Clients that hold the repository keys and read plaintext. Each has a long-term key pair, a human-readable name, and a full or partial role. A new device pairs with a short code or QR code; with no existing device present, it joins with the recovery key.
Need only four operations: write an immutable object, read by name, list by prefix, delete. Objects are content-addressed and append-only, packed at 4 to 8 MiB. An object that has not fully arrived is "not yet here", never "corrupt".
Content keys derive from a key epoch. Revoking a device opens a new epoch whose key is wrapped only for the remaining devices. The password protects the local machine only; a forgotten password is reset with the recovery key and the device keeps its identity.
An always-on machine is the full device. The Mac is a partial device with a mount. It keeps only pinned folders and recently read content; everything else still appears in Finder and is fetched from the full device or a store when opened. Past the cache budget, content confirmed to exist elsewhere is released automatically. Unsynced local writes and open files are never released.
Verified on macOS 27 with FUSE-T: the mount carries .metadata_never_index, is excluded from Time Machine, browsing every folder in Finder left the cache unchanged, and reading an unavailable file while offline fails immediately instead of hanging.
Not as a stable release. The confirmed requirements are implemented and each has acceptance evidence, but the on-disk format and the API are still allowed to change, there has been no third-party security audit, and no compatibility promise has ever been made. It is suitable today for the author and a few people who accept that the format may change.
Verification results as of 12 October 2026. Counts are actual executions per feature combination.
| Check | Result | |
|---|---|---|
| Host Rust tests, default features | 672 passed / 0 failed | pass |
| Host Rust tests, all features | 700 passed / 0 failed | pass |
| Container run, Rust | 754 passed / 0 failed / 2 ignored | pass |
| C runtime / Python / Go race | 15 / 10 / full package | pass |
| Frontend build, web smoke test | 24 checks | pass |
| Real FUSE-T partial mount, Finder browsing | cache 54,288 → 54,288 B | pass |
| Requirements acceptance matrix | R01–R62 mapped to tests | done |
The password comes from CIPHERFS_PASSWORD or an interactive prompt. S3 credentials are read from environment variables and never written to the shared index.
cipherfs init --path ~/vault cipherfs put --vault ~/vault --source ./notes.txt --target docs/notes.txt cipherfs store add directory --vault ~/vault --dir ~/Library/CloudStorage/Dropbox/cfs cipherfs sync directory --vault ~/vault --store-dir ~/Library/CloudStorage/Dropbox/cfs cipherfs role set partial --vault ~/vault --read-cache-bytes 2147483648 cipherfs pin add --vault ~/vault --path docs cipherfs mount --vault ~/vault --mount-point ~/CipherFS --background cipherfs devices list --vault ~/vault cipherfs safe-to-remove --vault ~/vault cipherfs history --vault ~/vault --path docs/notes.txt cipherfs export --vault ~/vault --target ~/plain-copy --history
Rust applications use SyncedVault: put, put_from_reader, get, get_to_writer, get_range, delete, list. A successful write means the data is durable locally; a background task handles sync, reconnection and retries. Memory use does not depend on object size.
| Purpose | Algorithm |
|---|---|
| Content AEAD | XChaCha20-Poly1305 (optional AES-256-GCM, ChaCha20-Poly1305) |
| Chunking | FastCDC, min/avg/max 1 / 4 / 8 MiB, unit cap 16 MiB |
| Content addressing | BLAKE3 |
| Device identity | ed25519 signatures, X25519 wrapping, doubles as the P2P node id |
| Local password | Argon2id wraps local key material; never synced |
| Transport | QUIC (Iroh); HTTPS to stores as needed |