Three shapes from pi's process/child helpers:
1. A cleanup arrow such as `() => process.off(signal, handler)` sits in
@types/node's `Process`-returning slot. The lambda's ABI was decided
from its inferred return type before the contextual void slot was
consulted, so the arrow promised a `Process` value the lowering cannot
produce and the registration fenced. `lambdaSignature` now takes the
contextual void answer first; the adoption block further down already
computes it, so every program that compiles today keeps its IR.
2. `child.off`/`removeListener` were unimplemented. They join the
`child.on` family, are limited to the removable literals `spawn`,
`exit`, `close` and `error`, and require the listener value that was
registered — removal is by closure identity, scanning the registry
from the end as Node does. New C runtime entry points
`scr_child_off_spawn/_exit/_close/_error` release the entry and shift
the tail.
3. `child.signalCode` reads `string | null` from the recorded exit
signal, with `scr_child_has_signal_code`/`scr_child_signal_code`
behind the same typed-union wrap as `spawnRes.signal`; the IR
validator pins the `string | null` result.
The process listener registration paths (stdin events, IPC message and
disconnect, uncaughtException and uncaughtExceptionMonitor,
unhandledRejection, warning, signals, exit) now accept any discarded
expression, matching `child.on` — `resultIsDiscarded` rather than a bare
expression statement — so `handlers.push(() => process.off(…))` compiles.
Evidence: corpus 8412 (removal through `off` and `removeListener` for
all four events, `signalCode` while running, after a normal exit, after
a fatal signal and after a failed spawn, idempotent double removal) and
the `node-types` fixture plus project-config test that pins pi's exact
`() => process.off(...)` cleanup closure. Ambient declarations gain the
`signalCode` property and the four `off`/`removeListener` overloads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
existsSync and the fs.promises functions accept a file: URL wherever
they accept a path. They convert it after their arguments are evaluated,
and a URL that does not convert gives false from existsSync and a promise
rejected with fileURLToPath's error from fs.promises, as in Node.
copyFileSync and renameSync check a string source for null bytes before
they convert a URL destination, and readFileSync checks a runtime
encoding before it converts a URL path, so the error Node reports first
is the one thrown.
Refs #1616
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
X25519 KeyObjects come from generateKeyPair(Sync)("x25519"), which keeps
the private key clamped as OpenSSL does, and from createPrivateKey and
createPublicKey of PKCS#8, SubjectPublicKeyInfo, JWK (OKP, X25519) and
raw keys. They export in the same forms. sign, verify, createSign and
createVerify throw OpenSSL's "operation not supported for this keytype",
with its library, reason and code properties. X25519 uses mbedTLS's
Curve25519 group.
crypto.diffieHellman({ privateKey, publicKey }[, callback]) computes the
X25519 and ECDH secrets of two keys of one type, given as KeyObjects or
in any form createPrivateKey and createPublicKey read. The checks run in
Node's order: the options, the callback, both keys present, the key types
when both are KeyObjects ("Incompatible key types for Diffie-Hellman"),
then the public and private key inputs. Mismatched curves, a small-order
X25519 point and key types other than EC and X25519 throw OpenSSL's
errors. The callback form passes the secret, or the error without the
OpenSSL properties, in a later macrotask, as Node's DHBitsJob does.
createPublicKey also reads a private key's DER encodings (pkcs8 and
sec1), which Node accepts there.
getCipherInfo(nameOrNid[, options]) runs in a new runtime unit,
scr_cipher_info.c, over a table that scripts/gen-cipher-info.mjs generates
from Node 24.22's getCipherInfo. The table covers every cipher OpenSSL
names, including the ones without a native implementation. Lookups match
names case-insensitively and accept OpenSSL's nids. The unit applies
cipher.js's argument checks and GetCipherInfo's keyLength and ivLength
rules, and returns the info object in Node's key order. CipherInfo maps
to a checked-dynamic value, so TypeScript and JavaScript code receive
Node's object. Reading the nid Node leaves out for the SIV and SM4 GCM,
CCM and XTS ciphers, where the declared type gives number, throws a
TypeError.
createCipheriv and createDecipheriv support aes-128/192/256-ccm (and the
id-aesN-ccm names) over OpenSSL's ccm128 steps. They follow Node's tag and
IV lengths, the plaintextLength requirement, the message-length caps and
the one-update rule, and report a failed authentication from final().
They also support the RFC 3394 and RFC 5649 key wrap ciphers (aes-N-wrap,
aesN-wrap, id-aesN-wrap, the -pad forms and the -inv ciphers) over
wrap128.c's routines, with Node's lengths, integrity failures and
one-update rule.
The new names follow the existing ones in the cipher table, so the lookup
cost of the existing ciphers is unchanged.
crypto.subtle.wrapKey exports a secret key (raw, raw-secret or jwk) and
encrypts it, and unwrapKey decrypts and imports one, as Node's
webcrypto.js does: the wrap algorithm normalizes through the wrapKey
registry (AES-KW) and then the encrypt registry (AES-GCM, AES-CBC and
AES-CTR with their dictionaries), the wrapping key's algorithm and
usages are checked, and a JWK payload is its JSON text, padded with
spaces to a multiple of 8 bytes for AES-KW. A wrapped JWK that is not
UTF-8 JSON with a kty member rejects with Node's DataError.
AES-KW is RFC 3394 key wrap over the vendored mbedTLS block cipher, in
scr_webcrypto_aes.c beside the other AES modes: wrapping takes a
multiple of 8 bytes and at least 16, unwrapping a multiple of 8 and at
least 24, and the integrity check value is compared in constant time.
The cipher's failures, and unwrapKey's import errors, reject on a later
loop turn as Node's job promises do.
This completes jose's symmetric key management: 7132 runs the real
jose 6.2.12 PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW,
A128KW and A256KW paths (EncryptJWT and jwtDecrypt) against Node.
getPublicKey replaces wrapKey as the refused method in the fenced
fixture and the JavaScript refusal test.
crypto.createDiffieHellman, crypto.getDiffieHellman and
crypto.createDiffieHellmanGroup return native DiffieHellman and
DiffieHellmanGroup handles backed by mbedTLS bignums. generateKeys,
computeSecret, getPrime, getGenerator, getPublicKey, getPrivateKey,
setPublicKey, setPrivateKey and verifyError follow Node 24.22:
- the modp1, modp2, modp5 and modp14 through modp18 groups, with the
private key widths of Node's OpenSSL 3 path (200 to 400 bits for
modp5 and up, [2, p - 2] for modp1 and modp2);
- explicit primes and generators in every argument form and encoding,
with keys of bits(p) - 2 bits, or below min(2^N, q) when the
parameters equal an OpenSSL named group (RFC 3526, RFC 7919,
RFC 5114);
- generated safe primes with OpenSSL's congruences for the generator;
- verifyError's DH_check bits, computed when it is first read;
- Node's argument, generator, modulus, key range, state and encoding
errors, including the OpenSSL errors' library and reason properties.
Safe-prime generation sieves candidates with the odd primes below a
bound that grows with the prime's length and tests them with
Miller-Rabin rounds over a variable-time Montgomery exponentiation that
only reads public values; key and secret exponentiations stay on
mbedTLS's constant-time path. Primes longer than 8192 bits, property
reads other than verifyError, property writes, Object.prototype methods,
and calls whose output encoding is typed any or matches no @types/node
overload are refused.
The runtime unit scr_dh.c links, with the mbedTLS archive, only into
programs that use DiffieHellman. wasm32-wasi builds refuse it. The ECDH
unit's argument and encoding helpers move to scr_crypto_args.h, which
both units include.
readFileStream resolved on 'end' while its 'close' listener printed
"released". 'close' follows 'end' once the descriptor is closed, so the
line landed wherever that completion fell among the child processes
the fixture starts next, and the comparison with Node depended on
timing. The read now resolves from its 'close' listener, after the line
is printed.
readFileSync, writeFileSync, appendFileSync, statSync, lstatSync,
readdirSync, mkdirSync, rmSync, rmdirSync, mkdtempSync, accessSync,
unlinkSync, chmodSync, chownSync, lchownSync, truncateSync, copyFileSync,
renameSync, realpathSync, realpathSync.native, openSync and opendirSync
accept a file: URL wherever they accept a path. The URL converts as
fileURLToPath converts it, with Node's errors for other schemes, hosts,
encoded separators and null bytes.
Arguments after a URL path that have side effects are evaluated into
hidden locals before the conversion, so they run even when it throws. A
URL path after a string path, or with options that are not literals,
fails with SC2020, because Node checks those arguments before it converts
the URL. existsSync and the fs.promises functions also fail with SC2020
for URL paths: existsSync answers false for a URL it cannot convert, and
fs.promises reports conversion errors as rejections.
Fixes#1616
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
crypto.createECDH returns a native ECDH handle backed by mbedTLS for
prime192v1, secp224r1, prime256v1, secp384r1, secp521r1, secp192k1,
secp224k1, secp256k1, brainpoolP256r1, brainpoolP384r1 and
brainpoolP512r1. generateKeys, computeSecret, getPublicKey,
getPrivateKey, setPrivateKey, setPublicKey and ECDH.convertKey follow
Node 24: compressed, uncompressed and hybrid points, input and output
encodings, the private key range, key pair validation, and Node's error
codes and messages.
Curve names follow OpenSSL's short-name lookup: names of other curves
from crypto.getCurves() are refused at compile time when literal and
throw a coded SC2020 error when computed, other OpenSSL short names
throw ERR_CRYPTO_OPERATION_FAILED, and unknown names throw
ERR_CRYPTO_INVALID_CURVE. Calls whose arguments no @types/node overload
accepts, and computeSecret(view, encoding), are refused at compile time.
The runtime unit scr_ecdh.c links, with the mbedTLS archive, only into
programs that use ECDH. wasm32-wasi builds refuse it.
@types/node declares http_parser, v8, uv, zlib, ares and modules as plain
strings, but the runtime's versions object holds only node and openssl. The
dyn read was narrowed to the declared string with a checked extraction, so
comparisons with undefined folded to false and other uses threw. Such reads
now keep the undefined arm, like the optional stream properties, and
unannotated bindings initialized from them store the same union.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Merge origin/main (e9fb01603); the import lines at the top of
lowerer.ts conflicted and both are kept.
- Corpus 6385 gains a wrapper whose block-local `args` alias shadows an
outer one in the same function; each call forwards its own arguments.
A method call declared to return `this`, such as setEncoding, resolves to
its receiver's access path, so a 'data' listener on the stream that
`const out = child.stdout.setEncoding("utf8")` returns receives strings.
A setEncoding call that a preceding statement always evaluates (its
expression, a variable initializer, or the receiver of a method chain in
one of those) counts as preceding the listener. Only `new` and the
child_process factories (spawn, fork, exec, execFile) count as creating a
separate stream; any other call result may be a stream another binding
already holds.
6490 covers events.once with an AbortSignal: abort, an aborted signal, an
undefined signal, abort-listener order, 'removeListener' events, a
throwing 'newListener' listener and the settlement turns. 6491 covers
stream receivers (file streams, Readable.from, a destroyed PassThrough, a
Writable) and a NodeJS.ReadableStream part raced against its destination.
6492 covers races over events.once results with entries that only reject
and entries of different value types. 6493 covers stream subclasses whose
completion callbacks are typed `Function`. 6494 covers events.once over
checked-dynamic stream slots. The node-types fixture runs the race shapes
under @types/node, where events.once returns Promise<any[]>.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- matchInferSignature reads a fixed-tuple rest as its positions, so
(...args: [string, number]) needs two arguments, and a positional
any[] pattern parameter matches only when the function's parameter
there accepts an array, as TypeScript's assignability does
(corpus 6398).
- Merge origin/main (200fe8d22); node-v24-backlog.json regenerated.
- dns.lookup with options known only at run time (a checked-dynamic
value, or one cast to an overload's option type) may answer a string or,
with `all: true`, an address list, whatever the overload the checker
picked says: an inline callback now binds its parameters as
checked-dynamic values. Array.isArray tests a checker-narrowed dyn read's
dyn value instead of folding on the extracted type, so it never answers
false for an address list the declared type omits.
- net.connect({ lookup }) accepts a lookup of Node's own type
(net.LookupFunction: options plus a callback taking an address string
or list), called as Node's autoSelectFamily dial does ({ hints, all:
true }); the fallback declarations spell net.LookupFunction that way.
dns.lookup accepts Node's hints flags (ADDRCONFIG, V4MAPPED, ALL) and
passes them to getaddrinfo.
- A CommonJS export typed as the empty object literal (tsc's enum output
`exports.E || (exports.E = {})` after `exports.E = void 0`) rides
checked-dynamic storage, so the preamble's undefined no longer traps at
module load: @opentelemetry/api loads and its no-op metrics and trace
APIs run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>