Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
77ec53a
node:tls: send a client's FIN after the first step of its handshake (…
robobun Oct 3, 2026
bb35d1b
clone() of a Bun.file() body no longer freezes what the Bun.file() an…
dylan-conway Oct 3, 2026
0ba17d5
dns(macOS): use DNSServiceQueryRecord so split DNS can fail over (#44…
Jarred-Sumner Oct 3, 2026
aa83076
node:buffer: reject a non-string value in the per-encoding write meth…
robobun Oct 3, 2026
b73ae47
Bump mimalloc: faster heaps, less purge churn; release memory during …
Jarred-Sumner Oct 4, 2026
1878660
Bump WebKit: idle JSC threads and Atomics.wait release their free mem…
Jarred-Sumner Oct 4, 2026
b335bbe
fix(module): preserve package interop in async CJS imports
steipete Oct 4, 2026
9c3439a
Bump mimalloc: upstream v3.5.3 (#44575)
Jarred-Sumner Oct 4, 2026
c7b06d9
docker: inline the release signing key instead of asking a keyserver …
Jarred-Sumner Oct 4, 2026
d492876
fetch: set up TLS once per connection, not once per request (#44507)
robobun Oct 5, 2026
a7c2bb6
fix(module): pair namespace markers with the OpenClaw engine
steipete Oct 5, 2026
6e995e9
chore: merge upstream Bun and pin rebased OpenClaw WebKit
steipete Oct 5, 2026
1f976ed
fix(module): merge package interop prerequisite for namespace sync
steipete Oct 5, 2026
651a69b
chore: merge latest upstream Bun nightly through d4928764
steipete Oct 5, 2026
951a8b5
sql(postgres): keep a failed query in flight until its ReadyForQuery,…
robobun Oct 5, 2026
9bd19c9
sql(mysql): reject only the query whose row the client cannot decode …
robobun Oct 5, 2026
b983eca
test: join fetch leak fixture lifetime before disposal
steipete Oct 5, 2026
6b473b5
test: synchronize client readiness before TLS renegotiation
steipete Oct 5, 2026
65a7af8
chore: merge latest upstream Bun nightly through 9bd19c98
steipete Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/UPSTREAM_SYNC.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,20 @@ A landed sync reaches OpenClaw through a fork release. Dispatching the release
workflow on `automation/sync-upstream` without a tag builds and smoke-tests every
release target as a dry run; the release cadence and security-patch policy are in
[OPENCLAW_RELEASE.md](OPENCLAW_RELEASE.md#upstream-sync-and-security-patches).

## October 2026 engine and namespace update

The sync through upstream Bun `9bd19c98eacc01530a4e7609bc427abffa87d77e`
targets upstream WebKit `5718a6ec579b98362ea7276a426deedcc6281ef5`. The fork
consumes immutable OpenClaw WebKit
[`42ab38d705d4838748ccee77e7deb0e4e35515ee`](https://github.com/openclaw/WebKit/releases/tag/autobuild-42ab38d705d4838748ccee77e7deb0e4e35515ee)
through the complete published manifest in
`scripts/build/deps/webkit-artifacts.json`. Every archive is verified before
extraction, with no upstream-artifact fallback.

This engine provides upstream's idle compiler-thread and `Atomics.wait` memory
release. Its namespace facade API also requires the paired Bun integration from
[#106](https://github.com/openclaw/bun/pull/106): import namespaces remain
unmarked, while `require(esm)` gets an own marker and live exports. The manifest
and source counterpart land together. Roll back the matched Bun source and
engine pin together; changing only the manifest is not a compatible rollback.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,14 @@
- Fix Intl.Segments.containing() at both halves of surrogate pairs with the pinned OpenClaw WebKit; cover grapheme, word, and sentence boundaries in both lookup directions. Ports [oven-sh/WebKit#753](https://github.com/oven-sh/WebKit/pull/753). Thanks @robobun!

- Reduce kernel CPU during cold module imports on macOS by serializing transpiler-cache writes while preserving parallel parsing and cache reads.

- Keep ESM namespaces free of inherited `__esModule` markers and preserve the own marker and live exports for `require(esm)`, fixing Vite/tsx namespace interop. Adapts [oven-sh/bun#33894](https://github.com/oven-sh/bun/pull/33894) and [oven-sh/WebKit#279](https://github.com/oven-sh/WebKit/pull/279). Thanks @robobun!


- Sync oven-sh/bun through `c7b06d94bac19817ba34b6677bb1099fb4f6d2be`, preserving fork fixes and incorporating TLS handshake shutdown, macOS split-DNS failover, file-body cloning, Buffer write validation, mimalloc 3.5.3 and idle-memory release.
- Pin immutable [OpenClaw WebKit `42ab38d705`](https://github.com/openclaw/WebKit/releases/tag/autobuild-42ab38d705d4838748ccee77e7deb0e4e35515ee) by archive checksum, together with the required namespace facade integration from #106; retain fail-closed artifact selection.

- Preserve package-scope CommonJS interop in async imports without replacing the file's parser format. Adapts [oven-sh/bun#40940](https://github.com/oven-sh/bun/pull/40940). Thanks @robobun!

- Sync upstream nightly through `d4928764f23213ecf3cd61fa0b5b4a44369a5096`, configuring fetch TLS once per connection so pooled sockets can be reused during renegotiation without repeating session setup.
- Sync upstream nightly through `9bd19c98eacc01530a4e7609bc427abffa87d77e`, preserving PostgreSQL query ordering through errors and limiting MySQL row-decoding failures to the affected query.
32 changes: 22 additions & 10 deletions dockerhub/alpine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ FROM alpine:3.24 AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest

RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \
# The signing key is inlined so that a keyserver outage cannot fail the build.
# --assert-signer fails if --output is the signature's name without ".asc", hence ".verified.txt".
RUN apk --no-cache add ca-certificates curl gpg gpg-agent unzip \
&& arch="$(apk --print-arch)" \
&& case "${arch##*-}" in \
x86_64) build="x64-musl-baseline";; \
Expand All @@ -25,23 +27,33 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \
--compressed \
--retry 5 \
|| (echo "error: failed to download: $tag" && exit 1) \
&& for key in \
"F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \
; do \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \
|| gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \
done \
&& printf '%s\n' \
'-----BEGIN PGP PUBLIC KEY BLOCK-----' \
'' \
'mDMEY9GQFhYJKwYBBAHaRw8BAQdAkppAqaXl0RkROz6NfdvlwYd2UuUVfHLk2NNY' \
'IzEdnT+0GVJvYm9idW4gPHJvYm9idW5Ab3Zlbi5zaD6IkwQTFgoAOxYhBPPcwIqF' \
'csB0mz4YiI6rTUCnsitZBQJj0ZAWAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4H' \
'AheAAAoJEI6rTUCnsitZ96UBAMvwCLD6Ud1RZkpvvnGUU+idHt2hcNmYU2d0XxDI' \
'HQ0rAQCA9VFOtjZefQkfhDzgIgnEEdSFXaMiyY+D+LP8awNMCrg4BGPRkBYSCisG' \
'AQQBl1UBBQEBB0BpfePYSOEx8PihYkNXjlK5YT89CGHjEK5etleaB9i6OwMBCAeI' \
'eAQYFgoAIBYhBPPcwIqFcsB0mz4YiI6rTUCnsitZBQJj0ZAWAhsMAAoJEI6rTUCn' \
'sitZvhAA/j4SQxOCLheRG86A2181WAP4qLS1qxSw+fCf28DgiPfbAQCL0kcel+M9' \
'qbRIlMnwn6TwlQgN9w1qqlSnA9CbKXT9Aw==' \
'=dGV6' \
'-----END PGP PUBLIC KEY BLOCK-----' \
| gpg --batch --import \
&& curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \
-fsSLO \
--compressed \
--retry 5 \
&& gpg --batch --verify --output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpg --batch --assert-signer F3DCC08A8572C0749B3E18888EAB4D40A7B22B59 \
--verify --output SHASUMS256.verified.txt SHASUMS256.txt.asc \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& grep " bun-linux-$build.zip\$" SHASUMS256.txt | sha256sum -c - \
&& grep " bun-linux-$build.zip\$" SHASUMS256.verified.txt | sha256sum -c - \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& unzip "bun-linux-$build.zip" \
&& mv "bun-linux-$build/bun" /usr/local/bin/bun \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.verified.txt \
&& chmod +x /usr/local/bin/bun

FROM alpine:3.24
Expand Down
31 changes: 21 additions & 10 deletions dockerhub/debian-slim/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest

# The signing key is inlined so that a keyserver outage cannot fail the build.
# --assert-signer fails if --output is the signature's name without ".asc", hence ".verified.txt".
RUN apt-get update -qq \
&& apt-get install -qq --no-install-recommends \
ca-certificates \
curl \
dirmngr \
gpg \
gpg-agent \
unzip \
Expand All @@ -34,23 +35,33 @@ RUN apt-get update -qq \
--compressed \
--retry 5 \
|| (echo "error: failed to download: $tag" && exit 1) \
&& for key in \
"F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \
; do \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \
|| gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \
done \
&& printf '%s\n' \
'-----BEGIN PGP PUBLIC KEY BLOCK-----' \
'' \
'mDMEY9GQFhYJKwYBBAHaRw8BAQdAkppAqaXl0RkROz6NfdvlwYd2UuUVfHLk2NNY' \
'IzEdnT+0GVJvYm9idW4gPHJvYm9idW5Ab3Zlbi5zaD6IkwQTFgoAOxYhBPPcwIqF' \
'csB0mz4YiI6rTUCnsitZBQJj0ZAWAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4H' \
'AheAAAoJEI6rTUCnsitZ96UBAMvwCLD6Ud1RZkpvvnGUU+idHt2hcNmYU2d0XxDI' \
'HQ0rAQCA9VFOtjZefQkfhDzgIgnEEdSFXaMiyY+D+LP8awNMCrg4BGPRkBYSCisG' \
'AQQBl1UBBQEBB0BpfePYSOEx8PihYkNXjlK5YT89CGHjEK5etleaB9i6OwMBCAeI' \
'eAQYFgoAIBYhBPPcwIqFcsB0mz4YiI6rTUCnsitZBQJj0ZAWAhsMAAoJEI6rTUCn' \
'sitZvhAA/j4SQxOCLheRG86A2181WAP4qLS1qxSw+fCf28DgiPfbAQCL0kcel+M9' \
'qbRIlMnwn6TwlQgN9w1qqlSnA9CbKXT9Aw==' \
'=dGV6' \
'-----END PGP PUBLIC KEY BLOCK-----' \
| gpg --batch --import \
&& curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \
-fsSLO \
--compressed \
--retry 5 \
&& gpg --batch --verify --output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpg --batch --assert-signer F3DCC08A8572C0749B3E18888EAB4D40A7B22B59 \
--verify --output SHASUMS256.verified.txt SHASUMS256.txt.asc \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& grep " bun-linux-$build.zip\$" SHASUMS256.txt | sha256sum -c - \
&& grep " bun-linux-$build.zip\$" SHASUMS256.verified.txt | sha256sum -c - \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& unzip "bun-linux-$build.zip" \
&& mv "bun-linux-$build/bun" /usr/local/bin/bun \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.verified.txt \
&& chmod +x /usr/local/bin/bun \
&& which bun \
&& bun --version
Expand Down
32 changes: 22 additions & 10 deletions dockerhub/debian/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest

# The signing key is inlined so that a keyserver outage cannot fail the build.
# --assert-signer fails if --output is the signature's name without ".asc", hence ".verified.txt".

# Node.js includes python3 for node-gyp, see https://github.com/oven-sh/bun/issues/9807
# Though, not on slim and alpine images.
RUN apt-get update -qq \
&& apt-get install -qq --no-install-recommends \
ca-certificates \
curl \
dirmngr \
gpg \
gpg-agent \
unzip \
Expand Down Expand Up @@ -37,23 +39,33 @@ RUN apt-get update -qq \
--compressed \
--retry 5 \
|| (echo "error: failed to download: $tag" && exit 1) \
&& for key in \
"F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \
; do \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \
|| gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \
done \
&& printf '%s\n' \
'-----BEGIN PGP PUBLIC KEY BLOCK-----' \
'' \
'mDMEY9GQFhYJKwYBBAHaRw8BAQdAkppAqaXl0RkROz6NfdvlwYd2UuUVfHLk2NNY' \
'IzEdnT+0GVJvYm9idW4gPHJvYm9idW5Ab3Zlbi5zaD6IkwQTFgoAOxYhBPPcwIqF' \
'csB0mz4YiI6rTUCnsitZBQJj0ZAWAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4H' \
'AheAAAoJEI6rTUCnsitZ96UBAMvwCLD6Ud1RZkpvvnGUU+idHt2hcNmYU2d0XxDI' \
'HQ0rAQCA9VFOtjZefQkfhDzgIgnEEdSFXaMiyY+D+LP8awNMCrg4BGPRkBYSCisG' \
'AQQBl1UBBQEBB0BpfePYSOEx8PihYkNXjlK5YT89CGHjEK5etleaB9i6OwMBCAeI' \
'eAQYFgoAIBYhBPPcwIqFcsB0mz4YiI6rTUCnsitZBQJj0ZAWAhsMAAoJEI6rTUCn' \
'sitZvhAA/j4SQxOCLheRG86A2181WAP4qLS1qxSw+fCf28DgiPfbAQCL0kcel+M9' \
'qbRIlMnwn6TwlQgN9w1qqlSnA9CbKXT9Aw==' \
'=dGV6' \
'-----END PGP PUBLIC KEY BLOCK-----' \
| gpg --batch --import \
&& curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \
-fsSLO \
--compressed \
--retry 5 \
&& gpg --batch --verify --output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpg --batch --assert-signer F3DCC08A8572C0749B3E18888EAB4D40A7B22B59 \
--verify --output SHASUMS256.verified.txt SHASUMS256.txt.asc \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& grep " bun-linux-$build.zip\$" SHASUMS256.txt | sha256sum -c - \
&& grep " bun-linux-$build.zip\$" SHASUMS256.verified.txt | sha256sum -c - \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& unzip "bun-linux-$build.zip" \
&& mv "bun-linux-$build/bun" /usr/local/bin/bun \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.verified.txt \
&& chmod +x /usr/local/bin/bun

FROM debian:trixie
Expand Down
31 changes: 21 additions & 10 deletions dockerhub/distroless/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest

# The signing key is inlined so that a keyserver outage cannot fail the build.
# --assert-signer fails if --output is the signature's name without ".asc", hence ".verified.txt".
RUN apt-get update -qq \
&& apt-get install -qq --no-install-recommends \
ca-certificates \
curl \
dirmngr \
gpg \
gpg-agent \
unzip \
Expand All @@ -34,23 +35,33 @@ RUN apt-get update -qq \
--compressed \
--retry 5 \
|| (echo "error: failed to download: $tag" && exit 1) \
&& for key in \
"F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \
; do \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \
|| gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \
done \
&& printf '%s\n' \
'-----BEGIN PGP PUBLIC KEY BLOCK-----' \
'' \
'mDMEY9GQFhYJKwYBBAHaRw8BAQdAkppAqaXl0RkROz6NfdvlwYd2UuUVfHLk2NNY' \
'IzEdnT+0GVJvYm9idW4gPHJvYm9idW5Ab3Zlbi5zaD6IkwQTFgoAOxYhBPPcwIqF' \
'csB0mz4YiI6rTUCnsitZBQJj0ZAWAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4H' \
'AheAAAoJEI6rTUCnsitZ96UBAMvwCLD6Ud1RZkpvvnGUU+idHt2hcNmYU2d0XxDI' \
'HQ0rAQCA9VFOtjZefQkfhDzgIgnEEdSFXaMiyY+D+LP8awNMCrg4BGPRkBYSCisG' \
'AQQBl1UBBQEBB0BpfePYSOEx8PihYkNXjlK5YT89CGHjEK5etleaB9i6OwMBCAeI' \
'eAQYFgoAIBYhBPPcwIqFcsB0mz4YiI6rTUCnsitZBQJj0ZAWAhsMAAoJEI6rTUCn' \
'sitZvhAA/j4SQxOCLheRG86A2181WAP4qLS1qxSw+fCf28DgiPfbAQCL0kcel+M9' \
'qbRIlMnwn6TwlQgN9w1qqlSnA9CbKXT9Aw==' \
'=dGV6' \
'-----END PGP PUBLIC KEY BLOCK-----' \
| gpg --batch --import \
&& curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \
-fsSLO \
--compressed \
--retry 5 \
&& gpg --batch --verify --output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpg --batch --assert-signer F3DCC08A8572C0749B3E18888EAB4D40A7B22B59 \
--verify --output SHASUMS256.verified.txt SHASUMS256.txt.asc \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& grep " bun-linux-$build.zip\$" SHASUMS256.txt | sha256sum -c - \
&& grep " bun-linux-$build.zip\$" SHASUMS256.verified.txt | sha256sum -c - \
|| (echo "error: failed to verify: $tag" && exit 1) \
&& unzip "bun-linux-$build.zip" \
&& mv "bun-linux-$build/bun" /usr/local/bin/bun \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.verified.txt \
&& chmod +x /usr/local/bin/bun \
&& which bun \
&& bun --version
Expand Down
2 changes: 2 additions & 0 deletions docs/runtime/module-resolution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ import Stuff from "./my-commonjs.cjs";
const myStuff = require("./my-commonjs.cjs");
```

When `require()` loads an ES module with a default export and no explicit `__esModule` export, it returns a namespace with an own, enumerable `__esModule: true` property. Its exports remain live. Importing the same module returns the original namespace without that added marker. Both namespace objects have a null prototype and reject property assignments.

### Top level await

The only exception to this rule is top-level await. You can't `require()` a file that uses top-level await, since the `require()` function is inherently synchronous.
Expand Down
4 changes: 4 additions & 0 deletions docs/runtime/nodejs-compat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Stdin and eval scripts report malformed ancestor package metadata only when an i

The OpenClaw fork reports missing runtime imports without downloading npm packages. This default covers scripts, workers, and child processes running the fork. You can opt in with `--install=auto|fallback|force` or an explicit bunfig `install.auto` setting. Package-manager commands (`bun install`, `bun add`, `bun x`, and `bunx`) are unchanged. See [Auto-install](/runtime/auto-install).

Module namespaces have a null prototype. `require(esm)` adds an own enumerable `__esModule` marker only for modules with a default export and no explicit marker, without modifying the imported namespace. CommonJS namespaces preserve explicit markers and expose the original exports as `module.exports`.

## Built-in Node.js modules

`Error.prepareStackTrace` supports deletion, descriptor restoration, and accessor replacement. Nested stack formatting and throwing formatters leave later captures usable.
Expand Down Expand Up @@ -177,6 +179,8 @@ Unreadable selected package metadata also throws `ERR_INVALID_PACKAGE_CONFIG`, r

`import.meta.resolve()` validates the package scope of existing `.js`, `.ts`, and extensionless files. Missing-file URLs and `require.resolve()` defer that scope validation. Loading an already-resolved CommonJS file through the ESM loader preserves the original resolution mode.

Async imports of CommonJS files preserve package-scope interop. A `.cjs` file inside a `"type": "module"` package keeps `module.exports` as its default export and exposes its `__esModule` property, while the file still executes as CommonJS.

`registerHooks()` supports synchronous resolve/load chaining, source transformation, and deregistration. `nextResolve` honors parent URLs and custom conditions, and hooks preserve query/fragment module identities. Dynamic-import attributes, including attributes returned by resolve hooks, reach the matching load hook.

Runtime `Bun.plugin()` hooks retry missing native resolutions with the filesystem refresh policy used by `registerHooks()`. Delegated resolution can load package-import targets created after their directory was cached, including files created inside an `onResolve` callback that returns `undefined`.
Expand Down
16 changes: 16 additions & 0 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,10 @@ void us_socket_set_inline_reject(struct us_socket_t *s) {
SSL_set_verify(s_ssl(s), SSL_VERIFY_PEER, us_inline_reject_verify_callback);
}

void us_socket_set_first_flight_before_fin(struct us_socket_t *s) {
s->ssl_first_flight_before_fin = 1;
}

/* Drop the strdup'd passphrase. Called as soon as private-key load completes
* (the only consumer of the passwd_cb), so the secret never outlives ctx
* construction and SSL_CTX_free() is sufficient on every later path. Also
Expand Down Expand Up @@ -1778,6 +1782,8 @@ void us_internal_ssl_attach(struct us_socket_t *s, SSL_CTX *ctx,
s->ssl_in_use = 0;
s->ssl_pending_detach = 0;
s->ssl_pending_close_code = 0;
s->ssl_first_flight_before_fin = 0;
s->ssl_shutdown_after_first_flight = 0;
s->ssl_is_server = is_client ? 0 : 1;
s->ssl_inline_reject = 0;
s->ssl_verify_failed = 0;
Expand Down Expand Up @@ -2291,6 +2297,12 @@ struct us_socket_t *us_internal_ssl_on_open(struct us_socket_t *s, int is_client
/* Kick the handshake immediately — some peers stall waiting for ClientHello. */
ssl_set_loop_data(result);
ssl_update_handshake(result, 1);
if (ssl_gone(result)) return result;
result->ssl_first_flight_before_fin = 0;
if (result->ssl_shutdown_after_first_flight) {
result->ssl_shutdown_after_first_flight = 0;
us_internal_ssl_shutdown(result);
}
return result;
}

Expand Down Expand Up @@ -2882,6 +2894,10 @@ int us_internal_ssl_writev(struct us_socket_t *s, const struct us_iovec_t *iov,

void us_internal_ssl_shutdown(struct us_socket_t *s) {
if (us_socket_is_closed(s) || us_internal_ssl_is_shut_down(s)) return;
if (s->ssl_first_flight_before_fin) {
s->ssl_shutdown_after_first_flight = 1;
return;
}

/* Spilled ciphertext is data the layers above already count as written;
* a FIN/close_notify now would cut it off. Finish the shutdown from the
Expand Down
4 changes: 4 additions & 0 deletions packages/bun-usockets/src/internal/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ struct us_socket_t {
* inside a handshake callback must still RST, not FIN, when it is finally
* performed). */
unsigned char ssl_pending_close_code : 2;
/* us_socket_set_first_flight_before_fin: the first handshake step is still due, and a shutdown waits for it. */
unsigned char ssl_first_flight_before_fin : 1;
/* us_internal_ssl_shutdown held its FIN back for that step. */
unsigned char ssl_shutdown_after_first_flight : 1;
/* Consecutive send() failures with an errno that is neither
* would-block/transient nor a known peer-gone error (see
* us_socket_write_check_error). Reset by any send that makes progress.
Expand Down
2 changes: 2 additions & 0 deletions packages/bun-usockets/src/libusockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ void us_socket_start_tls_handshake(us_socket_r s) nonnull_fn_decl;
* server that fails verification. Must run before the handshake is driven
* (on_open, or between adopt_tls and start_tls_handshake). No-op otherwise. */
void us_socket_set_inline_reject(us_socket_r s) nonnull_fn_decl;
/* Call it from on_open. A shutdown before the first handshake step sends its FIN after that step. */
void us_socket_set_first_flight_before_fin(us_socket_r s) nonnull_fn_decl;

/* ── Listen ───────────────────────────────────────────────────────────────
* The listener owns: an embedded group for accepted sockets, the SSL_CTX
Expand Down
Loading
Loading