Appearance
Security
The threat model is a public SaaS on the open internet, run by a small team. Everything below ships enabled — none of it is a checklist left to the buyer.
Sessions & credentials
- Argon2id (64 MiB, 3 iterations) for passwords; timing-safe comparisons everywhere they matter.
- Server-side sessions (database-backed),
SameSite=Lax,HttpOnly,Securein production. Users list their signed-in devices and revoke them; a password change invalidates other sessions. - One-time tokens (verification, reset, magic link, invitations) are stored as SHA-256 hashes, single-use, expiring. A database leak exposes no usable link.
- TOTP two-factor with one-shot recovery codes, enforced on every sign-in path, anti-replay.
Request hygiene
- CSRF on every mutating request (
gorilla/csrf+ a header check on multipart uploads). - Rate limiting per IP and per account on login, signup, reset, contact.
- No account enumeration: signup, login and reset answer neutrally whether the account exists or not — and the tests assert it.
- Security headers: HSTS, CSP, X-Content-Type-Options, X-Frame-Options.
X-Forwarded-Foris honoured only fromTRUSTED_PROXIES.
Tenancy
Row-level: every business table carries organization_id, middleware resolves the org through the caller's membership, and outsiders get a 404 indistinguishable from a nonexistent org. Cross-tenant probes are part of the integration suite.
Outbound requests
internal/safehttp is the only client allowed to fetch configurable URLs (OIDC issuers, S3 endpoints, error collectors): https-only by default, private and link-local ranges blocked at dial time, redirects re-checked, response sizes capped. SSRF is handled where it starts — in the client — not in each caller.
Uploads
Type sniffed server-side against a short allowlist — SVG refused, it executes script when served inline — size-capped, stored under server-built keys, served by the app so buckets stay private.
Supply chain
CI pins every action to a full commit SHA, starts jobs from zero permissions, runs govulncheck, Trivy and npm audit, and CodeQL analyses Go and TypeScript on every push. Releases ship checksums.txt, a CycloneDX SBOM, SLSA provenance and a keyless Cosign signature.
The details, the verification commands and the disclosure policy live in the repository's SECURITY.md.