Closed
Description
Version: v24.22.2 (also v16βv24 tested); not reproducible on v26.13.0
Platform: macOS arm64 (Darwin 25.6.0); OpenSSL 3.5.9
What steps will reproduce the bug?
// repro.mjs β run: node repro.mjs
import tls from 'node:tls';
import { webcrypto } from 'node:crypto';
const { subtle } = webcrypto;
const key = await subtle.importKey('raw', new Uint8Array(32), 'AES-GCM', false, ['encrypt', 'decrypt']);
const iv = new Uint8Array(12);
const ct = await subtle.encrypt({ name: 'AES-GCM', iv }, key, new Uint8Array([1, 2, 3]));
// A session buffer in the v24 serialized format whose DER payload is not a valid SSL_SESSION.
const session = Buffer.from(
'006e6f64656a733a746c733a73657373696f6e3a31000014676174657761792e6578616d706c652e746573746d6f636b65642073657373696f6e',
'hex',
);
const socket = tls.connect({ host: '127.0.0.1', port: 9, servername: 'gateway.example.test', session });
socket.on('error', () => {});
socket.destroy();
await subtle.decrypt({ name: 'AES-GCM', iv }, key, ct); // aborts here
console.log('ok');What is the expected behavior? ok. A failed session deserialization should not affect an unrelated, valid WebCrypto operation.
What do you see instead?
# node[...]: v8::Maybe<bool> node::crypto::CipherJob<node::crypto::AESCipherTraits>::ToResult(...)
# Assertion failed: errors->Empty()
The process is terminated with SIGABRT.
Additional information
TLSWrap::SetSessionβGetTLSSessionβd2i_SSL_SESSIONfails and leaves the error on the JS thread's OpenSSL error queue. NoClearErrorOnReturnorMarkPopErrorOnReturnis used there (// TODO(tniessen): figure out error handling).- In
v24.x,CipherJob::ToResultrunsif (errors->Empty()) errors->Capture();on the JS thread and thenCHECK(errors->Empty())on success. The stale error is captured into a successful job, and the process aborts. - In
v26.x,ToResultno longer captures the queue, so the abort does not happen. Would that change be a candidate for a backport to v24.x (LTS)? - Separately, should
SetSessionclear or pop the error queue when deserialization fails? - In practice this was triggered by a test HTTP interceptor that emits fake TLS sessions, which undici then reuses (see the
mswjs/interceptorsreport). Any stale OpenSSL error on the JS thread would have the same effect.
Metadata
Metadata
Assignees
Labels
No labels
Activity
webteamfrit commentedon Nov 2, 2026
Related report on the interceptor side: mswjs/interceptors#892
panva commentedon Nov 2, 2026
The abort is handled by #66237 (
crypto: isolate cipher job errors).CipherJob::ToResult()no longer captures unrelated errors from the main thread's OpenSSL error queue.It is already in
v24.x-stagingas ae35a8f slated for the next 24.x LTS release.The TLS session deserialization path should still clean up its OpenSSL errors separately. Feel free to open a separate issue for that.
webteamfrit commentedon Nov 2, 2026
Thanks @panva, confirmed: ae35a8f fixes the abort for us (verified on v26.13.0; we'll pick it up in the next 24.x as well). I opened #67204 for the TLS session deserialization error cleanup, as suggested. Closing this one.