Security
Concrete controls — what we do, not what we wish we did.
Last updated
1. Tenant isolation
- Every database row that holds tenant data carries a user_id foreign key referencing user_sessions(id). Every query path filters by that id; cross-tenant joins are not possible at the schema level.
- Object storage is namespaced by user id (<uid>/<filename>). The file server refuses any path whose first segment does not match the requesting user.
- Inter-service requests carry an HMAC-signed X-User-Token header. Each Go service verifies the signature and only ever uses the embedded uid for downstream queries.
- SSE event streams filter every event to the connection's authenticated uid before flushing.
2. Data protection
- TLS 1.2+ on all public endpoints, terminated at the edge proxy with HSTS enabled.
- AES-256 at rest for both the primary database and object storage.
- Database role split — schema migrations run as the owning role at boot only; runtime queries use a DML-only role with no DDL privilege.
3. Authentication and access
- User sign-in via OAuth (Google today; additional providers on a near-term roadmap).
- Session cookies are HTTP-only, Secure, SameSite=Lax, and signed with a rotating secret.
- Production infrastructure access requires hardware-backed multi-factor authentication.
- Admin promotion in the application is a deliberate operator action via direct SQL — there is no self-serve admin escalation flow.
4. Infrastructure and operations
- All services run as containers behind an edge proxy with per-IP rate limiting.
- Persistent jobs use SELECT ... FOR UPDATE SKIP LOCKED for safe multi-replica execution and a stale-lock reaper for crash recovery.
- Per-user SSE connection caps prevent a single account from monopolising server file descriptors.
- Observability stack is always on: logs (Loki), metrics (Prometheus), traces (Tempo), dashboards (Grafana), alerts (Alertmanager).
5. Backups and disaster recovery
- Postgres point-in-time recovery via pgBackRest with daily fulls and continuous WAL archive to encrypted off-site storage.
- Backup integrity is verified by a periodic restore drill into an isolated environment; the most recent successful drill is documented internally and surfaced on request to enterprise customers.
- Recovery objectives: RPO ≤ 15 minutes, RTO ≤ 4 hours.
- Backups expire 30 days after creation. Account-deletion requests purge live data within 30 days; backups containing the deleted data expire on the same retention schedule.
6. Secret management
- Secrets are file-mounted into the runtime via the orchestrator's native secret store (Docker Secrets / Kubernetes Secrets / Vault). They are never written to environment variables baked into images.
- Two rotation classes are documented internally — one set requires a coordinated restart, the other is rolling.
- API keys for upstream providers (e.g. ByteDance) are scoped to the minimum permissions required.
7. Supply chain
- CI runs govulncheck on every Go change, bun audit on every JS change, and Trivy SARIF scanning on every built container image.
- Dependency updates are opened weekly by Dependabot and grouped by ecosystem so reviews stay focused.
- Reproducible container builds via pinned base images and lockfile-only dependency installs.
8. Responsible disclosure
If you believe you've found a security vulnerability, please email security@example.com with reproduction steps. We acknowledge within 2 business days, triage within 5, and aim to remediate critical severity issues within 30. We do not pursue legal action against good-faith security research that respects user privacy and follows this policy.