// The builtin table as guests see it, plus the refused surfaces (node:http2, node:https, node:net). import { test, expect } from "@playwright/test"; import { BUILTIN_EXPORT_NAMES } from "../src/shared/builtin-names"; import { guestLines, runGuest } from "./guest"; test("every facade name is a defined export of its builtin", async ({ page }) => { const ids = Object.keys(BUILTIN_EXPORT_NAMES); const res = await runGuest(page, { "/app/main.mjs": [ ...ids.map((id, i) => `import * as m${i} from "node:${id}";`), `const table = ${JSON.stringify(BUILTIN_EXPORT_NAMES)};`, `const mods = [${ids.map((_, i) => `m${i}`).join(", ")}];`, "const missing = Object.keys(table).flatMap((id, i) => table[id].filter((n) => mods[i][n] === undefined).map((n) => `${id}.${n}`));", 'console.log(missing.length ? missing.join(" ") : "ok");', ].join("\n"), }, "/app/main.mjs"); expect([res.stdout, res.stderr]).toEqual(["ok\n", "(node:1) ExperimentalWarning: WASI is an experimental feature and might change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n"]); }); test("node:http2 imports and instanceof work and are always false", async ({ page }) => { const lines = await guestLines(page, { "/app/main.mjs": [ 'import { Http2ServerRequest, constants } from "node:http2";', "console.log(typeof Http2ServerRequest, ({}) instanceof Http2ServerRequest, constants.NGHTTP2_NO_ERROR);", ].join("\n"), }, "/app/main.mjs"); expect(lines).toEqual(["function false 0"]); }); test("every node:http2 factory throws ERR_NIB_UNSUPPORTED naming http2", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const http2 = require("node:http2");', 'for (const f of ["createServer", "createSecureServer", "connect"]) {', ' try { http2[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:http2")); }', "}", 'try { new http2.Http2ServerRequest(); } catch (e) { console.log("new", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "createServer ERR_NIB_UNSUPPORTED true", "createSecureServer ERR_NIB_UNSUPPORTED true", "connect ERR_NIB_UNSUPPORTED true", "new ERR_NIB_UNSUPPORTED", ]); }); test("dns query methods refuse; dns/promises.Resolver is an unconstructable class", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const dns = require("node:dns");', 'const dnsPromises = require("node:dns/promises");', 'for (const f of ["resolveMx", "resolveTxt", "getServers", "setServers"]) {', ' try { dns[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:dns")); }', "}", 'try { dns.promises.resolveCaa(); } catch (e) { console.log("promises.resolveCaa", e.code); }', 'try { dnsPromises.resolveSrv(); } catch (e) { console.log("dns/promises.resolveSrv", e.code); }', 'try { new dns.Resolver(); } catch (e) { console.log("new Resolver", e.code); }', 'try { new dnsPromises.Resolver(); } catch (e) { console.log("new promises.Resolver", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "resolveMx ERR_NIB_UNSUPPORTED true", "resolveTxt ERR_NIB_UNSUPPORTED true", "getServers ERR_NIB_UNSUPPORTED true", "setServers ERR_NIB_UNSUPPORTED true", "promises.resolveCaa ERR_NIB_UNSUPPORTED", "dns/promises.resolveSrv ERR_NIB_UNSUPPORTED", "new Resolver ERR_NIB_UNSUPPORTED", "new promises.Resolver ERR_NIB_UNSUPPORTED", ]); }); test("node:cluster reports a lone primary; forking and the Worker class refuse", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const cluster = require("node:cluster");', "console.log(cluster.isPrimary, cluster.isWorker, JSON.stringify(cluster.workers));", 'for (const f of ["fork", "disconnect", "setupPrimary"]) {', ' try { cluster[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:cluster")); }', "}", 'try { new cluster.Worker(); } catch (e) { console.log("new Worker", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "true false {}", "fork ERR_NIB_UNSUPPORTED true", "disconnect ERR_NIB_UNSUPPORTED true", "setupPrimary ERR_NIB_UNSUPPORTED true", "new Worker ERR_NIB_UNSUPPORTED", ]); }); test("node:dgram loads and every Socket operation refuses", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const dgram = require("node:dgram");', 'try { dgram.createSocket("udp4"); } catch (e) { console.log("createSocket", e.code, e.message.includes("node:dgram")); }', 'try { new dgram.Socket(); } catch (e) { console.log("new Socket", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["createSocket ERR_NIB_UNSUPPORTED true", "new Socket ERR_NIB_UNSUPPORTED"]); }); test("node:inspector and node:inspector/promises load, share their console/Network hooks, and refuse", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const inspector = require("node:inspector");', 'const inspectorPromises = require("node:inspector/promises");', "console.log(inspector.console === inspectorPromises.console, inspector.Session === inspectorPromises.Session);", 'try { inspector.open(); } catch (e) { console.log("open", e.code); }', 'try { inspector.console.table(); } catch (e) { console.log("console.table", e.code); }', 'try { new inspector.Session(); } catch (e) { console.log("new Session", e.code); }', 'try { new inspectorPromises.Session(); } catch (e) { console.log("new promises.Session", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "true false", "open ERR_NIB_UNSUPPORTED", "console.table ERR_NIB_UNSUPPORTED", "new Session ERR_NIB_UNSUPPORTED", "new promises.Session ERR_NIB_UNSUPPORTED", ]); }); // A hand-assembled preview1 module: fd_write("hello from wasi\n") to fd 1, then // proc_exit(3). Real Node 24 prints the line and start() returns 3 under returnOnExit. const HELLO_WASI = "AGFzbQEAAAABEANgBH9/f38Bf2AAAGABfwACRgIWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQhmZF93cml0ZQAAFndhc2lfc25hcHNob3RfcHJldmlldzEJcHJvY19leGl0AAIDAgEBBQMBAAEHEwIGbWVtb3J5AgAGX3N0YXJ0AAIKEwERAEEBQSBBAUEoEAAaQQMQAQsLIwIAQSALCDAAAAAQAAAAAEEwCxBoZWxsbyBmcm9tIHdhc2kK"; test("node:wasi runs a preview1 module and validates its version as Node does", async ({ page }) => { const res = await runGuest(page, { "/app/main.cjs": [ 'const { WASI } = require("node:wasi");', 'const wasi = new WASI({ version: "preview1", args: [], env: {}, returnOnExit: true });', `const mod = new WebAssembly.Module(Buffer.from("${HELLO_WASI}", "base64"));`, 'console.log("exit", wasi.start(new WebAssembly.Instance(mod, wasi.getImportObject())));', 'try { new WASI({}); } catch (e) { console.log(e.code, e.message); }', 'try { new WASI({ version: "preview2" }); } catch (e) { console.log(e.code, e.message); }', ].join("\n"), }, "/app/main.cjs"); expect(res.stdout.split("\n")).toEqual([ "hello from wasi", "exit 3", 'ERR_INVALID_ARG_TYPE The "options.version" property must be of type string. Received undefined', "ERR_INVALID_ARG_VALUE The property 'options.version' unsupported WASI version. Received 'preview2'", "", ]); expect(res.stderr).toContain("ExperimentalWarning: WASI is an experimental feature and might change at any time"); }); test("process.report.getReport() reports a glibc linux, as napi-rs and detect-libc read it", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ "const { header, ...rest } = process.report.getReport();", "console.log(header.platform, header.arch, header.osName, header.glibcVersionRuntime, header.nodejsVersion === process.version);", 'console.log(Object.keys(rest).join(","));', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "linux x64 Linux 2.39 true", "javascriptStack,javascriptHeap,nativeStack,resourceUsage,uvthreadResourceUsage,libuv,workers,environmentVariables,userLimits,sharedObjects", ]); }); test("node:sea refuses and is node:-scheme only, like Node", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const sea = require("node:sea");', 'try { sea.isSea(); } catch (e) { console.log("isSea", e.code); }', 'try { require("sea"); console.log("bare returned"); } catch (e) { console.log("bare", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["isSea ERR_NIB_UNSUPPORTED", "bare MODULE_NOT_FOUND"]); }); test("node:trace_events refuses", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const traceEvents = require("node:trace_events");', 'try { traceEvents.createTracing({ categories: ["node"] }); } catch (e) { console.log("createTracing", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["createTracing ERR_NIB_UNSUPPORTED"]); }); test("node:module's off-thread and load hooks and native addons refuse", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const Module = require("module");', 'try { Module.register("./x.mjs"); } catch (e) { console.log("register", e.code); }', 'try { Module.registerHooks({ load() {} }); } catch (e) { console.log("registerHooks load", e.code); }', 'try { Module._extensions[".node"]({ exports: {} }, "/app/x.node"); } catch (e) { console.log("node ext", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["register ERR_NIB_UNSUPPORTED", "registerHooks load ERR_NIB_UNSUPPORTED", "node ext ERR_NIB_UNSUPPORTED"]); }); test("registerHooks resolve hooks run for import() and import.meta.resolve, last registered first, until deregistered", async ({ page }) => { const lines = await guestLines(page, { "/app/a.mjs": 'export default "a";', "/app/b.mjs": 'export default "b";', "/app/main.mjs": [ 'import { registerHooks } from "node:module";', 'const outer = registerHooks({ resolve: (s, c, next) => next(s === "virtual:x" ? "./a.mjs" : s, c) });', 'const inner = registerHooks({ resolve: (s, c, next) => next(s === "virtual:y" ? "virtual:x" : s, c) });', 'console.log((await import("virtual:y")).default, (await import("virtual:x")).default, (await import("./b.mjs")).default);', 'console.log(import.meta.resolve("virtual:y"));', 'inner.deregister();', 'try { await import("virtual:y"); } catch (e) { console.log("after deregister", e.code); }', 'outer.deregister();', ].join("\n"), }, "/app/main.mjs"); expect(lines).toEqual(["a a b", "file:///app/a.mjs", "after deregister ERR_MODULE_NOT_FOUND"]); }); test("import() takes a file: URL, and each ?query of one is a module of its own", async ({ page }) => { const lines = await guestLines(page, { "/app/config.mjs": 'export const loaded = Symbol("loaded");', "/app/main.mjs": [ 'const one = await import("file:///app/config.mjs?t=1");', 'const again = await import("file:///app/config.mjs?t=1");', 'const two = await import("./config.mjs?t=2");', 'const plain = await import("file:///app/config.mjs");', 'console.log(one === again, one.loaded === two.loaded, plain.loaded === one.loaded);', ].join("\n"), }, "/app/main.mjs"); expect(lines).toEqual(["true false false"]); }); test("node:vm refuses every realm-creating entry point, naming the missing primitive", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const vm = require("vm");', 'const script = new vm.Script("1");', 'for (const [name, fn] of [', ' ["createContext", () => vm.createContext({})],', ' ["runInContext", () => vm.runInContext("1", {})],', ' ["runInNewContext", () => vm.runInNewContext("1", {})],', ' ["Script#runInContext", () => script.runInContext({})],', ' ["Script#runInNewContext", () => script.runInNewContext({})],', ' ["Script#createCachedData", () => script.createCachedData()],', ' ["runInThisContext timeout", () => script.runInThisContext({ timeout: 10 })],', ']) { try { fn(); console.log(name, "returned"); } catch (e) { console.log(name, e.code); } }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "createContext ERR_NIB_UNSUPPORTED", "runInContext ERR_NIB_UNSUPPORTED", "runInNewContext ERR_NIB_UNSUPPORTED", "Script#runInContext ERR_NIB_UNSUPPORTED", "Script#runInNewContext ERR_NIB_UNSUPPORTED", "Script#createCachedData ERR_NIB_UNSUPPORTED", "runInThisContext timeout ERR_NIB_UNSUPPORTED", ]); }); test("an unknown builtin fails to resolve with Node's code", async ({ page }) => { const res = await runGuest(page, { "/app/main.cjs": 'require("node:nope");\n' }, "/app/main.cjs"); expect(res.code).toBe(1); expect(res.stderr).toContain("ERR_UNKNOWN_BUILTIN_MODULE"); }); test("node:net sockets and servers refuse; guests reach the network through http", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const net = require("node:net");', 'for (const f of ["createServer", "connect", "createConnection"]) {', ' try { net[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:net")); }', "}", 'try { new net.Socket(); } catch (e) { console.log("new Socket", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "createServer ERR_NIB_UNSUPPORTED true", "connect ERR_NIB_UNSUPPORTED true", "createConnection ERR_NIB_UNSUPPORTED true", "new Socket ERR_NIB_UNSUPPORTED", ]); }); test("assert.rejects' error-object matcher deep-compares the rejection's own keys", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const assert = require("node:assert");', 'assert.rejects(async () => { throw new Error("nope"); }, { name: "TypeError" })', ' .then(() => console.log("passed"))', ' .catch((e) => console.log(e.code, e.operator, e instanceof assert.AssertionError));', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["ERR_ASSERTION rejects true"]); }); test("node:https globalAgent is an Agent-shaped object, never constructed", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const https = require("node:https");', "console.log(typeof https.globalAgent, https.globalAgent instanceof https.Agent);", ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["object true"]); }); test("node:http2 performServerHandshake refuses", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const http2 = require("node:http2");', 'try { http2.performServerHandshake(); } catch (e) { console.log(e.code, e.message.includes("node:http2")); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["ERR_NIB_UNSUPPORTED true"]); }); test("node:tls's TLS-only surface refuses alongside its sockets and servers", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const tls = require("node:tls");', 'for (const f of ["getCiphers", "getCACertificates", "setDefaultCACertificates", "convertALPNProtocols"]) {', ' try { tls[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:tls")); }', "}", 'try { new tls.SecureContext(); } catch (e) { console.log("new SecureContext", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "getCiphers ERR_NIB_UNSUPPORTED true", "getCACertificates ERR_NIB_UNSUPPORTED true", "setDefaultCACertificates ERR_NIB_UNSUPPORTED true", "convertALPNProtocols ERR_NIB_UNSUPPORTED true", "new SecureContext ERR_NIB_UNSUPPORTED", ]); }); test("node:v8's internals (heap, coverage, profiling, promise hooks) refuse", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const v8 = require("node:v8");', 'for (const f of ["takeCoverage", "stopCoverage", "queryObjects", "startCpuProfile", "getCppHeapStatistics", "getHeapCodeStatistics", "isStringOneByteRepresentation", "setHeapSnapshotNearHeapLimit"]) {', ' try { v8[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:v8")); }', "}", 'try { v8.promiseHooks.createHook(); } catch (e) { console.log("promiseHooks.createHook", e.code); }', 'try { new v8.GCProfiler(); } catch (e) { console.log("new GCProfiler", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "takeCoverage ERR_NIB_UNSUPPORTED true", "stopCoverage ERR_NIB_UNSUPPORTED true", "queryObjects ERR_NIB_UNSUPPORTED true", "startCpuProfile ERR_NIB_UNSUPPORTED true", "getCppHeapStatistics ERR_NIB_UNSUPPORTED true", "getHeapCodeStatistics ERR_NIB_UNSUPPORTED true", "isStringOneByteRepresentation ERR_NIB_UNSUPPORTED true", "setHeapSnapshotNearHeapLimit ERR_NIB_UNSUPPORTED true", "promiseHooks.createHook ERR_NIB_UNSUPPORTED", "new GCProfiler ERR_NIB_UNSUPPORTED", ]); }); test("node:process's user and native-image syscalls refuse", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'for (const f of ["dlopen", "execve", "initgroups", "setegid", "seteuid", "setgid", "setgroups", "setuid"]) {', ' try { process[f](); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:process")); }', "}", ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "dlopen ERR_NIB_UNSUPPORTED true", "execve ERR_NIB_UNSUPPORTED true", "initgroups ERR_NIB_UNSUPPORTED true", "setegid ERR_NIB_UNSUPPORTED true", "seteuid ERR_NIB_UNSUPPORTED true", "setgid ERR_NIB_UNSUPPORTED true", "setgroups ERR_NIB_UNSUPPORTED true", "setuid ERR_NIB_UNSUPPORTED true", ]); }); test("node:https loads and every factory refuses", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const https = require("node:https");', 'for (const f of ["createServer", "request", "get"]) {', ' try { https[f]("https://example.com"); console.log(f, "returned"); } catch (e) { console.log(f, e.code, e.message.includes("node:https")); }', "}", 'try { new https.Agent(); } catch (e) { console.log("new Agent", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "createServer ERR_NIB_UNSUPPORTED true", "request ERR_NIB_UNSUPPORTED true", "get ERR_NIB_UNSUPPORTED true", "new Agent ERR_NIB_UNSUPPORTED", ]); }); test("node:crypto's sync-only and asymmetric surface refuses: ciphers, signing, DH/ECDH, certs", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const crypto = require("node:crypto");', 'try { crypto.createCipheriv("aes-128-cbc", Buffer.alloc(16), Buffer.alloc(16)); } catch (e) { console.log("createCipheriv", e.code, e.message.includes("node:crypto")); }', 'try { new crypto.Cipheriv("aes-128-cbc", Buffer.alloc(16), Buffer.alloc(16)); } catch (e) { console.log("new Cipheriv", e.code); }', 'console.log("Cipheriv extends Transform", Object.getPrototypeOf(crypto.Cipheriv.prototype) === require("node:stream").Transform.prototype);', 'try { new crypto.Sign("sha256"); } catch (e) { console.log("new Sign", e.code); }', 'console.log("Sign extends Writable", Object.getPrototypeOf(crypto.Sign.prototype) === require("node:stream").Writable.prototype);', 'try { crypto.sign("sha256", Buffer.from("x"), {}); } catch (e) { console.log("sign", e.code); }', 'try { new crypto.DiffieHellman(256); } catch (e) { console.log("new DiffieHellman", e.code); }', 'try { crypto.createDiffieHellman(256); } catch (e) { console.log("createDiffieHellman", e.code); }', 'try { new crypto.ECDH("secp256k1"); } catch (e) { console.log("new ECDH", e.code); }', 'try { new crypto.X509Certificate(Buffer.alloc(0)); } catch (e) { console.log("new X509Certificate", e.code); }', 'try { crypto.publicEncrypt({key: ""}, Buffer.alloc(1)); } catch (e) { console.log("publicEncrypt", e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "createCipheriv ERR_NIB_UNSUPPORTED true", "new Cipheriv ERR_NIB_UNSUPPORTED", "Cipheriv extends Transform true", "new Sign ERR_NIB_UNSUPPORTED", "Sign extends Writable true", "sign ERR_NIB_UNSUPPORTED", "new DiffieHellman ERR_NIB_UNSUPPORTED", "createDiffieHellman ERR_NIB_UNSUPPORTED", "new ECDH ERR_NIB_UNSUPPORTED", "new X509Certificate ERR_NIB_UNSUPPORTED", "publicEncrypt ERR_NIB_UNSUPPORTED", ]); }); test("node:crypto's Hash and Hmac are Transform streams: write()/pipe() work, not just update()/digest()", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const crypto = require("node:crypto");', 'const { PassThrough } = require("node:stream");', 'console.log("Hash instanceof Transform", crypto.createHash("sha256") instanceof require("node:stream").Transform);', "(async () => {", ' const h = crypto.createHash("sha256");', ' const chunks = [];', ' h.on("data", (c) => chunks.push(c));', ' const hashDone = new Promise((resolve) => h.on("end", resolve));', ' const src = new PassThrough();', " src.pipe(h);", ' src.end("abc");', " await hashDone;", ' console.log("piped digest", Buffer.concat(chunks).toString("hex"), chunks.length);', ' console.log("matches update/digest", Buffer.concat(chunks).toString("hex") === crypto.createHash("sha256").update("abc").digest("hex"));', ' const hm = crypto.createHmac("sha256", "key");', ' const hmChunks = [];', ' hm.on("data", (c) => hmChunks.push(c));', ' const hmacDone = new Promise((resolve) => hm.on("end", resolve));', ' hm.end("abc");', " await hmacDone;", ' console.log("hmac piped", Buffer.concat(hmChunks).toString("hex") === crypto.createHmac("sha256", "key").update("abc").digest("hex"));', "})();", ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "Hash instanceof Transform true", "piped digest ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 1", "matches update/digest true", "hmac piped true", ]); }); test("node:crypto's FIPS mode, curves and cipher table refuse: no cipher or curve works here", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const crypto = require("node:crypto");', 'console.log("getFips", crypto.getFips());', 'crypto.setFips(false);', 'for (const [name, call] of [["setFips(true)", () => crypto.setFips(true)], ["getCurves", () => crypto.getCurves()], ["getCipherInfo", () => crypto.getCipherInfo("aes-128-cbc")], ["getCiphers", () => crypto.getCiphers()]]) {', ' try { call(); console.log(name, "returned"); } catch (e) { console.log(name, e.code); }', "}", ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["getFips 0", "setFips(true) ERR_NIB_UNSUPPORTED", "getCurves ERR_NIB_UNSUPPORTED", "getCipherInfo ERR_NIB_UNSUPPORTED", "getCiphers ERR_NIB_UNSUPPORTED"]); }); test("node:crypto's hkdf refuses digests WebCrypto's HKDF can't do (md5), unlike Node/OpenSSL", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const crypto = require("node:crypto");', 'try { crypto.hkdf("md5", "a", "b", "c", 16, () => {}); console.log("did not throw"); } catch (e) { console.log(e.code); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["ERR_CRYPTO_INVALID_DIGEST"]); }); // ClientRequest's real constructor starts an (unobservable here) network attempt, // so this can't be golden-checked against Node without a flaky, network-dependent // fixture: it's a pure refusal assertion instead. test("node:http's ClientRequest refuses to construct: there is no client to drive it", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const http = require("node:http");', 'try { new http.ClientRequest({}); } catch (e) { console.log(e.code, e.message.includes("node:http")); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["ERR_NIB_UNSUPPORTED true"]); }); // A throwing getter here would break every ESM `import ... from "node:tls"` (the // kernel's ESM facade reads every named export eagerly), so this is a frozen empty // array instead — a known, documented compromise (see the report), not a refusal. test("node:tls's rootCertificates is a frozen, empty placeholder, not a refusal", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const tls = require("node:tls");', "console.log(Array.isArray(tls.rootCertificates), tls.rootCertificates.length, Object.isFrozen(tls.rootCertificates));", ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["true 0 true"]); }); test("node:tls's rootCertificates doesn't break importing node:tls as ESM", async ({ page }) => { const lines = await guestLines(page, { "/app/main.mjs": [ 'import tls, { rootCertificates } from "node:tls";', "console.log(typeof tls, rootCertificates.length);", ].join("\n"), }, "/app/main.mjs"); expect(lines).toEqual(["object 0"]); }); // Runtime-only (not golden): real Node's chown needs root and fails EPERM for an // ordinary user, and real chmod genuinely changes the file's real mode — neither is a // stable or even a true "no-op" ground truth to record. Here, modes and owners aren't // stored at all, so chmod/chown (and their f-/l- forms) are a deliberate no-op: stat // keeps reporting its fixed value, but the target still has to exist. link and statfs // also diverge on purpose — the VFS has no hard links and no backing store to size — // where real Node's versions of both would simply succeed. test("chmod/chown (and f-/l- forms) are no-ops that still check the target exists; link and statfs refuse", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const fs = require("node:fs");', 'fs.writeFileSync("noop.txt", "x");', 'const before = fs.statSync("noop.txt").mode;', 'fs.chmodSync("noop.txt", 0o400);', 'fs.chownSync("noop.txt", 1234, 5678);', 'console.log("chmod/chownSync mode unchanged", fs.statSync("noop.txt").mode === before);', 'fs.lchmodSync("noop.txt", 0o400);', 'fs.lchownSync("noop.txt", 1, 1);', 'console.log("lchmod/lchownSync mode unchanged", fs.statSync("noop.txt").mode === before);', 'const fd = fs.openSync("noop.txt", "r");', 'fs.fchmodSync(fd, 0o400);', 'fs.fchownSync(fd, 1, 1);', 'console.log("fchmod/fchownSync mode unchanged", fs.fstatSync(fd).mode === before);', 'fs.closeSync(fd);', 'try { fs.chmodSync("nope.txt", 0o600); } catch (e) { console.log("chmod missing", e.code, e.syscall); }', 'try { fs.fchmodSync(999999, 0o600); } catch (e) { console.log("fchmod bad fd", e.code, e.syscall); }', 'try { fs.linkSync("noop.txt", "noop2.txt"); } catch (e) { console.log("linkSync", e.code, e.message.includes("node:fs")); }', 'try { fs.statfsSync("."); } catch (e) { console.log("statfsSync", e.code, e.message.includes("node:fs")); }', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "chmod/chownSync mode unchanged true", "lchmod/lchownSync mode unchanged true", "fchmod/fchownSync mode unchanged true", "chmod missing ENOENT chmod", "fchmod bad fd EBADF fchmod", "linkSync ERR_NIB_UNSUPPORTED true", "statfsSync ERR_NIB_UNSUPPORTED true", ]); }); test("events.getEventListeners refuses on a native EventTarget (no public listener list)", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const { getEventListeners, getMaxListeners, setMaxListeners } = require("node:events");', "const target = new EventTarget();", 'try { getEventListeners(target, "x"); } catch (e) { console.log(e.code, e.message.includes("EventTarget")); }', // getMaxListeners/setMaxListeners are supported for a native EventTarget (just // bookkeeping, like the unenforced leak warning on our own EventEmitter). "setMaxListeners(5, target);", 'console.log("max", getMaxListeners(target));', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual(["ERR_NIB_UNSUPPORTED true", "max 5"]); }); // node:test's run() with process isolation (the default), coverage or watch: none of // these can be golden'd, since real Node actually performs them (spawns children, // enables coverage, starts a watcher) instead of throwing — see runner.ts's header // comment and compat/divergences.json's "test.run". Asserted directly here instead, // per the brief. test("node:test run() refuses process isolation (the default), coverage and watch, after validating every option", async ({ page }) => { const lines = await guestLines(page, { "/app/main.cjs": [ 'const { run } = require("node:test");', // Validation still wins over the refusal: a bad option throws its own // ERR_INVALID_ARG_TYPE/ERR_INVALID_ARG_VALUE, never the refusal, even though // every one of these calls would otherwise hit it. 'try { run({ isolation: "process", files: "nope" }); } catch (e) { console.log("bad option first", e.code); }', // isolation: 'process' is the default — omitting it refuses the same way. 'try { run({}); } catch (e) { console.log("default isolation", e.code, e.message.includes("node:test"), e.message.includes("v8.Serializer")); }', 'try { run({ isolation: "process" }); } catch (e) { console.log("explicit process", e.code); }', 'try { run({ isolation: "none", coverage: true }); } catch (e) { console.log("coverage", e.code, e.message.includes("inspector")); }', 'try { run({ isolation: "none", watch: true }); } catch (e) { console.log("watch", e.code, e.message.includes("FilesWatcher")); }', // isolation: 'none' with nothing unsupported set does not refuse — it is a // plain function call returning a TestsStream synchronously. 'const s = run({ isolation: "none", files: [] });', 'console.log("none ok", s.constructor.name, typeof s.on);', ].join("\n"), }, "/app/main.cjs"); expect(lines).toEqual([ "bad option first ERR_INVALID_ARG_TYPE", "default isolation ERR_NIB_UNSUPPORTED true true", "explicit process ERR_NIB_UNSUPPORTED", "coverage ERR_NIB_UNSUPPORTED true", "watch ERR_NIB_UNSUPPORTED true", "none ok TestsStream function", ]); });