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

> ## Agent Instructions
> Search and list return handles, provenance, and coverage. Fetch a body only with GET /v1/notes/{note} (line slice) or /raw.
> Read coverage before treating a result set as complete. Zero hits with coverage is a valid answer.
> Follow error hint. Writes need an Idempotency-Key header and agent_id.
> The user creates personal access tokens in web Settings → Access tokens. Read is included; authenticated note mutations require notes.write. Supplying agent_id does not grant permission.
> Reuse an Idempotency-Key only for an identical retry. After VERSION_MOVED, re-read and submit the changed operation with a new key.
> Local MCP is read-only. Authenticated Cloud MCP adds write_note and edit_note only with notes.write and connected write storage; open local mode skips the permission check. Inspect tools/list before calling conditional tools.
> A queued write has not changed the note. Record pending_id. Authenticated GET /v1/writes/{id} also requires notes.review; write-only tokens must hand the ID to a reviewer.
> Leave approval to a human. Agents must not approve their own proposals or request review permission merely to finish a task. The API permits review-scoped personal tokens; this is a workflow rule.
> Cloud search and note reads do not return Private plaintext. Coverage says Private notes not searched. Sync may still carry ciphertext.
> Production origin is https://api.elanotes.com. baseUrl has no /v1 suffix.

# Security

> What Ela encrypts, what the server can still see, the Private notes passphrase minimum, and how to report a vulnerability.

This is the plain-language version of the threat model. Standard notes are readable by the server. Notes in Private are encrypted on the device. We do not call the product zero-knowledge, and we have not had an external penetration test.

## Private notes passphrase

A new passphrase must be at least 12 characters. Ela also rejects common passwords and simple patterns. The check runs when you turn Private notes on or change the passphrase. Unlock of a passphrase you already use does not apply the minimum.

The server never receives the passphrase. Enabling Private notes on the web creates the wrapped key in the browser and uploads only that wrap. Desktop change-passphrase and enable do the same check on the device. The API cannot enforce this minimum.

If you lose the passphrase and do not have a recovery key, Ela cannot reset it. There is no escrow.

A Library that already uses a shorter passphrase can still unlock. Where Settings can change the passphrase, Ela asks you to replace one that is below the minimum.

The wire recovery notice is unchanged: notes in Private stay unrecoverable without the passphrase or a recovery key.

## What is encrypted where

| Data | On the server | On a computer running desktop | On an iPhone |
| - | - | - | - |
| Ordinary notes | Plaintext, so search can index them | Plaintext markdown and a local index | Encrypted local cache |
| Private titles, tags, and bodies | Ciphertext only | Plaintext in the folder and in the local index after you unlock | Encrypted local cache. Reading them also needs your passphrase |
| Attachments on ordinary notes | The file itself, in object storage. A download link lasts 15 minutes | The file on disk | Whatever the local cache sealed |
| Personal access tokens | A hash, not the token | The token only if you put it in the environment or an MCP config | Keychain |
| Passphrase | Not stored. A public salt is | Not saved. Remember stores the data key in the system keyring | Keychain, with a check value |

The web app does not keep an offline copy of your notes. Unlock with the passphrase or a recovery key stays in that tab's memory and locks on idle, sign-out, reload, or Lock. Search in that tab can include the notes you have unlocked, and that search does not ask the server to match Private text.

Sync does **not** skip Private. It uploads ciphertext. The filename, the size of that ciphertext, and the time of the change stay visible. Cloud search and cloud MCP do not return those notes, and coverage says Private notes not searched. The coverage JSON still lists `private/`.

If you lose the passphrase, those notes are unrecoverable. There is no reset, and we do not hold a copy of the key.

## What a few attackers can do

**The server, or someone who can read the database.** They can read ordinary notes, attachment files, filenames of private notes, and ciphertext. They cannot read a private note without the passphrase. They can hide new edits or replay an old copy of the op log. A private note's ciphertext is bound to its library, note, and field, so a payload moved to another note is rejected. An older copy of the same note can still be replayed. A library that started on `e2ee:v1` can still be swapped in a web session that has not latched to v2-only.

**Someone who steals the laptop.** Desktop keeps markdown in the clear, including Private, and keeps a decrypted copy in the local database. Full-disk encryption is what stops this. Ela does not turn that on. The desktop app is unsigned.

**Someone who steals the phone.** The local database is encrypted, the key and the session tokens sit in the Keychain (this device only, when unlocked), and the store is excluded from phone backup. The phone passcode is what protects the Keychain. After the phone is unlocked, private notes that were already synced are plaintext inside that database, so the passphrase is not a second lock on the local copy.

**An agent connected to your notes.** It reads the text you let it read, including instructions hidden in a note. Cloud writes wait in the review queue unless you turn that off. Local MCP can read Private.

Each signed-in account gets its own library. A shared organization claim is not access. Session lifetime stays at the WorkOS defaults. API request logs drop the query string and redact authorization, cookies, and validation inputs. Production refuses to boot when a static bearer token is set.

## What we do not claim yet

* No external penetration test.
* Desktop signing and notarization are not done. There is no auto-update feed.
* Browser unlock of Private runs in the tab. Each visit trusts the code the site just sent. The installed desktop and iOS apps do not re-fetch the decryptor on every load. The web key is memory-only.
* Deleting a note does not erase provider backups. The backup retention window is a value the operator has to confirm. We do not publish a number we have not verified.
* Moving a note into Private removes it from search. Attachment files uploaded earlier can remain in object storage until they are collected. SVG is not an upload type.
* Required multi-factor sign-in is a WorkOS setting the operator turns on. This page does not claim it is on.

## Report a vulnerability

Email [mike@elavize.com](mailto:mike@elavize.com).

Please include what you did, what you expected, and the version or commit if you have it. Do not include note text from a real library. Give us a chance to fix the issue before you publish it.

Response times are listed in the repository `SECURITY.md`. Those times are placeholders until the owner confirms them. They are not a service level yet.

`https://elanotes.com/.well-known/security.txt` points at this page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.