# Security model
Source: https://selfstore.dev/docs/security
What selfstore protects, in layers, and where each layer stops - encrypted backups, an at-rest local cache, the optional cacheLock, and the one ceiling no browser app escapes. Honest boundaries, not marketing.
selfstore is a security tool, so it states its limits as plainly as its
guarantees. This page is the summary; the full analysis - assets, trust
boundaries, thirteen named threats and explicit non-goals - is
[THREAT-MODEL.md](https://github.com/selfstoredev/selfstore/blob/main/THREAT-MODEL.md),
and the backup format is [independently specified](https://selfstore.dev/docs/format) with test
vectors.
## The shape of the model
There is no selfstore server, so there is no central store to breach. The trust
boundaries are simple:
- **Your device is trusted.** Your data is readable on the machine you use it
on - that is the point of local-first.
- **The home is untrusted for confidentiality.** A Drive admin, a WebDAV host,
an S3 operator: with a password they only ever hold ciphertext.
- **The network is untrusted.** Backups travel as ciphertext; credentials are
refused over plain http.
- **Your app's origin is trusted, and is the ceiling** (see the end).
## The layers
**1. The backup, end to end.** With a password, a backup is AES-256-GCM over a
random data key, itself wrapped per password with Argon2id (memory-hard, ~46
MiB). Whoever holds the file sees ciphertext plus a small cleartext header (app
name, date) that is never secret and never authenticated. A wrong password, a
flipped byte or an altered parameter all fail as `DECRYPT_FAILED` - there is no
partially-valid read.
The backup file (a .zip on your home): a tiny cleartext header, then everything else sealed. Whoever holds the file, without the password, holds noise.
**2. The local cache, at rest.** The IndexedDB working copy is not plaintext:
its collections and file blobs are AES-256-GCM-sealed under a **non-extractable
per-device key**. This defeats casual inspection, partial exfiltration and disk
forensics of the store. Its limit is honest: the key sits in the same database,
so a copy of the whole browser profile carries a usable key. Only the small
sync bookkeeping stays in the clear.
**3. `cacheLock` - beat a profile copy.**
For the top tier, seal the cache under a key held **in
memory only** - derived from a password, or an app-supplied key such as a
passkey PRF result - never written to disk. A copied profile then carries no
usable key. The unavoidable cost is one unlock per session: a secret that could
be derived without asking could be derived by an attacker with the profile too.
Branch it on your app's existing login and the UX is unchanged. See
[sensitive apps](https://selfstore.dev/docs/sensitive) for the setup.
## The ceiling: your origin
None of the layers changes the one limit no browser app escapes. **Code running
in your origin** - through XSS, a compromised dependency, a poisoned CDN - can
read the decrypted data and the in-memory password, whatever the cache lock is
set to. selfstore is defence-in-depth (secrets out of web storage, ciphertext
off the device), not a sandbox against the app it runs inside. This is the
number-one non-goal, stated up front. Mitigate it where it lives:
- A **strict Content-Security-Policy**; no third-party scripts.
- **Subresource Integrity** and a reproducible build.
- **Ship only the entries you use**, to keep the attack surface small.
## Other stated limits
- **Rollback.** Without a server, selfstore cannot prove a home served the
*latest* backup, only that committed local edits are never erased by an older
one. Mitigate by encrypting and using a home only the user controls.
- **Metadata.** An encrypted backup still reveals its app name, date and rough
size; record ids and clocks travel inside the encrypted envelope.
- **Password recovery.** There is none. Lose the password, lose the encrypted
backup. That is a promise, not a gap.
## Reporting
Security reports go to the process in
[SECURITY.md](https://github.com/selfstoredev/selfstore/blob/main/SECURITY.md).
Map of this site for a model: https://selfstore.dev/llms.txt
Every page in one file: https://selfstore.dev/llms-full.txt