Skip to content

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, Secure in 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-For is honoured only from TRUSTED_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.

Commercial license — you buy it once, the code is yours.