Skip to content

fix(cli): fall back to plaintext under sslmode=prefer like libpq - #7509

Merged
jgoux merged 4 commits into
developfrom
fix/db-sslmode-prefer-fallback
Nov 6, 2026
Merged

jgoux merged 4 commits into
developfrom
fix/db-sslmode-prefer-fallback

Conversation

@jgoux

@jgoux jgoux commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Summary

An unset sslmode (libpq's default, prefer) and an explicit sslmode=prefer were TLS-only. That broke any remote Postgres or Supavisor without TLS, such as a self-hosted stack, even though psql connects to the same URL fine:

$ supabase db push --db-url "postgres://postgres:…@db.internal:5432/postgres"
failed to connect to postgres: failed to connect to `host=db.internal user=postgres database=postgres`: tls error (The server does not support SSL connections)

prefer and unset now follow libpq: try TLS first, then retry the same host in plaintext.

sslmode before after
disable plaintext plaintext
allow plaintext → TLS plaintext → TLS
prefer / unset TLS only TLS → plaintext
require, verify-ca, verify-full TLS only TLS only

Local 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.

flowchart TD
  T[TLS attempt] -->|connected| OK[use connection]
  T -->|"server answered N to SSLRequest,<br/>or the TLS handshake failed"| P[plaintext attempt, same host]
  T -->|"28000: pg_hba has no TLS entry<br/>(hostnossl only)"| P
  T -->|"28P01 / 3D000 / 42501<br/>(wrong password, missing database, …)"| F[fail]
  T -->|"anything else<br/>(unreachable, timeout, …)"| N[next host, if any]
  P -->|connected| OK
  P -->|"failed (not an auth error)"| N
Loading

For a 28000 rejection the retry matches libpq, which reconnects without TLS when the server rejects a prefer TLS session. require and stricter modes have no plaintext attempt, so they still fail on these errors, with the existing sslmode=disable hint.

Fixes #4142

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.
@jgoux
jgoux requested a review from a team as a code owner November 5, 2026 12:02
@jgoux
jgoux requested a review from Prashansa-K November 5, 2026 12:02
@jgoux jgoux self-assigned this Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

CLI benchmark (Lightweight Commands): no changes found

Commit 63bed03 against develop at 3217fb4 · 10 runs each · report only, never blocks merging

Command Workload Before After Change
supabase --version version 161 ms 162 ms no real change
supabase --help help 172 ms 174 ms no real change
supabase init init 163 ms 163 ms no real change
supabase migration new bench migration-new 165 ms 168 ms no real change
supabase gen types --lang typescript --project-id replayfixtureprojref gen-types 203 ms 211 ms no real change
supabase projects list projects-list 184 ms 188 ms no real change
supabase functions list --project-ref replayfixtureprojref functions-list 201 ms 190 ms no real change (-6%, within normal variation)

API calls: unchanged for all 7 commands.

Binary size: 134.7 MB, unchanged.

Details: timing ranges, API calls per command, slowest phases

Timing ranges

Command Workload Before range After range Change Verdict
supabase --version version 153-172 ms 150-218 ms +1 ms (+0%) within noise
supabase --help help 158-193 ms 158-192 ms +2 ms (+1%) within noise
supabase init init 153-187 ms 157-176 ms -1 ms (0%) within noise
supabase migration new bench migration-new 158-213 ms 157-187 ms +3 ms (+2%) within noise
supabase gen types --lang typescript --project-id replayfixtureprojref gen-types 180-224 ms 182-220 ms +8 ms (+4%) within noise
supabase projects list projects-list 174-239 ms 179-198 ms +4 ms (+2%) within noise
supabase functions list --project-ref replayfixtureprojref functions-list 174-213 ms 174-215 ms -11 ms (-6%) within noise

API calls per command

Command Workload Before After Endpoint changes
supabase --version version 0 0
supabase --help help 0 0
supabase init init 0 0
supabase migration new bench migration-new 0 0
supabase gen types --lang typescript --project-id replayfixtureprojref gen-types 2 2
supabase projects list projects-list 1 1
supabase functions list --project-ref replayfixtureprojref functions-list 2 2

Slowest phases

No command moved beyond noise, so there are no phase deltas.

Workflow run

Comment /bench to run the lightweight benchmark (this comment) again on the latest commit, or /bench full to also run the integration-heavy benchmark, which posts in its own comment.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.
jgoux added 2 commits November 5, 2026 16:33
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.
@jgoux
jgoux added this pull request to the merge queue Nov 6, 2026
Merged via the queue into develop with commit a3e4904 Nov 6, 2026
49 checks passed
@jgoux
jgoux deleted the fix/db-sslmode-prefer-fallback branch November 6, 2026 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

failed to connect to postgres since 2.40.4, works on --debug

2 participants