Security
Last updated 26 August 2026
This page describes what we actually do, not what we aspire to. If something here stops being true, this page changes with it. To report a vulnerability, skip to reporting a vulnerability.
1. Accounts and sign-in
- Passwords are hashed with Argon2id, the current password hashing competition winner. We never store, log or email a password, and we cannot tell you what yours is.
- Sign-in is throttled by email address and IP address separately, so someone spraying one account cannot lock its owner out.
- Magic links are single-use, expire in 15 minutes, and are stored hashed. A stolen database backup does not yield working links.
- Changing a password invalidates every other session. Sign-in and password-reset responses are deliberately identical whether or not the address exists, so neither confirms an account to a stranger.
- Sensitive actions require re-entering your password even when you are already signed in — a borrowed unlocked browser is not enough to take an account over.
- Your dashboard lists every active session with its device and location, and you can revoke any of them individually.
2. Sessions and the browser
- Authentication uses HttpOnly, Secure, SameSite cookies, not tokens in local storage. A cross-site scripting bug cannot read a cookie JavaScript is not allowed to see.
- Every state-changing request carries a CSRF token bound to the session.
- A strict Content Security Policy restricts where scripts, frames and images may come from. Article embeds are limited to a named list of providers.
- HTTPS is enforced with HSTS. There is no unencrypted route into the application.
3. Data handling
- Data is encrypted in transit (TLS 1.2+) and at rest. Third-party API credentials are additionally encrypted at the application layer, so they are unreadable in a database dump.
- We do not store your IP address. Rate limiting and unique counts use a daily-rotating HMAC that cannot be reversed and is useless the next day.
- Tool inputs are not retained unless a tool says so and you agree. What we keep by default is which tool ran, when, whether it succeeded and how long it took.
- Uploads go to private storage, are served only through short-lived signed URLs, and are deleted automatically after 30 days.
- Database backups are encrypted and tested by restoring them — an untested backup is not a backup.
4. User-submitted content
Anything that will be rendered as markup — article content, custom HTML blocks — is sanitised against an allow-list when it is saved and again when it is rendered. Sanitising twice matters: the rules can be tightened after something was already stored under looser ones, and stored content is treated as untrusted regardless of who submitted it. Scripts, event handlers and inline styles are removed, and iframes are restricted to named providers.
5. How we build
- Dependencies are scanned for known vulnerabilities on every build, and the build fails on a high-severity advisory.
- Static analysis and automated tests run on every change. Nothing reaches production without passing both.
- Administrative access uses granular, role-based permissions — each action requires its own permission rather than a blanket "admin" flag — and privileged actions are written to an audit log.
- Production access requires multi-factor authentication.
6. Reporting a vulnerability
Email [email protected] with enough detail to reproduce the issue. We aim to acknowledge within two business days and to tell you our assessment and expected fix timeline within ten.
We will not pursue legal action against anyone who reports in good faith, stays within their own test account, does not access or modify other people's data, does not degrade the service, and gives us a reasonable window to fix the issue before publishing.
We do not currently run a paid bounty programme, but we credit every reporter who wants to be named.
Questions about this document? Get in touch.