Security
How madal protects what you publish, and where that protection ends. Last updated 16 October 2026.
The short version
madal encrypts a page on your machine before it's uploaded. The server stores ciphertext and
never receives a key. A link carries its key after the #, which browsers don't send
to servers. A page shared with people by email carries no key at all: the key is sealed to each
reader's public key, and only their devices can open it.
Only you can unlock your data. Your master password and the Secret Key in your Emergency Kit are the only things that unlock your keys. If you lose either one, nobody can get your data back, madal included.
What the server sees, and what it doesn't
It can't see:
- the content of link and private pages, or their titles
- the key of any link, or the key that opens a page shared with people or held for an invite. The one exception: someone reporting a page can choose to include its key, so we can check the report.
- your private keys, your master password or your Secret Key
It does see, because it needs them to work:
- who owns each page, its size, and when it was created and updated, so only you can change it and your page list works on every device
- when it expires and how many times it was opened, so it can enforce the limits you set
- who a page is shared with or invited to, and team memberships, so it serves the page only to them
- everyone's email and public keys, so you can share by email
- a salted hash of your IP address, for rate limits
- public pages in full, since they aren't encrypted. That's what
--publicmeans.
It uses these to run madal and for nothing else.
How a page is encrypted
Each page gets its own 256-bit key from a secure random source. Every version is sealed with AES-256-GCM under a fresh nonce, and the page's id and version number are bound in as associated data, so the server can't pass one version off as another or swap one page's content into another link.
K = 32 random bytes link = https://madal.facets.cloud/a/<id>#base64url(K) blob = "MDL1" || nonce(12) || AES-256-GCM(K, nonce, page, aad = "madal/v1/<id>/<version>")
With a password, the key becomes HKDF-SHA256(K, salt = PBKDF2-SHA256(password, 600,000
iterations)). The password is mixed into the 256-bit key and doesn't replace it, so
someone holding the ciphertext can't guess the password offline, and someone holding a leaked
link still needs the password.
The browser decrypts with WebCrypto, using the same format as the CLI. Test vectors produced by the Go code are decrypted by the browser code in CI, so the two can't drift apart.
Sharing with people and teams
- Every account has two P-256 key pairs, made on your device: one to receive keys (ECDH), one to sign team changes (ECDSA).
- Sharing wraps the page key to the reader's public key: ephemeral ECDH, HKDF-SHA256, then AES-256-GCM. The wrap names the page and the reader, so the server can't move it to another page or another person.
- A team has its own key pair, wrapped to each member. Every change to a team's key is signed by an admin and chained to the change before it. Devices refuse a rollback, a fork, or a signer they haven't accepted.
Your keys and the vault
Your private keys are stored on the server inside a vault, sealed with a key derived from two secrets, as 1Password does: your master password, and a 128-bit Secret Key made on your device that the server never receives.
unlock = PBKDF2-SHA256(master password, 600,000 iterations)
XOR HKDF-SHA256(Secret Key, salt = account id)
A stolen copy of the server's data can't be cracked by guessing your master password, because each guess would also need the Secret Key's 128 random bits. Signing in with Google, GitHub or an email link proves who you are. Only the master password and Secret Key together unlock your keys, and setup gives you an Emergency Kit with the Secret Key to keep.
Who can open a page
Every page has one of four kinds of access. You can move a page between named people and a
link later with madal share <id> --access only-people or --access
link. Once someone has been removed and the key has changed, one link can no longer open
every version, so going back to a link means publishing the page again. A public page can't be
switched either: publish it again instead.
- Only you, the default. The key is wrapped to your account alone, so it opens on any device where you've unlocked your keys.
- People and teams you name (
--to,madal share --with). Each reader gets the page key wrapped to their own key, and the server serves the page only to accounts that hold such a grant. A forwarded link opens nothing. - Anyone with the link (
--link). The key rides in the link, so whoever has the full link can open it. Switching such a page to named people makes the server stop serving it to anyone without a grant, so old links stop working. - Public (
--public). Not encrypted, readable by anyone.
Sharing with someone who doesn't use madal yet gives them an invite link. It carries a secret the server never sees, and it works only for an account signed in with the invited email, so a forwarded invite link opens nothing. Once they open it, it works like any page shared by name.
Removing a person or team takes effect at once: the server stops serving them the page, and the next version uses a new key they never receive, even if they kept the old one. What they opened before, they may have saved.
How long a page lasts
- A page lasts until you delete it, unless you give it an end:
--expires(up to a year) or--max-opens(up to 1,000 downloads by anyone but you). You can change both later withmadal share. Changing them starts the open count again from zero. - When it expires it stops opening, and its stored copy is deleted the next time anyone asks for it. When it reaches its open limit it stops opening for everyone but you.
madal deleteremoves every version and its stored files right away, and the link stops working for everyone. It can't be undone.- Only the newest 5 versions of a page are kept. Publishing a sixth deletes the oldest.
- Deleting your account deletes every page you own, so links you handed out stop working. What happens to your sign-in record is on the privacy page.
If you lose your keys
- A new device or browser: sign in, then unlock with your master password and your Secret Key from the Emergency Kit. Your keys are the same, so every page you published signed in, and every page shared with you, opens again. The only exception is a link published without an account, before accounts were required: it lives only on the machine that made it.
- A lost device: from another device, run
madal logout --everywhere(or sign out everywhere in the browser). That ends every sign-in, so the lost device can no longer fetch private pages or anything shared with you. Link pages are different: the device keeps their full links, and a link opens for anyone who has it. Delete those, or switch them to named people. - A forgotten master password or a lost Emergency Kit: there's no reset,
because nobody else holds what unlocks your keys. The only way on is to start from scratch
(Start from scratch on the keys page, or
madal account delete), which deletes your account and every page you own, then lets you sign up again with the same email and new keys. Pages others shared with you need to be shared with you again, and people who share with you will see that your key changed.
What to share with madal
Good fits: reports, dashboards, plans, reviews and notes you'd otherwise send in chat or email, where you want the server not to be able to read them and the link to stop working when you say so.
Poor fits:
- passwords, API keys and other credentials, whether you're keeping them or sending them. Scripts on a page can read what's on it once it opens, and links travel. Use a password manager, or a tool built for sending secrets.
- anything that must stay secret after a reader has opened it, since a reader can keep a copy
- data your rules say may only live in systems you've vetted, such as regulated health or payment data. madal hasn't been audited for that.
- pages that must stay private from whoever runs madal even in the worst case, opened in a
browser. See the first limit below, and use
madal getfor those.
Pages run in a sandbox
Agent-written HTML is untrusted code. It runs in an iframe on a separate domain,
madal.facetsusercontent.cloud, with sandbox set and without
allow-same-origin. A page can run scripts and make network requests, which is how it loads libraries from
CDNs; that also means a script on the page can send what the page shows anywhere. It can't
read cookies or storage, submit forms, navigate the page around it, or reach the app or your
keys. The decryption key never enters the frame: the app decrypts and hands only the HTML over.
Links a page opens in a new tab load as ordinary pages, outside the sandbox. The sign-in cookie is
HttpOnly, Secure, SameSite=Strict and sent only to
madal.facets.cloud.
Limits you should know
- You trust the JavaScript the server sends. Like every web app that decrypts in the
browser, a malicious operator could ship code that leaks a key from the link. The CLI doesn't
load code from the server when it runs:
madal get <link>decrypts on your machine, without a browser. - The first share trusts the server's copy of a public key. After that, devices
remember keys and refuse to share when one changes until you accept it. To rule out a swapped
key on the first share, compare fingerprints:
madal whoamishows yours, andmadal keys fingerprint EMAILshows anyone's. - A link is only as private as wherever you paste it. Anyone with the full link can open it. For people who must not pass it on, share by email instead.
- The source is closed for now. The browser code is readable as served, and you can check the claims above yourself, as shown below.
Check it yourself
- Open a madal link with your browser's developer tools on the Network tab. The request
for the page has no
#part, and the response body is ciphertext. - Remove everything after
#and reload. The page doesn't open. - The decryption code is
/static/crypto.js, served unminified.
Reporting a vulnerability
Please don't post security issues in public. Report them privately on GitHub: github.com/Facets-cloud/madal-releases/security. Only the maintainer sees the report. Include what you found and how to reproduce it.