Commit Graph
79 Commits
Author SHA1 Message Date
Chris Tate 3b951ef4e2 Let objects inherit from JavaScript class prototypes 2026-10-23 23:43:30 -05:00
Chris Tate 37df3c4dfd Support split CommonJS require bindings (#1945) 2026-10-23 19:50:13 -05:00
Chris Tate 02597fa3e8 Merge main and give split require cycles the const form's admission 2026-10-23 17:15:33 -05:00
Luis Alvarez D 70e6e9b591 Fence split requires that close a CommonJS cycle 2026-10-23 11:40:15 -05:00
Chris Tate 313eb1177c Merge main and fix CI 2026-10-23 09:14:31 -05:00
Chris Tate 19e0fe8bcd Merge main and read cipher options through getters and prototypes 2026-10-23 05:33:14 -05:00
Chris Tate b9275c2693 buffer: fromArrayBuffer ranges, the deprecated constructor, Buffer as a value 2026-10-23 03:05:19 -05:00
Chris Tate 75cf8f0e52 Run zod 4's core constructor factory in the dynamic tier (#1918) 2026-10-22 22:08:46 -05:00
Chris Tate 77c0636826 Include star re-exports in namespace objects, and read causes and instanceof Error like Node under a runtime Error parent
- A module namespace converted to an untyped object, and a computed
  namespace read, use the module's resolved exports, which include the
  members of `export *` declarations.
- `super(message, options)` through a runtime Error parent installs the
  cause as Error does: an inherited `cause` counts, the value is read
  through its getter, and the getter's exception propagates.
- `value instanceof Error` on an instance of a class whose heritage is
  evaluated at run time tests that class's prototype chain for
  Error.prototype. A program with such a class checks prototype chains
  for `instanceof Error` on untyped values.
- The cache of names written on the global object is a Lowerer field
  instead of a WeakMap keyed by the Lowerer.
- errors.test.ts: `"prototype" in Map` answers from the builtin
  constructor's keys now, so it leaves the refused reads and joins a
  check of `in` probes against Node's answers.
2026-10-22 22:00:01 -05:00
Chris Tate 09c132fb2e Merge main 2026-10-22 21:55:35 -05:00
Chris Tate e665913200 Print accessor properties of arrays and fence number keys in defineProperty
- An enumerable getter or setter on an array printed as undefined: the
  property loop rendered the accessor's unused data slot. It now prints
  [Getter], [Setter] or [Getter/Setter], as util.inspect does.
- Object.defineProperty on a live native array with a number key that is
  not an array index (-3, 1.5) skipped the numeric-property fence and
  lost the property on commit. A number key now takes the same fence as
  its string spelling.
2026-10-22 21:39:37 -05:00
Chris Tate 1501559d4a Read and write the "undefined" property for element indexes that are undefined
An element index the checker types as a number can hold undefined at run
time (an element read from a holey array). JavaScript then names the
array's "undefined" property, but the index converted with ToNumber, so
such reads and writes used the "NaN" property: Node prints `10 0,7,0,7`
for a program that sets `values[NaN] = 10` and then writes through two
holes, the compiled program printed `7 0,7,0,7`.

The runtime's noncanonical property table, which holds the properties
named by non-index numbers, now spells one NaN payload
(scr_arr_undefined_key) "undefined". At array element reads, writes,
compound and update writes, destructuring targets and writes in value
position, an index that can be undefined is that key when it is
undefined. The property stays a typed entry of the array, so the Int32[]
contract checks cover it like any element store.

A typed array cannot hold the property: a write through an undefined
index throws the catchable SC1090 error where Node adds it.

Fixes #1640.
2026-10-22 21:34:06 -05:00
Chris Tate dcd86a17da Merge main 2026-10-22 20:52:05 -05:00
Chris Tate 846552b0e2 Support crypto.getCipherInfo, AES-CCM and the AES key wrap ciphers
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.
2026-10-22 17:18:54 -05:00
Chris Tate 20f4d1ff12 Support the legacy createCipheriv modes and crypto.checkPrime/generatePrime 2026-10-22 09:08:35 -05:00
Chris Tate e6115f87e8 Merge main 2026-10-22 07:22:32 -05:00
Chris Tate 714dbfe2bb Merge main 2026-10-22 06:59:47 -05:00
Chris Tate 75bcf9dfa7 Merge main 2026-10-22 06:55:45 -05:00
Chris Tate df6de14c94 Support new tls.TLSSocket and tls.connect({ socket }) (#1791) 2026-10-22 06:01:47 -05:00
Chris Tate 3cefd421a1 Fence requestCert beside a secureContext and free failed TLSSockets
new tls.TLSSocket(socket, { isServer: true, secureContext, requestCert:
true }) skipped the options walk that refuses requestCert, so the server
asked for no client certificate and accepted every client, even with
rejectUnauthorized: true. With a secureContext, Node still reads the
socket's own options, so a truthy requestCert, SNICallback and
ALPNCallback now throw the fence there too.

A TLS socket's transport kept its 'secure' and 'session' listeners until
the socket was freed. A handshake that fails never fires 'secure', so a
listener that captures the socket kept the socket and its TLS state alive
after it closed. The transport now drops those listeners when the socket
settles, and ignores listeners added after that.
2026-10-22 05:58:32 -05:00
Chris Tate 01a467060b Support WebCrypto wrapKey and unwrapKey
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.
2026-10-22 05:48:30 -05:00
Chris Tate 1360830149 Support new tls.TLSSocket and tls.connect({ socket }) 2026-10-22 04:52:44 -05:00
Chris Tate 6e753cd5c8 Merge main 2026-10-22 04:08:52 -05:00
Chris Tate b5fb4115e1 Merge main 2026-10-22 03:33:29 -05:00
Chris Tate 1773ffbef3 WebCrypto: the crypto global, node:crypto's webcrypto, and secret-key SubtleCrypto 2026-10-22 02:52:01 -05:00
Chris Tateandsaboursepideh 1a240fbf10 Support asymmetric keys, signatures and Ed25519 in node:crypto and WebCrypto
Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-22 02:49:33 -05:00
Chris Tate 054cb50ea4 Array keys and properties: non-index number keys, Object.keys, console.log 2026-10-22 02:31:20 -05:00
Chris Tate 283a504fe0 Support crypto.createDiffieHellman and getDiffieHellman
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.
2026-10-21 21:55:47 -05:00
Chris Tate 0950725949 Refuse elements on Array.prototype 2026-10-21 17:24:17 -05:00
Chris Tate 08c7930958 Support crypto.createECDH and ECDH.convertKey (#1589) 2026-10-20 20:18:22 -05:00
Chris Tate 4e80351f87 Merge main 2026-10-20 19:53:23 -05:00
Chris Tate 2582e46e76 Support crypto.createECDH and ECDH.convertKey
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.
2026-10-20 19:17:05 -05:00
Chris Tate 450c8ac2a5 Gate thenable adoption on then fields, keep Promise.prototype bindings checked, and cache the gate on the lowerer 2026-10-20 17:59:43 -05:00
Chris Tate e0d14fefaa Merge main 2026-10-20 12:55:55 -05:00
Chris Tate 5aa4ccc3f2 Update tests for the EvalError and builtin module changes 2026-10-20 11:28:48 -05:00
Chris Tate 68a3d8f846 Merge main 2026-10-20 09:06:21 -05:00
Chris Tate 9e54b4a198 Merge main 2026-10-20 09:00:02 -05:00
Chris Tate c475aabc2a Refuse arguments to inherited stream constructors from untyped code
Construction from checked-dynamic code (`new F(options)` with F an
untyped value holding a ctor-less subclass of a stream class or
EventEmitter) dispatched to the class's constructor and completed the
arguments against the inherited signature, which takes none here, so the
options were dropped. The checked construction dispatch now reports
those arguments at run time with the message typed construction reports
at compile time, located at the class: a fixed argument count refuses at
the call, and a spread call refuses when its argument list is not empty.
2026-10-20 08:41:04 -05:00
Malte UblandClaude Opus 5.5 4b82886458 Keep builtin constructor members refused instead of reading undefined
Reading Object, Date, RegExp, Map, Set, Promise, WeakMap and WeakSet as
interned function values made every use of those globals a dynamic value,
including the receivers of member reads. The interned function models only
name, length and constructor, so `Map.prototype`, `Date.prototype.getYear`,
`Object.isFrozen` as a value, `Map["prototype"]` and `"prototype" in Map`
stopped reaching their SC2020/SC1090 fences and read undefined through the
dynamic paths instead (769 Test262 variants went from a refusal to a wrong
result).

A builtin global read for its members now keeps its member lowerings and
fences; only name, length and constructor read through the value. A member
read through an alias typed as the constructor's static side (`const D =
Date; D.prototype`) reports SC2020 before the generic field read. Passing
the value to Object or Reflect statics that reflect on or redefine its
members (`Object.hasOwn(Object, ...)`, `Object.defineProperty(Promise,
...)`, `hasOwnProperty.call(Map, ...)`) is refused; identity and the empty
enumerable key lists still compile. The interned functions answer Node's
lengths (Date 7, RegExp 2, Promise 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:23:28 -07:00
Chris Tate ac0ae29b18 Construct CommonJS constructor functions imported through a member alias (#1453) 2026-10-20 00:10:40 -05:00
Chris Tate 59dd6d1742 Read member aliases only while the binding holds the exported value
A member export resolves to its binding only when the binding is declared
before the write and never reassigned, so a consumer reads the value the
member stored. A module that re-exports another module's exports through
module.exports = require("./x") now counts its own writes after the
re-exported module's: a member it adds resolves, and a member both modules
write stays refused. The exports rebinding refusal and its test now cover
the form that stays refused, exports = value without module.exports.
2026-10-20 00:02:49 -05:00
Chris Tate b690f8f8f4 Count destructuring and for-of writes of a factory's parameter
A factory's parameter counts as the argument it was called with only
while the factory can't change it. The check saw assignments and updates
of the name, but not a write through a destructuring pattern
(`[body] = ...`, `({ body } = ...)`) or a for-in or for-of target, so a
factory could replace its callback with one that reads `this` and the
call still compiled without the module object.

The parameter now counts as changed by any assignment, update,
destructuring or for-in/for-of target that writes it, by a declaration
of the same name, by a write to an element of `arguments`, and by a call
of `eval`.
2026-10-19 23:56:22 -05:00
Chris Tate 751d22ae6e Pass this on to module exports only where every function that gets it ignores it
The exemption for `f.apply(this, ...)` and `f.call(this, ...)` dropped
SC1090 for every module export that passes `this` on. When the function
it reaches reads `this`, Node gives it the module object, and the direct
module call has none, so the result differed from Node.

A `this` that is passed on now counts as unread only when the function
it goes to provably ignores its `this`: an arrow, a function that uses
`this` only to pass it on to such functions, or what a known function
returns, with its parameters bound to the call's arguments and never
assigned. The analysis follows at most six calls and functions, and
refuses otherwise. Effect 4's provideService still compiles: it passes
`this` to what dual(3, body) returns, which passes it on only to the
arrow it was given.

Passing `this` on to a function that reads it now refuses, directly,
through such a factory, or when the factory reassigns its parameter.
2026-10-19 23:09:08 -05:00
Chris Tate 6dd82db8ff Compile module exports that pass this on with apply or call 2026-10-19 21:59:58 -05:00
Chris Tate bb6270efc1 Fix silent divergences: constructor returns, arr && arr[0], ES5 class names, builtin function values, uninitialized let writes, module-export this, 1 ** Infinity (#1351) 2026-10-19 19:56:36 -05:00
Malte UblandClaude Opus 5.5 2b8cd22423 Add WebCrypto HMAC: crypto.subtle importKey, sign, verify and digest
crypto.subtle.importKey('raw', ...) for HMAC keys, HMAC sign and verify,
and the SHA-1/SHA-2 digests compile through the crypto global, with
CryptoKey values as native handles. scr_webcrypto.c is only the WebCrypto
layer (Web IDL conversion, algorithm normalization, the CryptoKey handle,
promise wrapping and Node's TypeErrors and DOMExceptions) over node:crypto's
digest core and deferred promise queue. It links only into programs that
call these methods (the new webcrypto link feature). Other algorithms, the
jwk format and the other SubtleCrypto methods are SC2020 refusals.

The real jose@6.2.12 HS256 path (SignJWT, jwtVerify) now runs natively. It
also needed four compiler fixes: object patterns of positions and length
over arrays, the Uint8Array base64/hex feature probes, a compile-time
String.fromCharCode.apply, and structuredClone of a parameter whose default
is {}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:26 -07:00
Malte Ubl 63d04d3a70 Merge remote-tracking branch 'origin/main' into opt/silent-bugs
# Conflicts:
#	tests/harness/errors.test.ts
2026-10-18 17:32:39 -07:00
Chris Tate 707ac5cf77 Carry Hash and Hmac values through unknown and any slots (#1257) 2026-10-18 18:38:52 -05:00
Malte UblandClaude Opus 5.5 8a89024e7b Refuse module exports that read this when called through the module binding
`m.f()` where `m` is `const m = require("./x")` or `import * as m from
"./x"` lowers to a direct call of the exported function, so a function
that reads `this` saw no receiver (or another frame's) where Node passes
the module object: `exports.hello = function () { return this.name; }`
threw a TypeError natively. Such calls now refuse with SC1090 (deferred
in JavaScript); exports that do not read this compile as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:41:11 -07:00
Malte UblandClaude Opus 5.5 11c97074e2 Answer the object a constructor returns from new
A class constructor's `return expr` was evaluated and dropped, so
`new C()` answered the constructed instance even where Node answers the
returned object (`if (C.instance) return C.instance;` built a second
instance).

A return that yields an instance of the class itself stores it in a
per-class result slot, read and cleared by one helper at every
construction site (direct new, class values, the checked dynamic
construction dispatchers, record-to-class coercion). A return of any
other object refuses with SC1090 (compile time in TypeScript, a
deferred fence in JavaScript); a JavaScript value typed only at run time
is tested when it returns. super() into a class that can return an
existing instance refuses, since Node would rebind the subclass's this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:42:18 -07:00