`Database.prototype.aggregate()` cast its arguments without checking
their types, so a missing `options` leaked a raw V8 TypeError and a
non-string `name` was silently coerced. Throw `ERR_INVALID_ARG_TYPE`
like the other sqlite bindings do.
Assisted-by: Claude Code
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66463
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Raw and JWK getters return independently owned bytes or BIGNUMs. RSA
metadata snapshots public parameters and PSS restrictions. EC fallback
paths reconstruct or duplicate the source key. EC PKCS8 export changes
encoding flags only on its own clone.
Early OpenSSL 3 key downgrading mutated shared provider fields. Current
OpenSSL publishes separate legacy and provider conversion caches under
internal read/write locks. Ordinary DER/PEM export, equality, signing,
and key derivation already access the same keys without these locks.
Remove export and metadata acquisitions, including their coverage of V8
allocation and encoding, and remove the now-unused shared mutex storage.
Retain shared immutable key ownership. The concurrent reuse test checks
that EC PKCS8 export leaves the original flags intact.
Refs: https://github.com/nodejs/node/pull/36825
Refs: https://docs.openssl.org/3.5/man7/openssl-threads/
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66413
Reviewed-By: James M Snell <jasnell@gmail.com>
Verify configuration only copies signature bytes and reads DSA/EC order
width for P1363 conversion. Conversion constructs independent DER and
BIGNUM values. Signing and verification already run without the mutex
using separate operation contexts.
Cover cold shared public keys and worker clones with checked P1363
verification while signatures, derivation, exports, metadata queries,
and equality checks overlap.
Compare source PKCS8 encoding after workers finish. OpenSSL's ordinary
EC PKCS8 encoder temporarily changes source flags and races even on the
unmodified base. Keep WebCrypto PKCS8 exports concurrent through their
independent clones.
Refs: https://github.com/nodejs/node/pull/36825
Refs: https://github.com/nodejs/node/pull/37816
Refs: https://docs.openssl.org/3.5/man7/openssl-threads/
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66413
Reviewed-By: James M Snell <jasnell@gmail.com>
ncrypto::KEM creates a new EVP_PKEY_CTX per operation. Parameters,
entropy, scratch buffers, and outputs are private to each call. ML-KEM
expansion finishes during import, and operations read an immutable key.
EC and X25519 retain read-only recipient keys. RSA synchronizes mutable
caches internally.
Remove the outer locks serializing jobs that share a KeyObject or
CryptoKey. Cover cold keys and worker clones across RSA, EC, X25519, and
ML-KEM, checking every resulting shared secret.
Refs: https://docs.openssl.org/3.5/man7/openssl-threads/
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66413
Reviewed-By: James M Snell <jasnell@gmail.com>
RSA_Cipher delegates to ncrypto::Rsa with a new EVP_PKEY_CTX per
operation. Padding, digests, labels, and output buffers belong to that
context. OpenSSL and BoringSSL synchronize RSA blinding and Montgomery
caches internally.
The lock was introduced for early OpenSSL 3 key downgrading that cleared
shared provider fields. Current OpenSSL publishes a separate legacy
cache under its own lock.
Cover cold shared CryptoKeys and worker clones with concurrent
encrypt/decrypt, synchronous KeyObject aliases, and checked plaintexts.
Refs: https://github.com/nodejs/node/pull/36825
Refs: https://docs.openssl.org/3.5/man7/openssl-threads/
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66413
Reviewed-By: James M Snell <jasnell@gmail.com>
Secret key bytes are immutable after KeyObjectData construction. AES,
ChaCha20-Poly1305, MAC, and KDF jobs read those bytes without acquiring
KeyObjectData::mutex(), and secret-key export and equality do the same.
Do not allocate a shared mutex that none of these operations uses.
Asymmetric key mutex initialization remains eager so copies continue to
share the same lock while asymmetric acquisitions remain.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66413
Reviewed-By: James M Snell <jasnell@gmail.com>
Several Environments can share a thread, so state in src/ that belongs
to one of them cannot be kept in a thread_local. Add a cpplint rule
that rejects thread_local in src/ unless the declaration is marked with
NOLINTNEXTLINE(runtime/thread_local), and mark the existing uses, which
are per-thread by design.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66411
Refs: https://github.com/nodejs/node/pull/66239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
The ngtcp2 and nghttp3 allocators shared one thread_local state whose
BindingData pointer was set by the last BindingData that handed out an
allocator. With several Environments on a thread, memory allocated for
a session in one Environment was accounted against another's
BindingData and failed a CHECK when freed.
Give each BindingData its own heap-allocated state. nghttp3 buffers
backing external strings can be freed after the BindingData is gone,
so the state counts live allocations and is deleted once the
BindingData has been destroyed and the last of them is freed.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66411
Refs: https://github.com/nodejs/node/pull/66239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
The root cert store and the certificates set through
tls.setDefaultCACertificates() were thread_local, with a cleanup hook on
whichever Environment used TLS first. When several Environments share a
thread, setting the default CA certificates in one of them replaced the
trusted CAs of the others. Keep both on the Environment, next to its
other OpenSSL state.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66411
Refs: https://github.com/nodejs/node/pull/66239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Several conversions from SQLite text to V8 strings passed a length of
-1 to String::NewFromUtf8(). With that length, V8 skips its length
check and aborts the process when the UTF-8 text exceeds
String::kMaxLength.
SQLite error messages embed the offending identifier or token, so
CreateSQLiteErrorImpl() could hit this. NullableSQLiteStringToValue(),
used for setAuthorizer() callback arguments and statement.columns()
metadata, had the same problem, and the authorizer also called
ToLocalChecked() on each argument.
Convert error messages with Utf8StringMaybeOneByte(), and check the
length up front in NullableSQLiteStringToValue(), so oversized strings
throw ERR_STRING_TOO_LONG as column values already do. In the
authorizer, deny the action and let the pending error reach the caller.
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/66521
Fixes: https://github.com/nodejs/node/issues/66520
Fixes: https://github.com/nodejs/node/issues/66522
Reviewed-By: Guilherme Araújo <arauujogui@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
Reviewed-By: James M Snell <jasnell@gmail.com>
Add websocketMask(source, mask, output[, offset[, length]]) and
websocketUnmask(buffer, mask), which XOR data with a repeating 4-byte
key. This is the masking that WebSocket clients apply to every frame
they send (RFC 6455, Section 5.3), and that servers undo on every frame
they receive.
Userland WebSocket implementations do this either with a byte-by-byte
JS loop (undici, and ws without optional dependencies) or with the
bufferutil native addon. bufferutil is installed for only about 3% of
ws downloads and has no linux-arm64 prebuild, so almost all users run
the JS loop. The signature matches bufferutil, so existing users can
switch with a feature check, as ws did for buffer.isUtf8().
The implementation uses a V8 fast API call and processes 8-byte words,
which the compiler vectorizes. It handles overlapping source and output
views, and treats every view type as raw bytes. It is about 20x faster
than the JS loop at 1 KiB and 40x faster at 64 KiB, and faster than
bufferutil at every size from 32 bytes up.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode
PR-URL: https://github.com/nodejs/node/pull/66335
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Allow safe integer numbers as int64 and uint64 arguments alongside
bigint values. Reject negative numbers for uint64 and numbers outside
the safe integer range. Keep 64-bit return values as bigint.
Apply validation and conversion across the Fast API, shared-buffer,
and generic argument conversion paths.
Add coverage for Number and BigInt boundaries, invalid inputs, and
single-argument calls before and after optimization.
Signed-off-by: HoonDongKang <d159123@naver.com>
Assisted-by: Codex:Astra-medium
PR-URL: https://github.com/nodejs/node/pull/66197
Fixes: https://github.com/nodejs/node/issues/66198
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
process.exit(), uncaught exceptions and unhandled rejections exit
through Environment::Exit(), which never calls OnEnvironmentStopping().
If a Worker blocked in a synchronous native call held up that exit past
the deadline, the watchdog blamed the main thread.
Check Environment::is_stopping() and report that the process did not
finish exiting instead.
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/66361
Fixes: https://github.com/nodejs/node/issues/66360
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
With `force: false` and `preserveTimestamps: true`, the native
directory copy used by `cpSync()` left existing destination files in
place but still copied the source timestamps onto them. The JavaScript
walk, used by `cpSync()` with a `filter` and by `fs.cp()`, leaves
those files untouched.
Only copy the timestamps of files that were actually copied, as is
already done for their mode.
Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66305
Reviewed-By: Anna Henningsen <anna@addaleax.net>
On x64 SysV and arm64, a Fast API signature with two or more
arguments that uses only pointer and float types got no JS wrapper.
V8 then truncated pointer BigInts silently. With `-1n` or
`2n ** 64n + 5n`, cold calls threw ERR_INVALID_ARG_VALUE, while
optimized calls passed 0xffffffffffffffff or 5 to native code.
Use the argument wrapper for any signature with a pointer argument.
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/66371
Fixes: https://github.com/nodejs/node/issues/66370
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
The copy path of toArrayBuffer() allocated its backing store with
V8's default failure mode, so a large length aborted the process
with a fatal out-of-memory error. toBuffer() throws a catchable
ERR_MEMORY_ALLOCATION_FAILED for the same input.
Allocate with kReturnNull and throw ERR_MEMORY_ALLOCATION_FAILED
when the allocation fails. The memory is left uninitialized because
memcpy() fills it right after.
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/66406
Fixes: https://github.com/nodejs/node/issues/66405
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Stopping a Worker while it is running an FFI callback aborted the whole
process with "Callbacks cannot throw an exception". This happened on
worker.terminate(), on process.exit() inside the callback, and when the
main thread exited while the Worker was in a callback, since exit
terminates all Workers.
All three stop the Worker by terminating execution, and InvokeCallback
treated the termination as a thrown exception. Check HasTerminated()
first and return a zeroed result so the native caller can unwind.
Callbacks that throw still abort.
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/66389
Fixes: https://github.com/nodejs/node/issues/66388
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
close() destroys the connection but keeps the DatabaseSync object, and
open() did not replay the state held on it.
An authorizer set with setAuthorizer() was not reinstalled, silently
dropping a deny-all policy. Limits written through db.limits.* reverted
to the constructor values. Extension loading was re-enabled from the
constructor ceiling rather than the current setting, so the connection
flag contradicted a previous enableLoadExtension(false), though
loadExtension() itself stayed blocked by its own check.
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
Assisted-by: Claude Code
PR-URL: https://github.com/nodejs/node/pull/66042
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
When spawn()/spawnSync() are called without options.env,
normalizeSpawnArguments() copied process.env with a spread and then
walked the copy to build the KEY=value array uv_spawn() takes.
Spreading the process.env proxy costs one enumerator callback plus a
query and a getter interceptor per variable, each doing a linear
getenv() scan and allocating; with a couple of hundred variables that
was the single largest JS-side cost of spawning a process.
Add KVStore::Pairs() (Enumerate() + Get() by default, one
uv_os_environ() pass for the real environment, skipping hidden
variables on Windows exactly like Enumerate() does), expose it as
process_wrap.getEnvPairs(), and use it for the default-environment
case. A user supplied options.env and the permission model case keep
the existing code. On Windows the same sort/first-wins-case-insensitive
filter is applied to the pairs. The variables copyProcessEnvToEnv()
propagates are part of the real environment by definition, and its
entries cannot contain null bytes, so those steps only remain on the
options.env path.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65325
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
GetValidatedSize() checks the value against
static_cast<double>(SIZE_MAX), which rounds up to 2^64 on 64-bit
platforms. A length or offset of 2 ** 64 gets through, and the cast to
size_t after it is undefined behavior. With GCC on x64 it gives 0, so
ffi.setUint8(ptr, 2 ** 64, 42) writes to ptr instead of throwing.
Anything above Number.MAX_SAFE_INTEGER may already have been rounded
by the time it gets here, so reject those values too. The export*()
helpers already cap their length there, and so does setInt64() for
number values. SIZE_MAX is still the limit on 32-bit platforms.
When buffer.constants.MAX_LENGTH is Number.MAX_SAFE_INTEGER, as on
64-bit builds without the V8 sandbox, toBuffer() and toArrayBuffer()
now throw ERR_OUT_OF_RANGE for MAX_LENGTH + 1 instead of
ERR_BUFFER_TOO_LARGE.
Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66216
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
When mkdir() fails with ENOENT, the recursive algorithm assumes the
parent is missing, creates it and retries. On procfs mkdir() keeps
returning ENOENT although the parent exists, and under a concurrent
rmdir() the parent can disappear again, so the retry loop never
terminates: `fs.mkdirSync('/proc/x', { recursive: true })` spins at
100% CPU.
Retry a path that failed with ENOENT only once and report ENOENT the
second time, like the non-recursive call does.
Fixes: https://github.com/nodejs/node/issues/66268
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66340
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
With the FreeEnvironment() fix for sibling Environments and the handle
cleanup depth tracked per thread, only the Environment being freed
loses JavaScript while FreeEnvironment() runs the shared loop. Callbacks
of the other Environments on that loop run their JavaScript as usual.
Update embedding.md and the comment in node.h, which still describe
JavaScript as disallowed on the whole isolate.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66239
Refs: https://github.com/nodejs/node/pull/65977
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
The FreeEnvironment() fix for sibling Environments keeps the depth of
nested Environment::CleanupHandles() calls on the IsolateData, so that
InternalCallbackScope can re-allow JavaScript for sibling Environments
while one of them is being freed. Environments that each have their own
IsolateData on the same isolate and loop never see that counter and
still fail with "illegal access". Environments that share a loop share a
thread, so keep the depth in a thread_local instead.
Refs: https://github.com/nodejs/node/pull/65977
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66239
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
PrepareFunction() looked up the function cache before parsing the
signature. Parsing can run user getters, and a getter that calls
lib.close() clears the cache, which invalidates the iterator. For a
function that was already cached, reading it afterwards used freed
memory and crashed the process.
Look up the cache after the signature is parsed. If the library was
closed during parsing, ResolveSymbol() now throws
ERR_FFI_LIBRARY_CLOSED.
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/66368
Fixes: https://github.com/nodejs/node/issues/66367
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Return SQLITE_CONSTRAINT from xBestIndex() when a hidden column has
an equality constraint that is not usable at the current point in the
query plan. Accepting such a plan makes xFilter() call rows() with
null, silently dropping rows and returning an empty result, e.g. for
"SELECT DISTINCT * FROM m(t.a) JOIN t ON t.a = m.a". Cost estimates
alone cannot enforce parameter dependencies; rejecting the plan lets
SQLite pick an ordering where the parameter value is available.
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: opencode
PR-URL: https://github.com/nodejs/node/pull/66215
Fixes: https://github.com/nodejs/node/issues/66214
Reviewed-By: Guilherme Araújo <arauujogui@gmail.com>
SQLite serves TEXT up to SQLITE_MAX_LENGTH, past what V8 can represent
as a string. V8 returns an empty handle without throwing, and the
user-defined function path then suppressed the SQLite error as if a
JavaScript exception were pending. exec() reported success for a
statement that never ran, and get() returned undefined.
Throw ERR_STRING_TOO_LONG when the value cannot be converted, and
suppress a SQLite error only when an exception is actually pending.
Assisted-by: Claude Code
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66209
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
InternalCallbackScope looks up the Environment from the isolate two
times per call, inside async_context_frame::exchange, and it keeps the
prior async context frame in a v8::Global also when there is no frame,
that is the common case. Every call from native code into JS pays this:
MakeCallback, CallbackScope, AsyncWrap, Node-API.
Now the scope passes the Environment it already has, the option is read
with an inline accessor instead of copying the shared_ptr, and the
global handle is created only when the prior frame is not undefined.
benchmark/napi/make_callback, Node 26.3.0 built with and without this
change, Linux x64, 30 runs: from 202-208 ns to 155-159 ns per call.
Refs: https://github.com/nodejs/performance/issues/24
Signed-off-by: Nigro Simone <nigro.simone@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66316
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: James M Snell <jasnell@gmail.com>