Repository navigation
fix(cli): fall back to plaintext under sslmode=prefer like libpq - #7509
Conversation
An unset sslmode or sslmode=prefer now tries TLS first and retries the same host in plaintext when the server declines TLS, the handshake fails, or pg_hba rejects the TLS connection (SQLSTATE 28000). Other failures are not retried in plaintext. require and stricter modes stay TLS-only.
CLI benchmark (Lightweight Commands): no changes foundCommit
API calls: unchanged for all 7 commands. Binary size: 134.7 MB, unchanged. Details: timing ranges, API calls per command, slowest phasesTiming ranges
API calls per command
Slowest phasesNo command moved beyond noise, so there are no phase deltas. Comment |
There was a problem hiding this comment.
🤖 AI Review
Confirmed both Claude findings by reading the implementation, surrounding configuration, tests, and trusted contracts. Codex reported no findings. Verification was static; runtime dependencies are absent.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | apps/cli/src/command-internal/db-connection.sql-pg.layer.ts:840 |
error-handling |
claude | Under prefer/unset, a TLS 28000 rejection followed by a plaintext 28000 rejection advances to the next host or DoH address instead of terminating. A later connection or error can therefore mask the primary host's pg_hba rejection. |
| 🟡 MINOR | apps/cli/src/command-internal/db-connection.sql-pg.layer.ts:503 |
security |
claude | The new plaintext fallback also applies to generated linked-project connections without an SSL-mode override. An on-path attacker can decline SSL negotiation and trigger an unencrypted retry carrying database credentials and SQL, removing the previous TLS-only default for these connections. |
Stats
Claude findings: 2 · Codex findings: 0 · Confirmed: 2 · Refuted: 0 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: auto · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
…8000 Under prefer, a 28000 on the plaintext retry after a TLS 28000 moved on to the next host, which could mask the primary's pg_hba rejection. A 28000 is now final once the target has been tried over TLS, matching libpq.
libpq downgrades silently. The CLI now prints a warning naming the host and pointing to PGSSLMODE=require, so an unencrypted connection to a server that should offer TLS is visible.
- Include the TLS attempt's error before the plaintext one when both are rejected with 28000. - Reword the downgrade warning to cover a failed or rejected TLS connection. - Fix the sslmode doc: absent now means TLS first, then plaintext. - Assert the warning in the prefer, 28000, TLS-success and allow tests.
Summary
An unset
sslmode(libpq's default,prefer) and an explicitsslmode=preferwere TLS-only. That broke any remote Postgres or Supavisor without TLS, such as a self-hosted stack, even thoughpsqlconnects to the same URL fine:preferand unset now follow libpq: try TLS first, then retry the same host in plaintext.sslmodedisableallowprefer/ unsetrequire,verify-ca,verify-fullLocal targets (the running project stack) still connect in plaintext without a TLS attempt.
When the plaintext retry runs
The retry is limited to failures where the server could not do TLS. Unreachable hosts and auth errors are not dialed again in plaintext.
For a 28000 rejection the retry matches libpq, which reconnects without TLS when the server rejects a
preferTLS session.requireand stricter modes have no plaintext attempt, so they still fail on these errors, with the existingsslmode=disablehint.Fixes #4142