Batch commit of the pending working tree on security/audit-fixes-2026-07. Major areas: - Outbound webhooks / Zapier: schema + signed delivery with retries, public v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain. - Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when Spaces is unconfigured instead of silently using ephemeral disk. - Integrations & features (concurrent work): accounting (QuickBooks/Xero), e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding, expanded legal pages. - DB migrations 0006–0009. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.9 KiB
3.9 KiB
Security & Pre-Deployment Checklist
⚠️ TREAT ALL SECRETS IN
.env.localAS COMPROMISED. This project was distributed in a transfer package, which means every secret that was present in.env.local— theDATABASE_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_URLeverywhere 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 sendAuthorization: Bearer <CRON_SECRET>.- 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=requirein production so the connection uses verified TLS (encrypted + certificate-verified).DATABASE_SSL=disableis only for local / unix-socket development. If the provider uses a private/custom CA, supply it viaDATABASE_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.tsnow defaults to verified TLS (rejectUnauthorized: true) and never silently runs plaintext. Behavior is controlled by the explicitDATABASE_SSLenv var (disable/no-verify/require), with optionalDATABASE_CA. - Constant-time cron auth —
lib/cron-auth.tsperforms atimingSafeEqualbearer-token comparison that fails closed whenCRON_SECRETis unset. All cron routes (daily,late-fees,follow-ups) now use it and share the standardAuthorization: Bearerscheme. - Security headers —
next.config.tssets 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.tsenables 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.