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 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-22 19:02:11 +00:00
parent 2a9980076d
commit 800ac6e6c2
2 changed files with 110 additions and 4 deletions

View File

@@ -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) {

View File

@@ -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();
});
});