Security

How Mauvely Cloud's encryption works, what the server can still see, and what this design does not protect against.

Mauvely Cloud is zero-access: the server stores files it cannot read. This page says precisely what that means, and precisely what it does not. Everything below is a summary of what the product actually does — including the parts where the honest answer is “this is not protected”.

The claim, stated precisely

File contents, file names and MIME types are encrypted in your browser before they are transmitted. No key that opens them is ever sent to Mauvely, derivable by Mauvely, or stored on Mauvely’s servers in a usable form.

A complete dump of Mauvely’s database and file storage, with nothing else, decrypts to nothing.

The key hierarchy

password ──Argon2id──→ key-encryption key ─┐
                                            ├──→ master key
24 words ──BIP-39───→ recovery key ────────┘        │
                                                    │
                              your key pair ────────┤
                             (to receive shares)    │
                                                    │
                                        your root folder key
                                                    │
                                    each folder's key, wrapped
                                    under its PARENT's key
                                                    │
                                       each file's content key
                                                    │
                              file bytes, in 4 MiB authenticated chunks

Four properties everything else follows from:

Your password and your recovery phrase are peers. Two independent wrappings of the same master key. Either opens it, neither reveals the other. There is no third copy and no administrative override, because either would be a key Mauvely holds.

Folder keys chain under their parent. Handing someone a folder key gives them that folder and everything beneath it, and nothing above it. Sharing a subtree is one key handed over, not a re-encryption pass.

Content keys are separate from folder keys. Renaming, moving and re-sharing rewrap a key and never touch a byte of the file.

The master key never changes. A password change re-wraps it; so does generating a new recovery phrase. Both are instant regardless of how much you store, and neither re-encrypts anything.

Why these choices

Choice Reason
Argon2id, 64 MiB memory Memory-hard, so a GPU cannot parallelise password guesses the way it can against older schemes. Kept at 64 MiB because asking a mobile browser for more gets the tab killed rather than a slower hash.
AES-256-GCM Authenticated. Tampered data fails to decrypt rather than producing garbage the app might act on.
BIP-39 wordlist No two words share their first four letters, so a mistyped or transposed word is caught before you are told the phrase is wrong. Cloud uses the wordlist and checksum only — a Mauvely phrase is not a wallet phrase.

Your password stopped reaching Mauvely

Your vault key is derived from your account password. If the account system still received that password, it could derive your vault key — and “we cannot read your files” would be false, however good the cryptography underneath.

So it does not. Your browser stretches your password and sends the result as its credential. Two separate derivations, two separate salts, from the same password:

  • One produces the credential mauvely.com verifies.
  • One produces your vault key, which never leaves the browser.

Neither can be computed from the other’s output.

File encryption

Files are encrypted in 4 MiB chunks. Two guards matter more than the choice of cipher:

Every chunk has a unique nonce, derived from a random per-version base with the chunk’s index in a fixed field. Reusing a nonce under this cipher does not weaken it, it breaks it — so reuse is made structurally impossible rather than merely unlikely.

Every chunk is bound to its position: which version it belongs to, which index it is, and how many chunks there are in total. Encryption alone proves a chunk was not edited; it says nothing about whether it is the chunk that belongs here. Without this binding, the server could silently truncate a file, reorder two chunks, or splice in a chunk from another file — and every individual decryption would still succeed.

All four of those attacks are covered by tests, along with decryption under the wrong key.

Keys in your browser

  • Keys are imported in a form the browser will not let scripts read back. Injected script could still use a key while your vault is open — unavoidable in a web app — but cannot copy it out.
  • Keys are never put in localStorage or sessionStorage. Those hold plain strings, and a string is exfiltrated in one line. “Trust this device” uses the browser’s key storage instead, the one place a key object can be kept without its bytes ever being available again.
  • Auto-lock after 30 minutes idle drops keys from memory rather than hiding the interface, on any device you have not chosen to trust.

The content policy is part of the encryption

Browser-based encryption assumes the code in the tab is the code we shipped. Any origin that can inject a script into the page can read your master key while the vault is open.

So Cloud’s content security policy names no host but its own, and every third-party convenience the main site enjoys is given up here: fonts are served from Cloud itself rather than a font CDN, the design system is served from this origin, and there is no analytics, no CDN and no error reporter.

What the server can still see

Encryption hides contents. It does not hide the shape of what is stored, and pretending otherwise would be dishonest.

Visible Why
How many files and folders you have Each is a row
The approximate size of every file Encrypted data is about the same length as the original. Padding is future work
The shape of your folder tree Parent links drive every listing and permission check
Creation and modification times Ordering and the trash sweep need them
Who you shared with, and when Shares are rows keyed on accounts and email addresses
Which email addresses have Cloud vaults Sharing by email cannot work otherwise. Session-gated and rate-limited, but a real disclosure
Which file was accessed, when, from which address Every read passes through the server
Your total storage used Quota enforcement is the point
Who is in which workspace Membership is what access is checked against
In a workspace chat: who was mentioned, when each message was sent, and by whom This is what makes unread counts and “you were mentioned” work without decrypting anything
Which app a file came from — Compose, Snap or the web Stored in the clear so the storage breakdown can be counted server-side, and so a plan that excludes desktop sync can be enforced without opening anything

Not visible: file contents, file names, MIME types, thumbnails, folder names, workspace names, message bodies.

Published courses are outside all of this

One feature deliberately steps outside everything above, and it would be dishonest to leave it implicit.

A shared course is stored unencrypted. It has to be: a learner opening your link has no account and holds no key, so there is nothing that could decrypt it for them. Publishing takes a copy of a course out of your vault and puts it on the public web. The original file stays encrypted and is untouched, nothing else in your vault changes, and the interface says all of this before you publish rather than after.

Two consequences worth stating:

  • Mauvely can read a published course, in the same way anybody with the link can. It is a public web page.
  • Courses are served from courses.mauvely.com, never from cloud.mauvely.com. A Compose course can contain custom code blocks — real JavaScript written by whoever built the course. On the vault’s own origin that script could read your master key out of a tab you had unlocked. A separate origin is the entire mitigation, and it is why the course origin holds no session, sets no vault cookie, and is named nowhere in the vault’s content policy.

Learner names, email addresses and progress are visible to us, because they are ordinary rows: the report belongs to whoever published the course, we do not aggregate it across courses, and there is no global view of it.

Learn

Learn is a different model. A course held in Learn is protected by keys Mauvely manages, scoped to your organisation — not the key hierarchy above, and not a key a browser derives.

Teachers and organisation admins can see progress, answers and feedback for their own organisation’s courses. No organisation sees another’s.

Learn never calls a course “encrypted.” The zero-access claim on this page is about Cloud, not about Learn.

Workspaces

A workspace is a folder whose key is wrapped separately for each member, using the same mechanism as sharing with a named person. Its chat is encrypted under a key derived from that same folder key — so exactly the people who can read the files can read the messages, by construction rather than by a permission check.

An organisation admin is not an exception

An Enterprise admin can see seats, storage, workspace sizes and the audit log. They cannot read a file, a file name, a workspace name or a message, and there is no setting that grants it.

This is the same design, applied consistently: a workspace’s key is wrapped to its members’ public keys by their browsers, and an admin who is not a member holds no copy. Neither do we. An admin who needs access to a team’s files is added to that team by someone already in it, in the open.

An escape hatch here would be a key Mauvely holds and could be compelled to produce, and it cannot be true that we hold no key for individuals but do for the customers paying most.

Sharing with a named person

The file’s key is wrapped to the recipient’s public key. Only their private key opens it, and the server sees only the wrapped result.

The weak point is key distribution, not the cryptography. The server hands out the recipient’s public key — so a malicious or compromised server could substitute its own and read everything shared afterwards. Nothing in the protocol prevents this.

What the product does instead is make it detectable:

  • Fingerprints are shown in the share dialog and on the Encryption page.
  • A recipient’s fingerprint is pinned at the moment you share.
  • A later change is surfaced loudly.

If you are relying on this against Mauvely itself rather than against an outside attacker, verify a fingerprint out of band. Key transparency is the only thing that closes this properly, and it is not built.

What this does not protect against

Stated plainly, because a threat model that omits these is not one.

A malicious server serving malicious JavaScript

The fundamental limit of encryption delivered by a browser. Mauvely could ship a build that captures your password on the next unlock. Nothing in this design prevents that, and no web application can.

The mitigations are the strict content policy, no third-party origins, and the fact that such a build would be visible in the code the browser downloads.

A native client with a signed binary is a genuinely stronger position, and Compose and Snap are now in it: each holds its own key pair, and your master key is wrapped to that device’s public key by a browser after a human compares fingerprints. A desktop app never receives your password and never derives your vault key from one. Revoking a device is deleting one row.

A compromised computer

Malware on your machine with the vault open reads everything. Encryption at rest does not help here.

Public-key substitution when sharing

Covered above: detectable via fingerprints, not prevented.

A guessed or phished password

The vault opens for anyone who can supply the password and holds a session. Two-factor authentication on mauvely.com gates the session, and Cloud refuses a session that has not cleared its second factor — but the password itself is the last line.

Traffic analysis

File sizes, timings and access patterns are visible to anyone who can see the traffic reaching the server, and to the server itself.

Someone you already shared with

Revocation stops future access. It cannot un-know a key someone already holds, and cannot delete what they already downloaded.

Whoever controls a synced passkey

A passkey is a third way to open your vault, beside your password and your recovery phrase. If that passkey syncs — through iCloud Keychain, Google Password Manager or similar — then whoever controls that account can unlock the vault without your Mauvely password. A device-bound passkey does not have this property. Your recovery phrase is unaffected either way.

Ask AI

Ask anything about Mauvely

Answers come from this documentation.