The old site said "AES-256 encryption, the same as Google Cloud". That means nothing. Here is what does.
Edge
TLS and security headers
A Let’s Encrypt certificate renewed automatically by Traefik, with HTTP redirected to HTTPS. Restrictive content policy, frame denial, nosniff and referrer policy, reapplied in every configuration block, because nginx headers are not inherited.
Data
Encryption at rest
National ID numbers encrypted inside the database. Third-party access tokens stored with envelope encryption: one key encrypts the key that encrypts the data, so a copy of the database never becomes access to the client’s account.
Authentication
Sessions
A cookie signed with HMAC-SHA256, compared in constant time, httpOnly and scoped to the site. The secret is mandatory in production: without it the application refuses to start, with no fallback to a development value. And the token carries a fingerprint of the credential, changing the password instantly drops every old session on every device, with no session table in the database.
Authorisation
Three-layer access control
A middleware guard, per-request revalidation of the user and a check inside every server action. A user who is deleted, blocked or whose credential changed drops to logout on the next request, not at the next sign-in.
Brute force
Login attempt limits
Login and password recovery are rate-limited per email address and per IP, with a single generic error that never reveals whether an address exists. Single-use reset links, valid for 30 minutes and invalidated if the password changes.
Availability
Rate limiting at the edge
We counted the files the homepage requests, allowed for visitors on mobile behind carrier NAT, and picked thresholds that stopped a load test of 654 requests per second without stopping the legitimate traffic simulated in the same test. The public capture service accepts 10 requests per minute per IP.
Infrastructure
The server
A closed ufw firewall and fail2ban on SSH, installed after we counted 346 brute-force attempts in seven days in the log. Multi-stage containers running as a non-root user. The mail service on an internal network, with no port exposed on the host.
Secrets
Secrets kept out of the bundle
No secret ever ships in the bundle sent to the browser. Environment variables on the server, and the Stripe webhook verified by signature. In the system distributed as an installer, the session secret is generated on first boot on the client’s machine with restricted permissions, the executable leaves here with nothing inside it.
Validation
Untrusted input
Schemas validated at the application boundary. Price and shipping recalculated on the server, because a value arriving from the browser is not data, it is a suggestion. Uploads restricted to the administrator, with an allowlist of types, a size cap and a randomised filename.
Trail
Auditing and backups
Every sign-in records date, user and device, and every conflict resolved during offline sync is written down. The database backup leaves the machine that produced it; frequency and retention are agreed per project, and restoring is tested, a backup never restored is just a file.
Supply chain
Dependencies
Versions pinned in a lockfile, updates reviewed before they ship, and a deliberately small surface: one of the landing pages delivered has zero third-party JavaScript libraries, only the typeface.
Process
A checklist that travels
We keep a 24 KB SECURITY.md that moves from project to project, recording what was checked, what was left out and why. You can ask to read the one for your project.