Technical controls
The cryptographic, access-control, and operational primitives that protect tenant data day-to-day.
Authentication
- Session auth uses an asymmetric RS256 JWT (15-minute access token + 7-day refresh token). Refresh tokens are stored as an HttpOnly, SameSite=Lax cookie scoped to
/api/v1/auth/. - CSRF protection is enabled on every state-changing route via a double-submit cookie pattern.
- Passwords are hashed with bcrypt; the cost factor is tuned for the production environment.
- API keys are prefixed with
lmn_, generated with a CSPRNG, and stored as a SHA-256 hash. Keys are shown once at creation and never displayed again — UI forces a copy-confirm before the dialog can close.
Authorisation
- Per-tenant roles:
owner,admin,member,viewer. Mutation endpoints check role; sensitive admin actions require theownerrole. - Cross-tenant platform admin is a separate flag (
users.is_platform_admin), distinct from the per-tenant role. Required for the operator dashboard at/admin/usage. - API keys carry granular scopes; only the minimum scope set is granted per key.
Multi-tenant isolation
- PostgreSQL row-level security with
FORCE ROW LEVEL SECURITYis enabled on every tenant-scoped table. Thelimena_approle does not haveBYPASSRLS; a query without an explicitapp.current_tenant_idreturns zero rows. - Cross-tenant operator reads run as a separate
limena_readonly/limena_maintenancerole with audited access.
Encryption
- In transit: TLS 1.2+ on every public endpoint. HSTS in production.
- At rest: integration credentials (OAuth refresh tokens, third-party API keys) are encrypted with AES-256-GCM using an HKDF-derived per-tenant key on top of a master key loaded from environment secrets. The plaintext value never persists.
Audit
Every mutation and every security-relevant event (login, logout, password change, API key creation/deletion, integration connect/disconnect, AI quota trip/reset) writes to audit_logs. The log is append-only at the application layer (the limena_app role has no UPDATE or DELETE privilege on the table) and exportable to CSV from Settings → Audit log.
Rate limiting + abuse
IP-keyed rate limits at the HTTP layer; per-tenant velocity caps for AI calls (default 500/hour); per-tenant monthly token ceilings (default 20M). Trips fail-soft with an in-app banner explaining the state.