# Security & Pre-Deployment Checklist > **⚠️ TREAT ALL SECRETS IN `.env.local` AS COMPROMISED.** > This project was distributed in a transfer package, which means every secret > that was present in `.env.local` — the `DATABASE_URL` / Postgres password, > `BETTER_AUTH_SECRET`, and any Stripe / OpenAI / SMTP credentials — has left a > trusted boundary and **must be treated as leaked**. Rotate **all** of them > before any production deployment or client handoff. Do not assume "it was only > a zip" — assume the file is public. --- ## 1. Rotate every secret before production / handoff Work through this list and rotate each item. Do **not** reuse any value that ever appeared in the distributed `.env.local`. - [ ] **Postgres password** — change the database role's password (or provision a brand-new role) and update `DATABASE_URL` everywhere it is configured. Then revoke the old credential. - [ ] **`BETTER_AUTH_SECRET`** — generate a fresh 32-byte secret: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` Rotating this invalidates existing sessions — expected and desired. - [ ] **Stripe** — roll the secret key (and restricted keys), and rotate the webhook signing secret in the Stripe Dashboard. - [ ] **OpenAI** — revoke the leaked API key and issue a new one. - [ ] **SMTP (SMTP2GO)** — rotate the SMTP password / credentials. - [ ] **`CRON_SECRET`** — set a strong random value (the cron routes now fail closed if it is unset): ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` Configure the same value in your host's env (e.g. DigitalOcean App Platform) so the scheduled tasks send `Authorization: Bearer `. - [ ] **Google OAuth** — if the client secret was present in the transfer, rotate it in the Google Cloud Console. ## 2. Secret hygiene - [ ] **Never commit `.env.local`** (or any real `.env*` with live values). Confirm it is listed in `.gitignore`. - [ ] Store production secrets in the deployment platform's encrypted env-var store (e.g. DigitalOcean App Platform → Environment Variables), not in files. - [ ] Use distinct secrets per environment (dev / preview / production). ## 3. Database / TLS - [ ] Set **`DATABASE_SSL=require`** in production so the connection uses **verified TLS** (encrypted + certificate-verified). `DATABASE_SSL=disable` is **only** for local / unix-socket development. If the provider uses a private/custom CA, supply it via `DATABASE_CA`. - [ ] Use a **managed Postgres on a private network** (or the provider's private endpoint) rather than a database exposed on a public IP. ## Resolved in code The following hardening has already been applied in this codebase: - **TLS enforcement** — `lib/db/index.ts` now defaults to verified TLS (`rejectUnauthorized: true`) and never silently runs plaintext. Behavior is controlled by the explicit `DATABASE_SSL` env var (`disable` / `no-verify` / `require`), with optional `DATABASE_CA`. - **Constant-time cron auth** — `lib/cron-auth.ts` performs a `timingSafeEqual` bearer-token comparison that **fails closed** when `CRON_SECRET` is unset. All cron routes (`daily`, `late-fees`, `follow-ups`) now use it and share the standard `Authorization: Bearer` scheme. - **Security headers** — `next.config.ts` sets a strict baseline on all routes: `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer` (protects the tenant-portal URL token), HSTS with preload, `X-DNS-Prefetch-Control: off`, and a Content-Security-Policy. - **Auth rate limiting** — `lib/auth.ts` enables better-auth's built-in rate limiting (20 requests / 60s per IP) to slow brute-force and credential stuffing. - **Test-plan backdoor removed** — the development-only backdoor that bypassed plan/subscription checks is disabled in production.