> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mauvely.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> How Mauvely Cloud's encryption works, exactly what the server can still see, and — stated plainly — 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.

### 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.

<Note>
  The mention list is the one deliberate exception in chat. Everything a message *says* is
  ciphertext, but the user ids it tags are stored in the clear — an unread badge has to be
  countable server-side, and the alternative is downloading and decrypting every message in every
  workspace to find out whether there is anything new.
</Note>

## 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](/cloud/shared-courses) 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.

## Workspaces

A [workspace](/cloud/workspaces) 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.

<Warning>
  **Removing a member cannot un-know a key they already had.** Their wrapped copy is deleted and
  the server stops serving them immediately, so they lose everything from that point on. But
  anything they downloaded while a member stays readable to them, and no key management reaches a
  file already on someone's disk.

  Re-keying the workspace on removal would close this, at the cost of re-wrapping for everyone
  remaining. It is not built.
</Warning>

### 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.

<AccordionGroup>
  <Accordion title="A malicious server serving malicious JavaScript" icon="code">
    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.
  </Accordion>

  <Accordion title="A compromised computer" icon="bug">
    Malware on your machine with the vault open reads everything. Encryption at rest does not help
    here.
  </Accordion>

  <Accordion title="Public-key substitution when sharing" icon="user-secret">
    Covered above: detectable via fingerprints, not prevented.
  </Accordion>

  <Accordion title="A guessed or phished password" icon="key">
    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.
  </Accordion>

  <Accordion title="Traffic analysis" icon="chart-line">
    File sizes, timings and access patterns are visible to anyone who can see the traffic reaching
    the server, and to the server itself.
  </Accordion>

  <Accordion title="Someone you already shared with" icon="share-nodes">
    Revocation stops future access. It cannot un-know a key someone already holds, and cannot
    delete what they already downloaded.
  </Accordion>
</AccordionGroup>
