Files
bun.sh/test/regression/issue/21792.test.ts
robobun dd7a639a6f fix(serve): correct TLS array validation for SNI (#21796)
## Summary

Fixes a prerequisite issue in #21792 where `Bun.serve()` incorrectly
rejected TLS arrays with exactly 1 object.

The original issue reports a WebSocket crash with multiple TLS configs,
but users first encounter this validation bug that prevents
single-element TLS arrays from working at all.

## Root Cause

The bug was in `ServerConfig.zig:918` where the condition checked for
exactly 1 element and threw an error:

```zig
if (value_iter.len == 1) {
    return global.throwInvalidArguments("tls option expects at least 1 tls object", .{});
}
```

This prevented users from using the syntax: `tls: [{ cert, key,
serverName }]`

## Fix

Updated the validation logic to:
- Empty TLS arrays are ignored (treated as no TLS)  
- Single-element TLS arrays work correctly for SNI
- Multi-element TLS arrays continue to work as before

```zig
if (value_iter.len == 0) {
    // Empty TLS array means no TLS - this is valid
} else {
    // Process the TLS configs...
}
```

## Testing

-  All existing SSL tests still pass (16/16)
-  New comprehensive regression test with 7 test cases 
-  Tests cover empty arrays, single configs, multiple configs, and
error cases

## Note

This fix addresses the validation issue that prevents users from
reaching the deeper WebSocket SNI crash mentioned in #21792. The crash
itself may require additional investigation, but this fix resolves the
immediate blocker that users encounter first.

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-15 21:25:54 -07:00

103 lines
3.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { readFileSync } from "fs";
import { join } from "path";
// This test verifies the fix for GitHub issue #21792:
// SNI TLS array handling was incorrectly rejecting arrays with exactly 1 TLS config
describe("SNI TLS array handling (issue #21792)", () => {
// Use existing test certificates from jsonwebtoken tests
const certDir = join(import.meta.dir, "../../js/third_party/jsonwebtoken");
const crtA = readFileSync(join(certDir, "pub.pem"), "utf8");
const keyA = readFileSync(join(certDir, "priv.pem"), "utf8");
const crtB = crtA; // Reuse same cert for second test server
const keyB = keyA;
test("should accept empty TLS array (no TLS)", () => {
// Empty array should be treated as no TLS
using server = Bun.serve({
port: 0,
tls: [],
fetch: () => new Response("Hello"),
development: true,
});
expect(server.url.toString()).toStartWith("http://"); // HTTP, not HTTPS
});
test("should accept single TLS config in array", () => {
// This was the bug: single TLS config in array was incorrectly rejected
using server = Bun.serve({
port: 0,
tls: [{ cert: crtA, key: keyA, serverName: "serverA.com" }],
fetch: () => new Response("Hello from serverA"),
development: true,
});
expect(server.url.toString()).toStartWith("https://");
});
test("should accept multiple TLS configs for SNI", () => {
using server = Bun.serve({
port: 0,
tls: [
{ cert: crtA, key: keyA, serverName: "serverA.com" },
{ cert: crtB, key: keyB, serverName: "serverB.com" },
],
fetch: request => {
const host = request.headers.get("host") || "unknown";
return new Response(`Hello from ${host}`);
},
development: true,
});
expect(server.url.toString()).toStartWith("https://");
});
test("should reject TLS array with missing serverName for SNI configs", () => {
expect(() => {
Bun.serve({
port: 0,
tls: [
{ cert: crtA, key: keyA, serverName: "serverA.com" },
{ cert: crtB, key: keyB }, // Missing serverName
],
fetch: () => new Response("Hello"),
development: true,
});
}).toThrow("SNI tls object must have a serverName");
});
test("should reject TLS array with empty serverName for SNI configs", () => {
expect(() => {
Bun.serve({
port: 0,
tls: [
{ cert: crtA, key: keyA, serverName: "serverA.com" },
{ cert: crtB, key: keyB, serverName: "" }, // Empty serverName
],
fetch: () => new Response("Hello"),
development: true,
});
}).toThrow("SNI tls object must have a serverName");
});
test("should accept single TLS config without serverName when alone", () => {
// When there's only one TLS config in the array, serverName is optional
using server = Bun.serve({
port: 0,
tls: [{ cert: crtA, key: keyA }], // No serverName - should work for single config
fetch: () => new Response("Hello from default"),
development: true,
});
expect(server.url.toString()).toStartWith("https://");
});
test("should support traditional non-array TLS config", () => {
// Traditional single TLS config (not in array) should still work
using server = Bun.serve({
port: 0,
tls: { cert: crtA, key: keyA },
fetch: () => new Response("Hello traditional"),
development: true,
});
expect(server.url.toString()).toStartWith("https://");
});
});