Files
property-management-network/SECURITY.md
T
Leon SerfatyandClaude Opus 4.8 c9968531e4 Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
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>
2026-07-02 13:42:34 -04:00

3.9 KiB

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 <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=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 enforcementlib/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 authlib/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 headersnext.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 limitinglib/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.