From 800ac6e6c23efbe3ce7d41f95cd988916a6ed976 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 22 Jan 2026 19:02:11 +0000 Subject: [PATCH] fix(sql): auto-populate TLS serverName when using tls: true option When using Bun.SQL with individual connection options and tls: true, the SNI hostname was not sent during the TLS handshake. This caused connections to fail on servers that require SNI (like Neon). The issue was that the code to auto-populate tls.serverName from the hostname ran before sslMode was promoted from disable to prefer. This fix swaps the order of these two operations so sslMode is promoted first, allowing serverName to be properly set. Fixes #26369 Co-Authored-By: Claude Opus 4.5 --- src/js/internal/sql/shared.ts | 8 +-- test/regression/issue/26369.test.ts | 106 ++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 test/regression/issue/26369.test.ts diff --git a/src/js/internal/sql/shared.ts b/src/js/internal/sql/shared.ts index fc484b00c7..b58eb5d2d8 100644 --- a/src/js/internal/sql/shared.ts +++ b/src/js/internal/sql/shared.ts @@ -838,6 +838,10 @@ function parseOptions( } } + if (tls && sslMode === SSLMode.disable) { + sslMode = SSLMode.prefer; + } + if (sslMode !== SSLMode.disable && !tls?.serverName) { if (hostname) { tls = { ...tls, serverName: hostname }; @@ -846,10 +850,6 @@ function parseOptions( } } - if (tls && sslMode === SSLMode.disable) { - sslMode = SSLMode.prefer; - } - port = Number(port); if (!Number.isSafeInteger(port) || port < 1 || port > 65535) { diff --git a/test/regression/issue/26369.test.ts b/test/regression/issue/26369.test.ts new file mode 100644 index 0000000000..4263bd704e --- /dev/null +++ b/test/regression/issue/26369.test.ts @@ -0,0 +1,106 @@ +import { SQL } from "bun"; +import { describe, expect, test } from "bun:test"; + +describe("Issue #26369 - SNI hostname not sent when using tls: true with individual connection options", () => { + test("tls: true with host option should auto-populate serverName", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: true, + adapter: "postgres", + }); + + // Verify serverName is automatically set from the hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("tls: true with hostname option should auto-populate serverName", () => { + const sql = new SQL({ + hostname: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: true, + adapter: "postgres", + }); + + // Verify serverName is automatically set from the hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("tls with explicit serverName should preserve it", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: { serverName: "custom-server-name.example.com" }, + adapter: "postgres", + }); + + // Verify explicit serverName is preserved + expect(sql.options.tls).toEqual({ serverName: "custom-server-name.example.com" }); + + sql.close(); + }); + + test("URL with sslmode=require should auto-populate serverName", () => { + const sql = new SQL("postgresql://user:pass@example-host.neon.tech/db?sslmode=require"); + + // Verify serverName is automatically set from the URL hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("URL without sslmode but with tls: true option should auto-populate serverName", () => { + const sql = new SQL("postgresql://user:pass@example-host.neon.tech/db", { + tls: true, + }); + + // Verify serverName is automatically set from the URL hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("tls object with other properties should also get serverName", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: { rejectUnauthorized: false }, + adapter: "postgres", + }); + + // Verify serverName is added alongside existing tls properties + expect(sql.options.tls).toEqual({ + rejectUnauthorized: false, + serverName: "example-host.neon.tech", + }); + + sql.close(); + }); + + test("no tls option should not set tls in options", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + adapter: "postgres", + }); + + // Verify tls is not set when not explicitly enabled + expect(sql.options.tls).toBeUndefined(); + + sql.close(); + }); +});