diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 4264e64..5c0dbbe 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -56,6 +56,7 @@ export function getPool(): pg.Pool { // Strip sslmode from the URL so our explicit `ssl` option fully controls TLS behavior. // Without this, pg merges URL-derived settings which can conflict with the options below. let ssl: pg.PoolConfig['ssl']; + const wantsTls = process.env.DATABASE_SSL === 'require' || process.env.DATABASE_SSL === 'verify'; if (process.env.DATABASE_SSL === 'disable') { // Explicit opt-out for databases that don't speak TLS at all (e.g. a disposable Docker // Postgres). In production this is honored ONLY for a private-network host; pointing it at @@ -66,14 +67,16 @@ export function getPool(): pg.Pool { ); } ssl = false; - } else if (ca) { - // Verified TLS against the managed-DB CA — the correct posture everywhere. - ssl = { ca, rejectUnauthorized: true }; - } else if (isProd && isPrivateHost(connectionString)) { - // Production database on a private network (app + Postgres on the same Docker/Swarm overlay). - // The connection never leaves that network, so plaintext is acceptable — and the managed-DB CA - // that verified TLS requires does not exist for a self-hosted container. + } else if (isPrivateHost(connectionString) && !wantsTls) { + // Database on a private network (app + Postgres on the same Docker/Swarm overlay, or a local + // container). The connection never leaves that network, so plaintext is fine here. This is + // checked BEFORE the CA branch on purpose: a leftover DATABASE_CA_CERT_PATH from a previous + // managed-database provider must not force TLS onto a server that does not speak it. Set + // DATABASE_SSL=require (or =verify) to opt a private host back into TLS. ssl = false; + } else if (ca) { + // Verified TLS against the managed-DB CA — the correct posture for any public host. + ssl = { ca, rejectUnauthorized: true }; } else if (isProd) { // Public database host in production: never connect over unverified TLS. Fail fast so a missing // CA cert is a loud deploy error instead of a silent man-in-the-middle exposure.