Security
Last reviewed 18 August 2026
This describes how the product actually works, including the parts that are weaker than we would like. If you are evaluating Secrets and Stuff and need something that isn't here, ask.
Encryption
Every item is encrypted with AES-256-GCM under its own single-use data key. That data key is itself encrypted with a long-lived master key, and only the encrypted form is stored. GCM is authenticated, so tampering with stored ciphertext causes decryption to fail rather than return corrupted data. There is no path that stores item content in plaintext, whatever its type.
The master key is held by the application, not by you. There is no persistent server holding it: it is stored as an environment variable in our hosting provider's configuration and injected into the serverless function that needs it, for the life of that invocation. It is never written to the database.
This means the application can decrypt your items, which is what lets sharing and recovery work the way they do. Two consequences worth being precise about. A copy of the database on its own cannot be read — a leaked backup or a breach on the database side yields ciphertext and nothing else. But the same hosting configuration also holds the database credentials, so anyone who compromises that account has both halves at once. That account is the single most important thing we protect, and it is the reason a managed key service is on the list below: it would not hide the key from that account, but it would put every decryption in a log we cannot alter and let us revoke access without a deployment.
We do not describe the vault as end-to-end encrypted or zero-knowledge, because it isn't. Moving item encryption into the browser is the direction we intend to go; until it ships, this page will keep saying so.
Share links are different
Share links genuinely are end-to-end encrypted, and this is not the same mechanism as above. The key is generated in the sender's browser and travels in the URL fragment — the part after the #, which browsers never transmit. The server receives ciphertext, an initialisation vector, and the options. It has no key and cannot decrypt a share link, ours or anyone's.
- The token in the link is 32 random bytes and is stored only as a SHA-256 hash. A copy of the database yields no working links.
- When a link is set to burn on read, opening it deletes the stored ciphertext rather than marking a flag. What remains is a record that it existed and was opened.
- Loading the page does not open or burn anything — link previewers and mail scanners fetch URLs unprompted, so revealing takes a deliberate action by the recipient.
- Password-protected links are checked in the recipient's browser, not on the server. The password never reaches us, and a wrong guess cannot consume a one-time link. The trade-off is that anyone holding the link can attempt passwords offline, which is why the key derivation uses 600,000 PBKDF2 iterations.
- The free tool needs no account. Those links are always one-view, expire in 24 hours, and are rate-limited per address. The address is stored as a keyed hash, never in the clear.
In transit
HTTPS only, with HSTS set to one year including subdomains. Application responses are sent no-store so that decrypted content is not held by intermediate caches. Responses also carry X-Frame-Options: DENY, X-Content-Type-Options: nosniff, and a strict referrer policy.
Accounts and access
Sign-in is Google only. We do not store, handle, or ever see a password, and multi-factor authentication is inherited from your Google account — if it is enforced there, it is enforced here.
Within an org there are owners, admins, and members. Every item has exactly one sharing scope: private, one named person, one team, or the whole org. The default is private, and a missing or invalid scope fails closed rather than open. Private means private — org owners and admins cannot read the contents of another member's private items, and cannot see their names in the audit log either.
What gets recorded
Reveals are logged, not just changes — the record shows who actually viewed a secret's contents, when, and from what address. Also recorded: item creation, edits, renames, sharing changes and deletions; membership, role, team and org changes; and share links being created, opened, and revoked. Admins can read their org's trail.
Secret contents, ciphertext, keys, and initialisation vectors are never written to the log. The name of a private item is treated as being as sensitive as the item itself, and is shown only to its owner.
Who else is involved
- Netlify — hosting
- Serves the application and runs its server functions. Sees request traffic and function logs.
- Neon — database
- Holds the encrypted data at rest, over an encrypted connection.
- Google — identity
- Handles sign-in. Receives nothing about your items.
- Stripe — billing
- Card details are entered on Stripe's own hosted checkout and never reach our servers. Stripe receives no vault data.
Deletion
Deleting an item deletes it and every stored version immediately — there is no recycle bin and no soft-delete. Deleting an org removes its items, teams, pending invitations, audit trail, and share links with it. Audit entries otherwise persist for as long as the org does; there is no automatic expiry.
What we haven't done yet
This list is here because a security page without one is not credible. These are known, they are accurate as of the date above, and they are roughly in the order we intend to address them.
- The master key sits in hosting environment configuration rather than a managed key service, alongside the database credentials — one account is the whole blast radius. Key rotation is not implemented either: the envelope design supports it, but the routine to re-wrap data keys does not exist yet.
- Item encryption happens on the server, not in your browser. See above.
- Removing someone from an org does not end their existing sessions. They lose access to the org on their next request against it, but a signed-in session is not actively revoked.
- Sign-in, sign-out, and failed authentication attempts are not recorded. The audit trail covers actions taken inside the application only.
- The audit trail is append-only by convention rather than by construction. There is no hash chaining and no write-once copy, so it is not tamper-evident against someone with database access.
- No independent penetration test has been performed.
- There is no SOC 2 report, and no audit is currently in progress. We would rather say that than imply otherwise.
- Backups are whatever the database provider's point-in-time recovery gives us. There is no independently tested restore procedure.
- No Content-Security-Policy header is set yet, and the access-control layer has no automated test coverage.
Reporting a problem
Email [email protected]. We will acknowledge within three working days and tell you what we intend to do.
Test against your own account and your own data only. Don't run denial-of-service attacks, don't use automated scanners against the production site, and don't access anyone else's data — if you reach someone else's data by accident, stop and tell us. Within those limits we will not pursue or support legal action against you for good-faith research.
Machine-readable contact details are at /.well-known/security.txt.
← Back