Repository navigation
fix(cli): pin the Supabase CA on every linked database connection - #7518
Conversation
Only gen types verified hosted servers against the bundled Supabase root CA. The shared linked resolver now pins it on the direct host and every pooler path, including the temp-role probe, for the supabase, supabase-staging and snap profiles. An explicit sslmode or sslrootcert from any source wins.
CLI benchmark (Lightweight Commands): no changes foundCommit
API calls: unchanged for all 7 commands. Binary size: 134.7 MB, down 8.0 KB from 134.7 MB. 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 after reading the checked-out code and trusted compatibility contracts. Corrected the breaking-change finding's location and scope: non-TypeScript project generation changes its TLS environment handling; preview direct connections still ignore PG* settings. Raised the certificate-verification regression to critical under the supplied severity definitions. Codex reported no findings. No fundamental conflict with pending next-branch work was found. Tests were not run.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🔴 CRITICAL | apps/cli/src/commands/gen/types/types.handler.ts:695 |
security |
claude | gen types --db-url silently loses certificate verification when a YAML profile with a custom name points to Supabase hosts. Previously, matching the profile's project_host or pooler_host caused CA pinning. The new helper skips names outside its built-in allowlist, leaving TLS unverified and permitting server impersonation. |
| 🟠 MAJOR | apps/cli/src/command-internal/supabase-tls.ts:37 |
breaking-change |
claude | Released connection behavior changes: linked commands using built-in hosted profiles now default to CA verification, rejecting certificate chains that previously worked with unverified TLS. Non-TypeScript gen types against a project ref now honors PGSSLMODE/PGSSLROOTCERT instead of overriding them with its pinned mode and CA. For example, PGSSLMODE=disable now selects plaintext and fails against an SSL-enforced project. Please use a fix(cli)!: title, add a BREAKING CHANGE: line to the PR description, and retarget the PR from develop to next. |
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.
- stop the temp-role retries at once on a rejected server certificate - add a CA-verification hint (PGSSLROOTCERT / PGSSLMODE=require), shown on the temp-role error - keep retrying auth and dial failures so propagation and ban detection are unchanged - normalize host case and trailing dot before matching direct hosts in pinSupabaseTls - document the built-in-profile restriction and the preview-branch pooler PG* handling - wrap an over-long JSDoc line in refuse-invalid-db-hosts
… the IP The temp-role probe retried for about 2.5 minutes when the pooler rejected the caller's IP (Address not in tenant allow_list), which no retry can fix. It now fails on the first attempt with the network-restrictions suggestion.
…supabase-ca # Conflicts: # apps/cli/src/commands/gen/types/SIDE_EFFECTS.md
7ttp
left a comment
There was a problem hiding this comment.
was poking around this one so figured i'd jump in, tested it and it works nicely! :D
left a few smol comments inline:
A root CA set without sslmode on a hosted host now keeps sslmode=require, so the driver verifies the chain against it instead of skipping verification. The bootstrap IPv6 fallback connection gets the pin too. The temp-role probe stops retrying on a hostname mismatch (ERR_TLS_CERT_ALTNAME_INVALID), no longer treats OUT_OF_MEM as a certificate rejection, and its network restrictions hint links the dashboard's database settings.
…supabase-ca # Conflicts: # apps/cli/src/commands/gen/types/SIDE_EFFECTS.md # apps/cli/src/commands/gen/types/types.handler.ts
Summary
Only
gen typeschecked hosted database servers against the bundled Supabase root CA. Every other command that connects to a linked project usedsslmodeunset, which means no certificate verification. That coversdb push/pull/dump/diff/reset/lint/advisors/query,migration *andinspect *.The shared linked resolver now pins the CA. It uses
sslmode=requireplus the inline bundle, which the driver verifies asverify-ca.Where the pin applies:
db.<ref>host;bootstrappush when it falls back to the direct host.When it applies:
supabase,supabase-stagingandsnap, and only on that profile's own direct-host or pooler domains.supabase-localand YAML profiles are never pinned.sslmodefrom any source (DSN,pg_service.conf,PGSSLMODE) is used as is, includingdisableandverify-full. A root CA set withoutsslmode(PGSSLROOTCERT, DSN or service file) replaces the bundled CA and keepsrequire, so the chain is verified against it.gen types --db-urlhonors the DSN as is.pg_proveintest dband the pgAdmin and migra fallbacks indb diff.Behavior changes:
gen typesalready verified it. A certificate that does not chain to the Supabase CA, for example from a TLS-terminating proxy in front of Postgres, now fails. SetPGSSLROOTCERTto a CA bundle that trusts it, orPGSSLMODE=requireto skip verification as before.SUPABASE_DB_PASSWORD) or when network restrictions reject the caller's IP (Address not in tenant allow_list, with a link to the dashboard's database settings), instead of retrying for about 2.5 minutes. Auth and dial failures keep retrying, since a network ban shows up as a dial failure.gen typeson a linked project or preview branch no longer overrides an explicitPGSSLMODE/PGSSLROOTCERT.db dumppassesPGSSLMODE=requiretopg_dump, without the inline CA, so the dump requires TLS but does not verify the chain. This is documented in itsSIDE_EFFECTS.md.Cleanup: the CA templates move to
command-internal/, and migra's duplicatePG_DELTA_CA_BUNDLEnow uses the same bundle.The pooler certificates on
aws-0-us-east-1.pooler.supabase.com,aws-1-eu-west-1.pooler.supabase.comandaws-0-us-east-1.pooler.snapcloud.cochain toSupabase Root 2021 CAin the bundle (openssl s_client -starttls postgres). For direct hosts,gen typesalready pinned them unconditionally.Verification
bun --bun vitest run --project integrationondb-config.integration.test.ts,gen/types/types.integration.test.tsanddb/diff: 417 passed. New tests cover:verify-full,disable,PGSSLROOTCERT, the service file and the pooler URLsslmode;supabase-local, a YAML profile and a foreign host;bootstrap: 471 passed;supabase-tlsandconnect-errorsunit tests: 69 passed. Thebootstrapdirect-host fallback test fails without the pin.pnpm run types:check,oxfmtandoxlintare clean.