Hardening checklist
Production checklist for self-hosted novamem. Walk this top-to-bottom before exposing the service publicly.
Secrets
- [ ]
NOVAMEM_COOKIE_SECRETisopenssl rand -hex 32. Never the placeholder. Rotating invalidates every session. - [ ]
POSTGRES_PASSWORDisopenssl rand -base64 24. Stored via Kubernetes Secret /.env(mode 0600) — never in git. - [ ]
NOVAMEM_BOOTSTRAP_ADMIN_PASSWORDis set ONLY for first boot. Comment it out / unset after the first user is created. - [ ] No tenant tokens (
nm_…) committed to repos / shared via plaintext channels.
Network
- [ ] Reverse proxy (nginx / Traefik / cloudflared) terminating TLS in front of novamem. The server itself doesn't do TLS.
- [ ]
NOVAMEM_INSECURE_COOKIES=0(default). Anything else dropsSecureflag — only safe behind a guaranteed-TLS proxy on localhost. - [ ]
NOVAMEM_CORS_ORIGINSis a tight allowlist if you're running browser-based SDK clients on different origins. - [ ]
NOVAMEM_HOST=127.0.0.1if your reverse proxy is on the same host — avoids accidental direct exposure.
Auth
- [ ]
NOVAMEM_AUTH_MODEisuserortenant, nevernonein production. - [ ] Bootstrap admin password changed via the dashboard's account page after first sign-in.
- [ ] Tenant tokens scoped to a project where possible (one per service / agent host).
- [ ] Token rotation policy: revoke + remint quarterly, or whenever a host is decommissioned / employee leaves.
Datastores
- [ ] Postgres backups enabled (pg_dump nightly + WAL archive for PITR if you need it). Test the restore.
- [ ] Postgres
max_connections≥NOVAMEM_PG_POOL_MAX× replica count + headroom. - [ ] Qdrant collections backed up (snapshots), or accept that they're rebuildable from warm-tier embeddings.
- [ ] FalkorDB persistence enabled (Redis AOF / RDB) — graph state is recoverable from warm via reindex but downtime is unpleasant.
Dashboard
- [ ] If you don't need the dashboard:
NOVAMEM_ADMIN_DASHBOARD=0—/admin/*and/v1/admin/metrics404. - [ ] If you do need it: behind a separate auth layer (mTLS / VPN / SSO proxy) when possible. Better Auth + cookies is good but defense-in-depth.
Observability
- [ ] Pino log level set appropriately (
infofor prod,debugfor staging). - [ ] Logs shipped somewhere durable (Loki / Cloudwatch / Datadog) — not just stdout in a pod.
- [ ] OTLP enabled (
OTEL_EXPORTER_OTLP_ENDPOINT) and traces routed to your existing collector. - [ ]
/healthpolled by your load balancer + monitoring (alert when degraded).
Audit
- [ ]
admin_audit_logreviewed periodically — every admin action is recorded. - [ ] User table reviewed quarterly — disable accounts of departed teammates.
Headers
novamem sets these by default; verify none are stripped by your reverse proxy:
Strict-Transport-Security: max-age=31536000; includeSubDomains(when behind TLS)Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'X-Content-Type-Options: nosniffReferrer-Policy: same-origin
Rate limiting
NOVAMEM_RATE_LIMIT_PER_MINUTE = 600 is per-IP. Tune for your traffic — too low blocks legitimate agents, too high doesn't protect against floods. SSE bypasses the limit (one long-lived connection); see MAX_SESSIONS_PER_USER for SSE caps.
Pin the docker image
Don't run :main in production. Pin to a release tag (v1.1.4) or short sha (:sha-d8e602f). The release notes on GitHub describe what each tag includes.
Threat model snapshot
| Threat | Mitigation |
|---|---|
| Bearer token leaked | Revoke from dashboard. Short labels per host help isolate the blast radius. |
| Cross-tenant data access | Tenant boundary enforced at every route. Token resolution returns the bound tenant. Cross-tenant share is impossible by design. |
| Cross-user data access (within tenant) | User-global memory is private; access only via project membership. Project membership is explicit. |
| Cookie session theft | Sessions are HttpOnly + Secure. Rotate NOVAMEM_COOKIE_SECRET to mass-invalidate. |
| Replay attacks | Better Auth sessions are server-resolved per request — no JWT replay window. |
| Prompt injection via memory entries | Memory content is opaque to novamem. Defence is upstream (in your agent). |