Security whitepaper
Encrypted where you work, not where we store it.
ReachFS servers cannot read your file contents or file names, by construction. This paper shows how, and states every limit of that claim.
Last updated October 3, 2026
Summary
ReachFS servers cannot read customer file contents or file names. Not by policy, by construction: compromising ReachFS infrastructure yields ciphertext.
Every file and name is encrypted on the customer's own computer before it is uploaded. The keys that open them exist only on that workspace's approved devices, and the server only ever relays them sealed. Object storage, whether ReachFS-managed or the customer's own bucket, holds ciphertext only.
The claim has edges, and this paper states them: the server sees the shape of the folder tree, file sizes and when things happen; notifications name the few files that were locked, conflicted or stuck uploading; and a workspace whose approved devices and recovery phrase are all lost cannot be recovered, by anyone. Known limitations lists every one.
Architecture at a glance
Only members' computers ever hold keys or plaintext. The metadata service syncs sealed metadata and hands out short-lived storage keys; object storage and the optional team cache hold only encrypted blocks.
Threat model
The design assumes ReachFS's own servers, staff and storage provider may be compromised, and protects file contents and names against all three.
| Threat | Protected | How |
|---|---|---|
| ReachFS server compromise | Yes | The server holds no key that can decrypt |
| Malicious ReachFS insider | Yes | Same: an insider sees ciphertext and the tree's shape |
| Object storage compromise | Yes | Blocks are encrypted before upload |
| Network interception | Yes | TLS 1.3, with the payload already encrypted underneath |
| Stolen laptop, powered off | Yes, with full-disk encryption | Device key in Windows Credential Manager; offline copies are ciphertext. The file list and unsent changes rely on BitLocker |
| Stolen team cache box | Yes | It holds encrypted blocks and no keys |
| Departed employee, files written after they left | Yes | Removal starts a new workspace key they have no path to |
| Departed employee, files written before they left | Opt-in | They may keep the old key; re-encryption closes it for live files |
| Stolen ReachFS password, inbox or session | Partly | Decrypts nothing without an approved device. Can request a new device, which an existing device must still approve |
| Malicious client binary, supply chain | Partly | Signed releases; reproducible builds are planned |
| Stolen laptop, unlocked and running | No | The drive is mounted and readable, as any local disk would be |
| Traffic analysis | No | Sizes and timing are visible; see What the server can and cannot see |
Cryptography
Each workspace has a 256-bit workspace master key (WMK), generated on the device that creates the workspace and never transmitted unwrapped. Everything else derives from it.
- Device keys. Each device generates an X25519 keypair at enrollment. The private key stays in Windows Credential Manager; the public key goes to the server. A device receives the WMK only as a sealed box addressed to its public key.
- Derived keys. BLAKE3
derive_keyturns the WMK into a metadata key (file metadata and directory-entry names), a content-addressing key (block ids) and a name key (case-insensitive name hashes, so the server can enforce one name per folder without seeing names). - Blocks. Files are split into 4 MiB blocks. A block's id is a keyed BLAKE3 hash of its plaintext, and its key and nonce are derived from that id:
key || nonce = BLAKE3_xof_keyed(block_root, block_id)[..56]
The key is a function of the plaintext, so each key encrypts exactly one message in its life, with no counter to keep. Identical content in one workspace produces the same block, which is stored once. Deduplication is workspace-scoped by design: another customer's copy of the same footage has a different id, so dedup cannot reveal what other tenants hold.
Metadata and names are rewritten in place, so they use a random 24-byte nonce, and each blob is bound by associated data to the row it belongs to: a blob moved to another file fails to open.
| Purpose | Primitive |
|---|---|
| Block, metadata and name encryption | XChaCha20-Poly1305 |
| Key wrapping to a device | X25519 sealed box |
| Key derivation, content addressing, name hashing | BLAKE3 (keyed and derive_key) |
| Recovery phrase | BIP-39, 24 words, with its checksum |
| Stored tokens (invites, sign-in codes, sessions) | SHA-256 of 32 random bytes |
| Passwords | argon2id, 64 MiB, 2 passes |
| Server-side secrets at rest | AES-256-GCM |
| Transport | TLS 1.3, HSTS |
Devices and keys
The server is a relay, not a key holder. An attacker with the full database and full control of sign-in still cannot enroll a device that decrypts: only an already-approved device can wrap the WMK to a new one.
- First device. It generates the WMK, seals it to its own public key, and uploads the sealed blob and a 16-byte BLAKE3 fingerprint. The server can open neither.
- Later devices. A new device registers its public key and waits as pending. An approved device belonging to the same person, or to an owner or admin, seals the WMK to it. The new device opens it locally.
- Revocation. Revoking a device deletes its sealed key on the server and flags the workspace for a new key epoch. On its next request the device is told it is revoked: it unmounts, then deletes its file list, unsent changes, cache and private key. Admins can do this to a member's device with Remove and wipe.
Every request names the device making it, and the server checks that the device belongs to the signed-in user and is not revoked. Long-lived sync streams re-check the session, device and membership every 30 seconds, so revocation reaches an open stream within one heartbeat.
A revoked device cannot be re-approved; it must enroll again with a new keypair. A device that never reconnects keeps what it already had on disk.
Removing people
A removed member cannot read anything written after their removal. Removing someone, or revoking a device, starts a new key epoch: a fresh random WMK, sealed to every remaining device, with the previous key wrapped under it.
epoch 1 WMK_1
epoch 2 WMK_2 + wrap(WMK_1)
epoch 3 WMK_3 + wrap(WMK_2)
Current members walk down this ladder to read anything ever written. A departed member holding an old key has no path up: each new key is fresh randomness, not a derivation. The server cannot rotate keys itself; the removing admin's device does it at once, or the next approved device to connect. Once rotated, the server refuses new data sealed under an older epoch, so a lagging device cannot keep writing what the departed member could read.
Removal also releases the person's file locks and revokes the storage credentials their devices held.
| Question | Answer |
|---|---|
| Can they read files written after removal? | No |
| Can they read files written before? | Yes, if they obtain the ciphertext; they may hold the old key |
| Can they get that ciphertext from ReachFS? | No. Their membership and storage keys are gone; the risk is out of band, such as an old backup |
| What closes the past? | Re-encryption (reachfs rewrap): every live file's content and metadata is rewritten under the current key, and the old blocks are deleted after the garbage collector's grace period |
| What re-encryption does not cover | Directory-entry names, and old copies pinned by snapshots, the recycle bin or version history until they expire |
One key is deliberately never rotated: the name key, which enforces one name per folder through a unique index. A departed member can therefore test whether a given file name exists, but cannot list names.
What the server can and cannot see
The server cannot read file contents, file names, folder names or media details. It does see the structure it needs to sync, lock and bill, listed here in full.
| The server sees | What that reveals |
|---|---|
| The folder tree's shape (which item sits under which) | Counts and nesting depth |
| File, block and slice sizes | A known file's size may fingerprint it |
| When every operation happens, and by which device | Working hours, project cadence, deadline crunches, who works on what |
| Member emails and device names | Needed to run the product |
| Paths of files named in notifications: a file someone was refused because it was locked, a conflicted copy, or files that keep failing to upload | Those few paths are stored with the notification for 90 days and sent through the email provider |
| Which files each person favourites, by id only | Interest, not names |
| Which files share blocks | That identical content, or a copy by reference, exists within the workspace |
Hiding these would cost what the product is for: tree shape needs oblivious data structures, sizes need padding, timing needs cover traffic. They are documented instead. Because the server cannot read names, it also cannot browse files: only the desktop app, holding the keys, can list them.
Data at rest and in transit
Everything ReachFS stores off the customer's computers is ciphertext or a hash. On the computers, two stores are deliberately plaintext and rely on full-disk encryption.
| Where | State | Protection |
|---|---|---|
| Object storage (managed or own bucket) | Encrypted blocks | Client-side XChaCha20-Poly1305, plus the provider's own at-rest encryption |
| Server database: file metadata and names | Encrypted | Client-side; the server cannot open them |
| Server database: tree structure, sizes, times | Plaintext | Listed in What the server can and cannot see |
| Server database: own-bucket credentials, authenticator secrets | Encrypted | AES-256-GCM under a server secret |
| Server database: passwords, tokens, sessions | Hashed | argon2id; SHA-256 for random tokens |
| Computer: offline copies (block cache) | Encrypted | The same ciphertext as object storage |
Computer: file list (mirror.sqlite) |
Plaintext names | Windows account permissions; wiped on revocation. Plaintext so search is instant |
| Computer: unsent changes (write-ahead log) | Plaintext | Windows account permissions; drained as it uploads; wiped on revocation |
| Computer: device private key, session | Protected | Windows Credential Manager (DPAPI) |
| Team cache box | Encrypted blocks | Holds no key |
Full-disk encryption (BitLocker or equivalent) is a deployment requirement: it is what protects the two plaintext stores on a stolen, powered-off laptop.
In transit, everything uses TLS 1.3 with HSTS, terminated at the server's reverse proxy. That is defence in depth: the payload was already encrypted on the device.
Accounts and sign-in
Accounts are ReachFS's own; no third-party identity service sits in the sign-in path. A ReachFS account identifies a person; it decrypts nothing on its own.
- Passwords are hashed with argon2id, at least 10 characters. A sign-in for an unknown email takes as long as a real one, so it reveals nothing about who has an account.
- Two steps for every password account. An authenticator app (TOTP) is required, with ten single-use backup codes. Each code works once.
- Passkeys (WebAuthn with user verification) count as both steps.
- Google and Microsoft sign-in uses OpenID Connect with PKCE. Only a verified email is accepted, because invites match on email.
- Limits. 10 wrong passwords in 15 minutes lock the account for 15 minutes; sign-in and reset requests are rate-limited per address. Security events (a new location, password changes, a new authenticator, backup codes used) are emailed to the person.
- Workspaces can require two-step sign-in. Sessions record whether their sign-in had a second step, and the server refuses the rest.
Desktop sign-in happens in the browser and hands back to the app with a one-time code bound to a PKCE challenge, so a program that intercepts the reachfs:// link cannot use it. The app also checks a state value, so an attacker cannot sign a victim's app into the attacker's account. The resulting desktop session is stored only as a hash on the server, kept in Windows Credential Manager on the computer, rolls forward 30 days with use, and can be revoked. A session cannot create a new session: starting one always takes a live sign-in on the website.
Invite links carry their token in the URL fragment, which browsers never send to servers, and are accepted only by the invited email address.
Storage access
No ReachFS app ships a storage key. A signed-in engine asks the server for credentials, and the server checks membership and that the request comes from one of the person's own non-revoked devices. It then returns a key it minted that:
- reaches only that workspace's prefix in the bucket;
- can read, or read and write (viewers get read-only), and can never delete;
- expires after two hours and is held only in the engine's memory.
Removing a member, demoting one to viewer, or revoking a device deletes the workspace's issued keys at the provider immediately, on every server instance. The keys that can delete stay on the server, with the garbage collector. A leaked engine key therefore reaches one workspace's ciphertext for at most two hours, and can destroy nothing.
Bring your own bucket. A workspace owner can keep the workspace in their own B2 or S3-compatible bucket. The owner's key is encrypted at rest on the server (AES-256-GCM, bound to the workspace) and never returned to any client.
| B2 bucket | Other S3-compatible bucket | |
|---|---|---|
| Keys given to devices | Minted from the owner's key, as above: one workspace, no delete, two hours | The owner's own key, after the owner confirms it reaches only that bucket |
| On removal or revocation | Revoked at once | Valid until the owner rotates it at their provider |
Either way the bucket only ever holds ciphertext, and never a workspace key.
Archive. Archiving a folder makes the server copy its encrypted blocks to a separate cold-storage bucket, verify each copy, and only then delete the original. The server moves ciphertext it cannot read; nothing is decrypted to archive or restore. Workspaces on their own bucket cannot be archived into ReachFS's archive storage.
Sharing outside the workspace
A share link gives someone without an account one file for review, or one folder read-only, as it was when the link was made. It carries its own key, and the server still cannot read what is shared.
- The app draws a fresh 256-bit secret and puts it in the link's
#fragment, which browsers never send to a server. - The link's manifest (each block's id with its own key, and how the bytes lay out) is sealed under that secret with XChaCha20-Poly1305. So are reviewers' comments and names.
- The secret goes to the server only sealed under the workspace's metadata key, so members' apps can read what reviewers wrote.
- The recipient's browser decrypts everything locally.
The server serves only the blocks the link was made with, only with a 12-hour token it issues after checking the link's password (bcrypt) and expiry. Guests never receive the workspace key, because a workspace key cannot be limited to one folder; they get exactly that folder's blocks.
The server learns that a link exists, which blocks it covers and their sizes, and when it is opened. Turning downloads off hides the download button, but a viewer who can watch can record the screen, and the app says so.
Key recovery
ReachFS holds no key that can open customer files, so it cannot recover them. If every approved device in a workspace is lost, the only way back is the workspace's recovery phrase.
- When a workspace is created, its creator is shown a 24-word BIP-39 phrase once, and must confirm they saved it before the workspace exists. Its checksum makes a mistyped word fail at once instead of silently deriving a useless key.
- The phrase derives an X25519 keypair. The server stores only the public half and a copy of the WMK sealed to it: one more blob it cannot open.
- When the key changes after a removal, the rotating device re-seals the new key to the phrase's public half, so the phrase keeps working without anyone typing it in.
- Recovery opens that blob on a new device with the phrase and seals the key to that device.
| Option considered | Decision |
|---|---|
| No recovery at all | Rejected: one lost laptop would lose a solo user everything |
| User-held recovery phrase | Shipped |
| Optional escrow held by the customer's own admin | Planned; it would be the customer's choice, and stated as such |
| Escrow held by ReachFS | Rejected: it would end the security claim |
Operational security
Biased toward keeping data. Orphaned storage costs pennies; deleting live data is unrecoverable. So:
- A block no file uses any more is kept for a 30-day grace period (14 days for what was already unreferenced when a recycle bin is emptied, never less) before the weekly sweep deletes it.
- The sweep re-checks, inside its own transaction, that no live file uses a block before deleting it, whatever the reference count says. It runs as its own service so it can be stopped on its own, and stopping it is step one of the data-loss runbook.
- A monthly audit compares storage with the database and reports orphans; it never deletes them.
- Each computer keeps unsent changes in a write-ahead log until the server confirms them, so a crash or a refused upload loses nothing.
Application controls. Every call checks workspace membership and role on the server; roles are never trusted from the client. Inputs are bounded by schema and explicit checks, mirrored as database constraints. Queries are generated and parameterised. Membership, device and storage-configuration changes are audit-logged. Dependencies are scanned with cargo audit, govulncheck and pnpm audit. Releases are code-signed.
Incident response.
| Scenario | Response |
|---|---|
| Server compromise | Rotate server secrets and storage credentials. Customer data stays encrypted. Customers notified within 72 hours |
| Client vulnerability | A signed emergency release, and the server can refuse outdated clients |
| Suspected data-loss bug | Halt the sweep at once, freeze deploys, investigate from the write-ahead logs and event log |
Disclosure. Report vulnerabilities to security@reachfs.app (also listed in security.txt). We acknowledge within 2 business days, triage within 5, fix critical issues within 30 days, credit reporters who want it, and take no legal action against good-faith research that avoids customer data and service disruption. Flaws in the client's cryptography or device enrollment are the most valuable class to report.
Compliance roadmap.
| Milestone | Status |
|---|---|
| Documented security policy set and vulnerability disclosure policy | Done |
| Third-party penetration test of server and client | Planned before general availability |
| SOC 2 Type I | Planned at launch |
| SOC 2 Type II | 6 to 12 months after Type I |
| MPA / TPN | After v1 |
Known limitations
Stated plainly, because overclaiming would be worse than the limits themselves.
- Metadata leaks. The server sees tree shape, sizes, timing and who touched which item. Notifications name the few files that were locked, conflicted or stuck.
- No recovery without a device or the phrase. If every approved device and the recovery phrase are lost, the files are gone. ReachFS cannot help.
- Removal does not reach the past by itself. A removed member may keep the old key. Re-encryption closes this for live files, but not for directory-entry names or for copies pinned by snapshots, the recycle bin or version history until they expire.
- Revocation needs the device to reconnect. A lost computer that never comes online again keeps what was on its disk.
- Two plaintext stores on each computer: the file list and unsent changes. Full-disk encryption is required to protect them.
- An unlocked, running computer is out of scope. Its drive is readable like any local disk.
- Own S3 buckets have weaker key control than B2. Devices hold the owner's key, which does not expire and is not revoked on removal until the owner rotates it.
- Share links can be recorded. Turning downloads off does not stop screen capture.
- The name key is never rotated, so a departed member can test whether a given file name exists, without being able to list names.
- Supply chain. Releases are signed; reproducible builds are not yet available.