The asynchronous convenience methods hand the whole input to zlib in
one write, but still take one threadpool round trip per `chunkSize`
(16 KiB by default) of output. Decompressing 4 MiB took 257 round
trips.
Only the concatenated result of these calls is observable, so double
the output buffer whenever it fills up, up to 1 MiB. 4 MiB of output
now takes 10 round trips. The buffer never grows past what is needed
to tell that `maxOutputLength` was exceeded, and results that fit in
the first buffer are unchanged.
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Assisted-by: Grok
PR-URL: https://github.com/nodejs/node/pull/66863
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
The asynchronous convenience methods (`zlib.deflate()`,
`zlib.inflate()`, `zlib.gunzip()`, ...) wrote the input to a
Transform stream and then ended it. That took two threadpool round
trips, one for the data and one for the final flush, and ran the
result through the stream machinery.
The whole input is known up front, so unless `info` or a custom
`flush` is requested, process it with the finish flush directly, the
way the synchronous methods already do, and collect the output in the
write callback. A call whose output fits in one buffer now takes a
single threadpool round trip. The output is unchanged: the previous
two-write sequence already produced the same bytes as the synchronous
methods. `zstdCompress()` keeps the separate end-of-frame write,
because a single `ZSTD_e_end` call can produce slightly larger frames.
benchmark/compare.js, 40 runs, n=100000:
confidence improvement
zlib/inflate.js method='inflate' *** +94.91 %
zlib/deflate.js method='deflate' *** +66.27 %
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Assisted-by: Grok
PR-URL: https://github.com/nodejs/node/pull/66863
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
`zlib.inflateSync(buffer)` and the other synchronous convenience
methods constructed a Transform stream for every call, even though
only its native handle is used and, without options, the stream is
never exposed. They also allocated a fresh output chunk for every
call.
When no options are passed, drive a native handle created with the
default settings directly, and decompress into a scratch buffer kept
between calls, copying the result out. Calls with options, including
`info`, keep the existing path. Results and errors are unchanged.
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Assisted-by: Grok
PR-URL: https://github.com/nodejs/node/pull/66864
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
closeSession() set SESSION_FLAGS_DESTROYED and then called
session.setTimeout(0). setStreamTimeout() returns early when the
session is destroyed, so the idle timer stayed armed and retained
the session until the server timeout fired.
Clear the timeout and remove its listeners before setting the
destroyed flag.
Fixes: https://github.com/nodejs/node/issues/66885
Signed-off-by: huang <13165998033@163.com>
PR-URL: https://github.com/nodejs/node/pull/66886
Reviewed-By: Tim Perry <pimterry@gmail.com>
Extends the module compile cache to WebAssembly modules loaded through
the ES module integration. A new kWasm entry type is keyed on the module
URL with the wire bytes as the hashed source, and stores the serialized
CompiledWasmModule. On load, cached code is deserialized through
v8::WasmModuleCompilation with the same compile options the translator
uses (js-string builtins and imported string constants), falling back
to compilation when V8 rejects it.
As for JavaScript, the cache entry is generated right after compilation.
V8 only serializes optimized tier code, so with the default lazy
baseline compilation there is nothing to cache and no entry is written;
a complete cache requires --no-liftoff --no-wasm-lazy-compilation, which
is documented as the way to use the compile cache for WebAssembly.
Signed-off-by: Guy Bedford <guybedford@gmail.com>
Assisted-by: Claude Code
PR-URL: https://github.com/nodejs/node/pull/66191
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
zipEntry.rawContent was a getter that exposed the retained buffer of an
in-memory entry and returned null for a file-backed or streaming one,
so the still-compressed bytes of a member read through ZipFile could
not be obtained at all.
Replace it with rawContent(), rawContentSync() and
rawContentIterator(), which mirror content(), contentSync() and
contentIterator() but return the bytes as stored, without
decompressing or verifying them. They work for every entry that has
readable content, return caller-owned buffers from the buffered
variants, and accept a maxSize bound on the compressed size.
Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/66801
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
On a mounted VFS, `readlink()`, `realpath()` and `mkdtemp()` only
returned a Buffer for `{ encoding: 'buffer' }`. The string form
`'buffer'`, any other encoding, and a Buffer `mkdtemp()` prefix were
ignored. The `VirtualFileSystem` `readlink` and `realpath` methods
ignored the encoding with `MemoryProvider` and passed it on to
`RealFSProvider`, which threw `ERR_INVALID_ARG_TYPE` for `'buffer'`,
while for other encodings `realpath()` failed and `readlink()`
returned the host path of absolute targets.
Get these paths from the provider as strings and apply the encoding
to the result, normalizing the options with `getOptions()` and
converting with `encodeRealpathResult()` as `fs` does. The latter
moves to `internal/fs/utils` so the VFS can use it.
Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66352
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Translate cipher-suite names into protocol IDs for BoringSSL's TLS 1.3
configuration API. Handle duplicates and unknown names, reject nonempty
lists without a supported suite, and implement honorCipherOrder through
preference flags.
Cover suite restrictions, explicit preference ordering, legacy-only
lists, runtime defaults, and command-line overrides.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66922
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Add SHA3-256, SHA3-384, and SHA3-512 to name lookup and getHashes().
BoringSSL exposes EVP_sha3_* descriptors but omits them from its
digest registry. Map the names directly to those descriptors so
node:crypto can use them too.
When using BoringSSL, limit WebCrypto SHA-3 support to digest
operations.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66922
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Speed up creating and aborting signals, adding and removing abort
listeners, AbortSignal.any() and AbortSignal.timeout(), and stop the
default abort reason from retaining the aborting caller.
- Only allocate and dispatch the abort event when the signal has abort
listeners, and only allocate the dependent signal list when there
are dependent signals.
- Track whether an event is trusted with a private field instead of a
WeakSet.
- Flatten addEventListener() options into locals instead of
allocating a normalized options object, only validate the signal
option when one is passed, and use the listener flags directly.
- Call the EventTarget kNewListener and kRemoveListener
implementations directly. V8 does not optimize keyed super property
access such as super[kNewListener]().
- Initialize the internal slots that AbortSignal.any() adds to its
sources up front, and the composite-only slots for composites, so
live signals don't change shape.
- Give every signal a single WeakRef that is shared by all the
composites that follow it, and register each composite and each
source once with their cleanup registries instead of registering
every (source, composite) pair with an unregister token. Registering
with an unregister token is several times more expensive.
- Register AbortSignal.timeout() timers through a holder that is
emptied when the timer fires, instead of using the signal as an
unregister token.
- Take positional arguments in AbortSignal's internal constructor
instead of destructuring an options object that defaults to
kEmptyObject. kEmptyObject is a null-prototype object, which V8 keeps
in dictionary mode, so each of those reads was an uncached dictionary
miss that cost more than the rest of the constructor.
- Create the default abort reason with Error.stackTraceLimit set to 0.
Until a captured stack is formatted, V8 keeps the call sites, which
retain the receivers and functions of the aborting caller's frames,
so a retained signal kept the aborted work alive. Formatting the
stack eagerly would fix that but costs several microseconds per
abort(). The default AbortError no longer shows where abort() was
called; callers that want that can pass their own reason.
- Add benchmarks for aborting, adding and removing abort listeners,
and AbortSignal.any().
Refs: https://github.com/nodejs/node/issues/66192
Refs: https://github.com/nodejs/node/pull/66265
Assisted-by: claude:opus-5.5
Signed-off-by: Robert Nagy <ronagy@icloud.com>
PR-URL: https://github.com/nodejs/node/pull/66789
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Add --test-forbid-only and the forbidOnly run() option to detect
accidentally committed exclusive tests. Reject only declarations and
runOnly(true) in both isolation modes, and record a run-level failure
so TODO and expected failures cannot absorb the error.
Document the runtime-only detection and add regression coverage for
filtering, option inheritance, both isolation modes, and disabled
guards.
Refs: https://github.com/nodejs/node/issues/60917
Assisted-by: a coding agent
Signed-off-by: João Victor <joao.oliveira@softtor.com.br>
PR-URL: https://github.com/nodejs/node/pull/66578
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
fs.promises.watch() over a virtual mount resolved the first next()
with { done: true } when the signal was already aborted, while native
fs.promises.watch() rejects with ABORT_ERR.
Buffer the AbortError in VFSWatchAsyncIterable so the first next()
rejects once and later calls behave like an exhausted iterator,
matching the native async generator. Concurrently pending next()
calls now resolve after the rejecting one instead of all rejecting.
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: opencode
PR-URL: https://github.com/nodejs/node/pull/66254
Fixes: https://github.com/nodejs/node/issues/66253
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
A MemoryFileHandle snapshotted the file's buffer at open and never
checked the entry again. After the file changed through the provider
or another handle, an open handle served stale reads and fstat sizes,
and its writes went into the old copy, undoing other handles' changes.
Record the entry content each handle last saw or wrote, and adopt the
entry's content whenever another handle has replaced it, so an open
descriptor is a live view of the file as on a real filesystem.
Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
Assisted-by: claude:opus-5.5
PR-URL: https://github.com/nodejs/node/pull/66356
Fixes: https://github.com/nodejs/node/issues/66355
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Startup snapshots retain unused randomInt() and UUID cache entries,
causing deserialized processes to replay the same random values.
Clear the caches after user serialization callbacks and before user
deserialization callbacks. The latter also handles caches refilled by
microtasks, next ticks, or exit handlers after serialization callbacks.
Refs: https://hackerone.com/reports/4059814
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs-private/node-private/pull/987
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruy Adorno <ruy@vlt.sh>
CVE-ID: CVE-2026-106074
The lazy file-backed ZIP resolver cached a parsed local header before
checking it against the central directory. A failed check therefore left
the entry with a cached header and unresolved content offset, allowing a
later read to use the file descriptor's current position.
Validate the local header and calculate its content offset before
committing either value to the entry.
Assisted-by: Pi
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/981
Refs: https://hackerone.com/reports/4004290
Reviewed-By: Robert Nagy <ronagy@icloud.com>
CVE-ID: CVE-2026-106070
TLS identity checks normalized non-canonical numeric hosts before
matching certificates. Platform resolvers can interpret the original
host as a different address.
Reject hosts that become IP addresses only after domainToASCII()
normalization.
Assisted-by: Pi
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/982
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
CVE-ID: CVE-2026-106069
CVE-ID: CVE-2026-106072
Add zlib.crc32c(data[, value]), which computes the CRC-32C checksum
(Castagnoli polynomial, RFC 3720 section 12.1). CRC-32C has better
error detection properties than the CRC-32 of zlib.crc32() and is the
checksum required by many current protocols, formats and services,
among them:
- iSCSI header and data digests (RFC 7143)
- SCTP packet checksums (RFC 9260)
- iWARP MPA framing (RFC 5044)
- NVMe/TCP header and data digests
- ext4, Btrfs and XFS metadata checksums
- the Snappy framing format
- Apache Kafka record batches
- LevelDB and RocksDB block checksums
- TensorFlow TFRecord files
- Google Cloud Storage and Amazon S3 object checksums
Implementing any of these in JavaScript currently needs a userland
CRC-32C, which is far slower than native code.
The function mirrors zlib.crc32(): it accepts a string (encoded as
UTF-8) or an ArrayBufferView, takes an optional 32-bit unsigned
starting value so a checksum can be continued across chunks, and has a
V8 fast API path.
The checksum is computed by absl::ExtendCrc32c() from the bundled
Abseil, which is already built and linked into node, so this adds no
new dependency and no new checksum implementation. absl_crc32c is
added to the libraries linked with --shared-abseil.
Abseil selects its hardware-accelerated CRC-32C path (SSE4.2 and
PCLMULQDQ on x64, the CRC32 and PMULL extensions on arm64) at compile
time, so with node's default compiler flags the portable table-driven
implementation is used. It runs at about the same speed as zlib.crc32()
(about 6.5 GB/s on a recent x64 machine), while the hardware path
reaches about 38 GB/s. Builds whose target CPU baseline already
includes those instructions get the hardware path without changes.
Enabling it for generic builds is left out on purpose: on x64, Abseil
uses the accelerated code for unrecognized CPUs without a runtime
feature check, so compiling it in unconditionally would fault on CPUs
without SSE4.2.
Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/66828
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Adds a "worker" package condition that is applied within Web Worker
threads created via the `Worker` global, for the worker entry point,
its dependencies, `import()`, `require()` and `import.meta.resolve()`.
Threads created via `worker_threads`, including from within a Web
Worker, do not apply the condition. Other conditions are unaffected.
Assisted-by: OpenCode
Signed-off-by: Guy Bedford <guybedford@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66635
Reviewed-By: James M Snell <jasnell@gmail.com>
process.finalization.register() and registerBeforeExit() now return
a function that removes only that registration, leaving other
registrations for the same object in place. The function does not
hold a strong reference to the object.
Each registration now uses its WeakRef as the unregister token, which
also makes process.finalization.unregister() remove the entries from
the FinalizationRegistry; previously it passed the object as the
token, which was never registered, so the call had no effect.
Refs: https://github.com/nodejs/node/pull/66482
Signed-off-by: Matteo Collina <matteo.collina@gmail.com>
Assisted-by: pi
PR-URL: https://github.com/nodejs/node/pull/66482
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
When a net.Server is configured with a `blockList`, the peer address is
looked up with getpeername() and checked before the connection is
accepted. If the peer address cannot be obtained (for example, the
connection was reset before getpeername(), causing
uv_tcp_getpeername() to return ENOTCONN), the access-control check was
silently skipped and the connection proceeded, allowing a blocked peer
to reach the application's 'connection' handler (fail open).
Fail closed: when a BlockList is configured but the peer address cannot
be determined, close the connection instead of bypassing the check. A
connection whose peer address cannot be obtained cannot be proven to be
allowed, so it must be refused.
Ref: https://github.com/nodejs/node/issues/66422
Ref: https://github.com/libuv/libuv/pull/5337
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Assisted-by: pi
PR-URL: https://github.com/nodejs/node/pull/66640
Refs: https://github.com/nodejs/node/issues/66422
Refs: https://github.com/libuv/libuv/pull/5337
Reviewed-By: Tim Perry <pimterry@gmail.com>
Constructing a TransformStream allocated five closures for the sink and
source algorithms of its two sides, and each side adopted the start
promise through a wrapper promise plus a thenable job, so a stream
without a start() left about three kilobytes pending in the microtask
queue until the started steps ran. Per-stream creation bursts spend
most of their time copying that graph through the scavenger.
The sink and source algorithms are now shared functions that reach the
transform stream through a field on their controller state (the
controller is passed to the close, abort and cancel algorithms for
that), and the post-start steps of both sides are delivered by one
reaction chain on a shared promise, taking the same microtask hops as
the spec's start promise adoption: three after construction for a
non-thenable start result, two after the adopting promise settles for a
thenable one. The readable and writable transfer state records are
materialized on first transfer instead of per stream.
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/66643
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Mattias Buelens <mattias@buelens.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
decodeFormComponent() already finds the first percent character, but
hasPercentHex() scans the plain prefix again. Start the check at the
known position to avoid repeating that work.
Replacing plus signs with spaces preserves character positions, so
escape validation and decoding behavior remain unchanged.
Signed-off-by: Roberto Bianchi <roberto.bianchi@spendesk.com>
PR-URL: https://github.com/nodejs/node/pull/66696
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
A single executable application built with "vfsArchive" mounted its
archive at the next free layer, like any file system a program mounts
itself, and only in the main thread. A worker had no mount at all, so
a path into the archive, such as the main script's __filename, could
not be loaded there.
Mount the archive at the layer reserved for --vfs-load instead, so it
is at the same mount point in every thread, and mount it in each
worker as well, with the main script placed at its root just as in
the main thread. The worker reads the main script through a new
getMainCode() binding, since no main script is handed to it. Unlike
the --vfs-load source, the archive needs no provider that a preload
could register, so its mount in a worker is not deferred past
--import.
Since the archive takes that mount point, reject --vfs-load alongside
it: --build-sea refuses it in "execArgv", and the executable refuses it
at startup when it comes from --node-options or from the execArgv of a
worker.
Also fix the example in vfs.md, which showed a file system a program
mounts itself at layer 0, the layer reserved for these mounts.
Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/66709
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
To stay cancellable while a source is pending, from() waits for every
value of an async source through waitForNormalization(). For each value
it created a PromiseWithResolvers(), raced it against the value with
SafePromiseRace(), which wraps both in new promises, and ran an async
function with try/finally. This was the largest per-batch cost of
normalizing an async source.
Wait with a single promise and a single reaction on the value instead,
and reject that promise directly on cancellation. The outcome is
unchanged: the value's result, its rejection, or the cancellation
reason, whichever comes first, and the cancellation reason if the
normalization was cancelled by the time the value fulfills.
With an async generator yielding 16-byte chunks, pipeTo() is about
1.7x faster and iterating from() about 1.75x faster.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
stream/iter registered its one-time 'abort' listeners with a new
`{ __proto__: null, once: true }` options object every time, which can
be once per chunk (abortableNext()) or per waiting write. Use a single
shared kNullOnceOption instead. It is frozen, because signals can come
from user code and a patched addEventListener() must not be able to
change the options for every later registration.
The difference is small, since the listener registration itself costs
much more: with 300,000 16-byte chunks, pull() with a signal is about
1.7% faster, push() writes that wait with a signal about 3% faster, and
pipeTo() and bytes() with a signal are unchanged.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
When a write filled the classic Writable, fromWritable() recorded that
it needed to drain, but only listened for 'drain' once a later write
was queued or something waited for drain. If the Writable emitted
'drain' before that, for example because its write callback ran on a
microtask or with process.nextTick(), the event was missed and the flag
was never cleared: the next write() or writev() never settled, canWrite
stayed false and ondrain() never resolved.
Listen for 'drain' as soon as a write returns false, and keep listening
until it is emitted.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>