Skip to content

Production security baseline

A secure production setup is not one feature switch. It is a set of boundaries that must agree: who can call a handler, which tenant a request belongs to, which fields are personal, where keys live, and how an operator recovers from a bad deploy.

Use this page as a launch gate. It explains the order and the evidence to collect; the linked guides and recipes contain the implementation details. A passing test suite is necessary, but it does not replace a staging drill or a backup restore.

Start by writing down which rows apply to your app. A single app can need all four profiles.

ProfileMinimum additional controls
Authenticated internal appRevocable sessions, handler access rules, audit, rate limits, backups
Multi-tenant SaaSTenant-scoped handlers, membership roles, ownership rules, cross-tenant operator boundaries
Public web surfaceAuthoritative tenant resolver, explicit anonymous access, field filtering, IP-based limits on public writes
Personal or regulated dataPII annotations, subject-key KMS, blind indexes where needed, GDPR export/erasure, key recovery procedure

If you cannot name the tenant boundary, the public entry points, the privileged roles, and the data subjects before writing code, stop here and resolve those design questions first. Read Multi-tenancy, Auth and permissions, and Crypto-shredding.

Complete this checklist with an application owner and an operations owner:

  • a staging deployment that uses the same image and environment shape as production
  • a test user for each relevant role, including a restricted tenant member and a system operator
  • a secret manager and a documented break-glass procedure
  • a tested Postgres, Redis, subject-key, and file-storage backup/restore path
  • a rollback image tag and a known database migration boundary
  • a list of public routes and the trusted input that resolves their tenant
  • a data inventory: personal fields, owners, retention periods, and external processors

Keep the evidence with the release record. Never put credentials, plaintext keys, PATs, MFA recovery codes, or copied production payloads into that record.

1. Mount the account and security baseline

Section titled “1. Mount the account and security baseline”

Start with the framework’s preset rather than hand-picking only the features that happen to be visible in the first screen.

  • Security baseline recipe shows the securityBaselineFeatures() composition and its production boot warning.
  • The preset covers sessions, crypto-shredding, rate-limiting, and audit. Your auth setup still needs the appropriate user, tenant, and email/password path.
  • Sessions provide server-side revocation; a signed JWT alone is not a logout or force-logout mechanism.

Run the app’s composition/boot check and then the production-like boot path. There must be no missing-security-baseline warning and no plaintext-PII fallback accepted as a production decision.

Terminal window
bun run boot
bun run typecheck
bun test

The exact test command can differ between app repositories. Keep the focused security-baseline and authentication integration tests in the release record.

2. Prove authorization, not just authentication

Section titled “2. Prove authorization, not just authentication”

Authentication answers who is calling. Authorization answers what that identity may do.

  1. Declare handler access explicitly. Handlers are default-deny.
  2. Use field-level access for sensitive columns; do not hide fields with ad-hoc branches in a handler.
  3. Use entity ownership rules when users may see only their own rows.
  4. Keep SystemAdmin operations in system-scoped features and require an access rule on every exposed handler.
  5. Use Personal Access Tokens only for headless clients, with the smallest domain and read/write scope they need.

The Field-level access recipe and auth concept provide the concrete contracts.

Test each boundary with a negative case:

  • a regular tenant member cannot call an admin handler
  • a caller without field-read access does not receive the field
  • a caller without field-write access cannot submit the field
  • a user from tenant A cannot read or mutate tenant B’s rows
  • a PAT cannot call a handler outside its configured scopes
  • a role downgrade or membership removal takes effect on the next PAT request

A UI hiding a button is not evidence. The HTTP/dispatcher request must be denied as well.

3. Close the tenant and public-surface boundary

Section titled “3. Close the tenant and public-surface boundary”

Every request needs a trustworthy tenant context.

  • Normal handlers use tenant-scoped ctx.db; do not import another feature’s table or construct a second unscoped database path.
  • Cross-tenant work is isolated in a system-scoped feature with explicit roles and an audit reason.
  • Public handlers use roles: ["anonymous"], not openToAll.
  • An anonymous request gets its tenant from an authoritative resolver such as the host/subdomain. The client must not submit the tenant id as authority.
  • Anonymous writes need validation and an IP-based handler limit. The anonymous access guide and anonymous multitenant recipe show the complete boundary.

Test a public route from two tenant hosts, then try an unknown host, a forged tenant header, and a public write above its rate limit. The expected outcomes must be tenant isolation, a safe resolver error, tenant-mismatch rejection, and 429 respectively. Do not ship a public resolver that trusts an unvalidated client header.

Add MFA after the ordinary login path works, and roll it out in stages:

  1. Mount TOTP-based MFA next to email/password auth.
  2. Store JWT, setup-token, and challenge-token secrets in the deployment secret store. Keep the setup and challenge purposes separate.
  3. Enroll administrators and support staff first.
  4. Verify normal login, challenge login, recovery-code login, disable, and session revocation in staging.
  5. Set tenant-wide admins or all enforcement only after the affected users have enrolled.

The Auth MFA recipe is the raw wiring contract; the guide is the rollout procedure. Recovery codes are shown once and must be handled like credentials.

Record a successful MFA enrollment and login for each enforcement group, plus a negative login for an unenrolled user who should be blocked. Verify that enabling, disabling, or regenerating MFA invalidates the other live sessions as intended.

Treat event history as durable. If personal data enters an event, it needs a durable erasure story before launch.

  • Annotate personal fields with the correct subject, tenant, or ownership relationship. Deliberately non-personal fields need an explicit reason.
  • Provision the subject-key KMS and the separate blind-index key before using lookupable encrypted fields.
  • Keep the secrets master key separate from the platform KEK and blind-index key.
  • Use a persistent file provider for GDPR exports; an in-memory provider loses export artifacts on restart.
  • Verify that API responses, search indexes, exports, and mail do not expose ciphertext after the subject key is erased.
  • Keep the key-manager migration and key-rotation guide as separate change procedures. Migration preserves values; rotation changes key material.

The Crypto-shredding recipe proves subject-key erasure, user-owned data, blind indexes, and the [[erased]] sentinel.

Before traffic:

  1. Write and read a record created before the current release.
  2. Perform one controlled subject-key erase in staging.
  3. Verify detail, list, event replay/rebuild, search, and export behavior.
  4. Run an equality lookup for every lookupable encrypted field.
  5. Restore a backup into an isolated environment and repeat the read probe.

Stop if a production boot would fall back to plaintext or if a rebuild process lacks the blind-index key.

The security boundary continues into the deployment system:

  • inject secrets from the deployment secret store; never commit or echo them
  • set TZ=UTC for the server process
  • build an immutable image and retain the previous image for rollback
  • apply the checked-in schema before starting the new app
  • run both liveness and readiness checks
  • start the worker lane when the topology requires async projections or jobs
  • configure persistent Postgres, Redis, file, and subject-key backups
  • test a restore and record the result, not just that a backup exists

Choose the deployment topology that matches the operational owner:

  • Docker for a reproducible image and a pre-deploy schema step
  • Solo for one VM with a small operational surface
  • K3s for multi-replica production, workers, HA Postgres, backups, and infrastructure-as-code

Run the image’s schema-apply step against a staging database, boot the image, exercise /health and /health/ready, then roll back to the previous image tag. Confirm that the database boundary is understood before deciding whether a code rollback is safe after a migration.

Attach these results to the release or change record:

  • feature graph and production boot validation passed
  • handler, field, row, tenant, and PAT negative-access tests passed
  • public resolver and anonymous rate-limit tests passed
  • MFA enrollment, challenge, recovery, and rollout checks passed
  • old encrypted data read successfully with the active KMS
  • controlled erasure, export, search, and rebuild checks passed
  • schema apply, health, readiness, and worker checks passed
  • backup restore and image rollback were rehearsed
  • no secrets, tokens, recovery codes, or plaintext PII entered logs or Git

A checklist item without an owner, timestamp, and evidence is not complete.

This page is a security route, not a generic compliance certification and not a provider-specific runbook. It does not decide your retention period, legal role, DPA coverage, threat model, or incident response obligations. Use it to find the Kumiko control and the test that proves it, then complete the deployment-specific and legal work for your environment.