mdread is a client-side, local-first app: there are no accounts, and reading and editing never send anything anywhere. That removes most of the usual attack surface, but a few areas still matter — chiefly rendering untrusted Markdown safely and, since sharing exists, keeping the share API blind.
When you share, the document is sealed in your browser with AES-GCM-256 and the key
is placed in the URL fragment, which browsers never transmit. The Worker
(worker/index.js) stores the ciphertext under a random id and can't decrypt it.
Anything that breaks that property is a vulnerability — see "In scope" below.
The link is the capability: anyone holding it can read the document. That's by design, not a bug.
Signing in is optional. It restores your list of share links and any documents you explicitly saved to the vault; nothing is uploaded unless you ask for it. Two credentials with two separate jobs:
- Email magic link proves which account you are. The server stores
SHA-256(email)and never persists the address itself. - Master password encrypts the vault. It is stretched with PBKDF2-SHA256 (600,000 iterations) in the browser and is never transmitted in any form — not hashed, not derived, not at all — nor stored, on the device or anywhere else.
The derived key may be kept in IndexedDB so a reload doesn't re-prompt. It is
created non-extractable, so crypto.subtle.exportKey refuses to return its bytes
even to script running on the page. Anyone with access to the browser profile can
still open the vault while it's remembered; "Lock now" and "Sign out" erase it.
A key that can be exported, or a stored master password, is a vulnerability.
So authentication decides which ciphertext you are handed; it never grants the ability to read it. A forgotten master password is unrecoverable by design, and there is deliberately no reset flow — adding one would require holding the key.
The latest release on main (and the deployed mdread.app)
is the only supported version. Fixes land there.
Please report security issues privately — do not open a public issue for anything exploitable.
- Preferred: open a GitHub security advisory.
- Or email cep.jewel@gmail.com with steps to reproduce.
Please include what you did, what happened, and the browser/OS. We'll acknowledge within a few days and keep you posted on the fix.
- XSS / HTML injection through rendered Markdown. All Markdown is parsed with
markedand sanitized withDOMPurifybefore it touches the DOM (src/modules/markdown.js); a bypass of that sanitization is the highest-value report. - Anything that causes data to leave the device (an unexpected network request), which would break the local-first/privacy guarantee.
- Service worker / cache-poisoning issues that could serve tampered assets.
- Any path by which a share key reaches the server — a key in a request line,
a
Refererheader, a log, or KV metadata. The fragment must stay a fragment. - Weaknesses in the share crypto itself (
src/modules/crypto.js): nonce reuse, a predictable key or id, or ciphertext that can be read or forged without the key. - Reading or deleting someone else's share without its link or delete token.
- Any path by which a master password or derived vault key reaches the server.
- Reading or writing another account's vault or vault documents, session fixation or forgery, a magic link that can be replayed or guessed, or a magic link appearing in any HTTP response body rather than only in email.
- Vault document bodies being readable without the master password, or a document body being cached in plaintext on the device.
- Account enumeration —
/api/auth/requestmust answer identically whether or not an address has an account.
Share links are end-to-end encrypted, so we cannot see, scan, or moderate what people store — that's the design, and it's also the limitation. If a link is being used to host phishing or other abuse, send the full URL to cep.jewel@gmail.com and the share will be deleted by id. Please don't open a public issue containing a live link.
- Attacks requiring a malicious browser extension or a compromised local machine.
- The contents of a Markdown file the user themselves opened (it's their own data, shown only to them).
- Reading a shared document when you legitimately have the link — the link is the password, and that is the intended behaviour.
- Reports against the bundled libraries that aren't reproducible in mdread — please report those upstream.