From 51c09eae6c176f6a41efcf4f53be7d486f2cb4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Estrada?= Date: Tue, 21 Jul 2026 00:41:30 -0500 Subject: [PATCH 1/7] src,permission: do not throw on denied access in audit mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The THROW_IF_INSUFFICIENT_PERMISSIONS and ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS macros called ThrowAccessDenied/AsyncThrowAccessDenied unconditionally and only guarded the `return` with `warning_only()`. ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS had no `warning_only()` guard at all — it always set the access-denied error and returned. As a result, running with `--permission-audit` still produced ERR_ACCESS_DENIED on any denied operation (fs, net, child_process, worker, addon, ffi, inspector, wasi), defeating the audit-only purpose of the flag. Guard the denied-error path behind `!warning_only()` in all three macros. In audit mode, the diagnostics-channel message is published (already done in Permission::is_scope_granted) and execution continues; in enforce mode (`--permission`), behavior is unchanged — the error is raised and the call returns. The tests cover both the direct (top-level) call and an `eval()`-wrapped call: the direct call exercises the normal script path, and the `eval()`-wrapped call exercises the V8 script-context boundary (the diagnostics subscriber is registered in the outer module context while the denied operation runs inside an eval'd string). Refs: https://github.com/nodejs/node/commit/9ddd1a9c27c253f46d587a8c906ccd83417b4606 Signed-off-by: Adrian Estrada PR-URL: https://github.com/nodejs/node/pull/64426 Reviewed-By: Rafael Gonzaga --- src/permission/permission.h | 33 +++++---- .../test-permission-audit-fs-does-not-deny.js | 59 ++++++++++++++++ ...test-permission-audit-net-does-not-deny.js | 69 +++++++++++++++++++ 3 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 test/parallel/test-permission-audit-fs-does-not-deny.js create mode 100644 test/parallel/test-permission-audit-net-does-not-deny.js diff --git a/src/permission/permission.h b/src/permission/permission.h index 84e3ea67ed5e..6a080fbe73c4 100644 --- a/src/permission/permission.h +++ b/src/permission/permission.h @@ -37,9 +37,11 @@ namespace permission { const auto resource__ = (resource); \ if (!env__->permission()->is_granted(env__, perm__, resource__)) \ [[unlikely]] { \ - node::permission::Permission::ThrowAccessDenied( \ - env__, perm__, resource__); \ - if (!env__->permission()->warning_only()) return __VA_ARGS__; \ + if (!env__->permission()->warning_only()) { \ + node::permission::Permission::ThrowAccessDenied( \ + env__, perm__, resource__); \ + return __VA_ARGS__; \ + } \ } \ } while (0) @@ -51,9 +53,11 @@ namespace permission { const auto resource__ = (resource); \ if (!env__->permission()->is_granted(env__, perm__, resource__)) \ [[unlikely]] { \ - node::permission::Permission::AsyncThrowAccessDenied( \ - env__, (wrap), perm__, resource__); \ - if (!env__->permission()->warning_only()) return __VA_ARGS__; \ + if (!env__->permission()->warning_only()) { \ + node::permission::Permission::AsyncThrowAccessDenied( \ + env__, (wrap), perm__, resource__); \ + return __VA_ARGS__; \ + } \ } \ } while (0) @@ -65,14 +69,17 @@ namespace permission { const auto resource__ = (resource); \ if (!env__->permission()->is_granted(env__, perm__, resource__)) \ [[unlikely]] { \ - Local err_access; \ - if (node::permission::CreateAccessDeniedError(env__, perm__, resource__) \ - .ToLocal(&err_access)) { \ - args.GetReturnValue().Set(err_access); \ - } else { \ - args.GetReturnValue().Set(UV_EACCES); \ + if (!env__->permission()->warning_only()) { \ + Local err_access; \ + if (node::permission::CreateAccessDeniedError( \ + env__, perm__, resource__) \ + .ToLocal(&err_access)) { \ + args.GetReturnValue().Set(err_access); \ + } else { \ + args.GetReturnValue().Set(UV_EACCES); \ + } \ + return __VA_ARGS__; \ } \ - return __VA_ARGS__; \ } \ } while (0) diff --git a/test/parallel/test-permission-audit-fs-does-not-deny.js b/test/parallel/test-permission-audit-fs-does-not-deny.js new file mode 100644 index 000000000000..13284802839d --- /dev/null +++ b/test/parallel/test-permission-audit-fs-does-not-deny.js @@ -0,0 +1,59 @@ +'use strict'; + +const common = require('../common'); +const { isMainThread } = require('worker_threads'); + +if (!isMainThread) { + common.skip('This test only works on a main thread'); +} + +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const { test } = require('node:test'); +const fixtures = require('../common/fixtures'); + +const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md'); + +function runAudit(mode) { + const childScript = ` + const dc = require('node:diagnostics_channel'); + const msgs = []; + dc.subscribe('node:permission-model:fs', (m) => msgs.push({ + permission: m.permission, + resource: m.resource, + })); + try { + ${mode === 'eval' ? + `eval('require("node:fs").readFileSync(process.env.BLOCKED_FILE)');` : + `require('node:fs').readFileSync(process.env.BLOCKED_FILE);`} + console.log('RESULT NO_THROW'); + } catch (e) { + console.log('RESULT THREW ' + e.code); + } + console.log('AUDIT ' + JSON.stringify(msgs)); + `; + + const env = { ...process.env, BLOCKED_FILE: blockedFile }; + const { status, stdout, stderr } = spawnSync( + process.execPath, + ['--permission-audit', '-e', childScript], + { encoding: 'utf8', env }, + ); + assert.strictEqual(status, 0, stderr); + const lines = stdout.split('\n'); + assert.ok(lines.includes('RESULT NO_THROW'), stdout); + const auditLine = lines.find((l) => l.startsWith('AUDIT ')); + assert.ok(auditLine, stdout); + const msgs = JSON.parse(auditLine.replace('AUDIT ', '')); + assert.strictEqual(msgs.length, 1); + assert.strictEqual(msgs[0].permission, 'FileSystemRead'); + assert.ok(msgs[0].resource.endsWith('protected-file.md')); +} + +test('permission-audit logs fs denial without throwing', () => { + runAudit('direct'); +}); + +test('permission-audit logs fs denial without throwing (eval)', () => { + runAudit('eval'); +}); diff --git a/test/parallel/test-permission-audit-net-does-not-deny.js b/test/parallel/test-permission-audit-net-does-not-deny.js new file mode 100644 index 000000000000..4fdaaf3494c9 --- /dev/null +++ b/test/parallel/test-permission-audit-net-does-not-deny.js @@ -0,0 +1,69 @@ +'use strict'; + +const common = require('../common'); +const { isMainThread } = require('worker_threads'); + +if (!isMainThread) { + common.skip('This test only works on a main thread'); +} + +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const { test } = require('node:test'); +const net = require('net'); + +async function runAudit(mode) { + const server = net.createServer(); + + await new Promise((resolve, reject) => { + server.on('error', reject); + server.listen(0, '127.0.0.1', resolve); + }); + const { port } = server.address(); + const env = { ...process.env, PORT: String(port), HOST: '127.0.0.1' }; + + const childScript = ` + const dc = require('node:diagnostics_channel'); + const net = require('node:net'); + const msgs = []; + dc.subscribe('node:permission-model:net', (m) => msgs.push({ + permission: m.permission, + resource: m.resource, + })); + const s = ${mode === 'eval' ? + `eval('net.connect(Number(process.env.PORT), process.env.HOST)')` : + `net.connect(Number(process.env.PORT), process.env.HOST)`}; + s.on('connect', () => { s.destroy(); console.log('RESULT CONNECTED'); }); + s.on('error', (e) => { console.log('RESULT ERROR ' + e.code); }); + s.on('close', () => { + console.log('AUDIT ' + JSON.stringify(msgs)); + }); + `; + + try { + const { status, stdout, stderr } = spawnSync( + process.execPath, + ['--permission-audit', '-e', childScript], + { encoding: 'utf8', env }, + ); + assert.strictEqual(status, 0, stderr); + const lines = stdout.split('\n'); + assert.strictEqual(lines[0], 'RESULT CONNECTED', stdout); + const auditLine = lines.find((l) => l.startsWith('AUDIT ')); + assert.ok(auditLine, stdout); + const msgs = JSON.parse(auditLine.replace('AUDIT ', '')); + assert.strictEqual(msgs.length, 1); + assert.strictEqual(msgs[0].permission, 'Net'); + assert.ok(msgs[0].resource.includes('127.0.0.1')); + } finally { + server.close(); + } +} + +test('permission-audit logs net denial without blocking connect', async () => { + await runAudit('direct'); +}); + +test('permission-audit logs net denial without blocking connect (eval)', async () => { + await runAudit('eval'); +}); From 8c1d1284cd87cca70a795744e15ed2f3823b109d Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:41:42 +0800 Subject: [PATCH 2/7] zlib: reject truncated zstd input Treat an unfinished Zstd frame as an unexpected end of file when the stream is finalized with ZSTD_e_end. Avoid reporting an error while the output buffer still needs to be drained or when an empty final write follows a completed frame. Preserve partial decompression when ZSTD_e_flush is used. Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64593 Fixes: https://github.com/nodejs/node/issues/64592 Reviewed-By: James M Snell Reviewed-By: Ryuhei Shima --- src/node_zlib.cc | 29 ++++++++++++++ test/parallel/test-zlib-truncated.js | 59 +++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 95201624cfa2..5a294ba321d2 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -358,6 +358,7 @@ class ZstdDecompressContext final : public ZstdContext { // Streaming-related, should be available for all compression libraries: void Close(); void DoThreadPoolWork(); + CompressionError GetErrorInfo() const; CompressionError ResetStream(); // Zstd specific: @@ -375,6 +376,7 @@ class ZstdDecompressContext final : public ZstdContext { private: DeleteFnPtr dctx_; + bool frame_complete_ = false; }; class CompressionStreamMemoryOwner { @@ -1717,6 +1719,8 @@ void ZstdDecompressContext::Close() { CompressionError ZstdDecompressContext::Init(uint64_t pledged_src_size, std::string_view dictionary) { + frame_complete_ = false; + #ifdef NODE_BUNDLED_ZSTD ZSTD_customMem custom_mem = { CompressionStreamMemoryOwner::AllocForBrotli, @@ -1752,12 +1756,37 @@ CompressionError ZstdDecompressContext::ResetStream() { } void ZstdDecompressContext::DoThreadPoolWork() { + // The JavaScript processing loop retries with an empty input buffer when the + // previous call filled the output buffer. Avoid interpreting that retry as + // the beginning of a new, incomplete frame. + if (frame_complete_ && input_.size == 0) { + return; + } + size_t const ret = ZSTD_decompressStream(dctx_.get(), &output_, &input_); if (ZSTD_isError(ret)) { + frame_complete_ = false; error_ = ZSTD_getErrorCode(ret); error_code_string_ = ZstdStrerror(error_); error_string_ = ZSTD_getErrorString(error_); + } else { + frame_complete_ = ret == 0; + } +} + +CompressionError ZstdDecompressContext::GetErrorInfo() const { + CompressionError error = ZstdContext::GetErrorInfo(); + if (error.IsError()) { + return error; } + + if (flush_ == ZSTD_e_end && !frame_complete_ && input_.pos == input_.size && + output_.pos < output_.size) { + return CompressionError( + "unexpected end of file", "Z_BUF_ERROR", Z_BUF_ERROR); + } + + return {}; } template diff --git a/test/parallel/test-zlib-truncated.js b/test/parallel/test-zlib-truncated.js index c489388a674e..0f9ce776b1db 100644 --- a/test/parallel/test-zlib-truncated.js +++ b/test/parallel/test-zlib-truncated.js @@ -22,6 +22,12 @@ const errMessage = /unexpected end of file/; { comp: 'gzip', decomp: 'unzip', decompSync: 'unzipSync' }, { comp: 'deflate', decomp: 'inflate', decompSync: 'inflateSync' }, { comp: 'deflateRaw', decomp: 'inflateRaw', decompSync: 'inflateRawSync' }, + { + comp: 'zstdCompress', + decomp: 'zstdDecompress', + decompSync: 'zstdDecompressSync', + partialFlush: zlib.constants.ZSTD_e_flush, + }, ].forEach(function(methods) { zlib[methods.comp](inputString, common.mustSucceed((compressed) => { const truncated = compressed.slice(0, compressed.length / 2); @@ -46,16 +52,59 @@ const errMessage = /unexpected end of file/; assert.match(err.message, errMessage); })); - const syncFlushOpt = { finishFlush: zlib.constants.Z_SYNC_FLUSH }; + const partialFlushOpt = { + finishFlush: methods.partialFlush ?? zlib.constants.Z_SYNC_FLUSH, + }; - // Sync truncated input test, finishFlush = Z_SYNC_FLUSH - const result = toUTF8(zlib[methods.decompSync](truncated, syncFlushOpt)); + // Sync truncated input test with a non-finalizing finish flush. + const result = toUTF8(zlib[methods.decompSync](truncated, partialFlushOpt)); assert.strictEqual(result, inputString.slice(0, result.length)); - // Async truncated input test, finishFlush = Z_SYNC_FLUSH - zlib[methods.decomp](truncated, syncFlushOpt, common.mustSucceed((decompressed) => { + // Async truncated input test with a non-finalizing finish flush. + zlib[methods.decomp](truncated, partialFlushOpt, common.mustSucceed((decompressed) => { const result = toUTF8(decompressed); assert.strictEqual(result, inputString.slice(0, result.length)); })); })); }); + +// A non-zero return from ZSTD_decompressStream() can also mean that the +// output buffer is full. Make sure that is drained before treating the return +// value as truncated input. +{ + const input = Buffer.alloc(zlib.constants.Z_DEFAULT_CHUNK * 2, 0x61); + const compressed = zlib.zstdCompressSync(input); + const decompressed = zlib.zstdDecompressSync(compressed, { + chunkSize: zlib.constants.Z_MIN_CHUNK, + }); + assert.deepStrictEqual(decompressed, input); +} + +// Ending a stream after a previous write completed a frame must not be +// mistaken for an empty, truncated frame. +{ + const input = Buffer.from(inputString); + const compressed = zlib.zstdCompressSync(input); + const decompressor = zlib.createZstdDecompress(); + const output = []; + + decompressor.on('data', (chunk) => output.push(chunk)); + decompressor.on('end', common.mustCall(() => { + assert.deepStrictEqual(Buffer.concat(output), input); + })); + decompressor.write(compressed, common.mustCall(() => decompressor.end())); +} + +// Conversely, ending after a previous write supplied only part of a frame +// must report that the frame is incomplete. +{ + const compressed = zlib.zstdCompressSync(inputString); + const truncated = compressed.subarray(0, compressed.length / 2); + const decompressor = zlib.createZstdDecompress(); + + decompressor.on('error', common.mustCall((error) => { + assert.match(error.message, errMessage); + })); + decompressor.write(truncated, common.mustCall(() => decompressor.end())); + decompressor.resume(); +} From 0bf185c566bac28f8db6d97b7f82930d9323d9b1 Mon Sep 17 00:00:00 2001 From: Maxence Robinet <107369283+saint-james-fr@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:37:30 +0200 Subject: [PATCH 3/7] doc: update sea example by fixing wrong code example The SEA configuration defines the output binary as `sea`, but the signing and run steps still referenced `hello`. Update the example to use `sea` consistently so the commands match the generated binary. Signed-off-by: Maxence Robinet <107369283+saint-james-fr@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64025 Reviewed-By: Luigi Pinca Reviewed-By: Trivikram Kamat --- doc/api/single-executable-applications.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/single-executable-applications.md b/doc/api/single-executable-applications.md index 7480a87d43a5..be5667cbd499 100644 --- a/doc/api/single-executable-applications.md +++ b/doc/api/single-executable-applications.md @@ -71,7 +71,7 @@ binary. * On macOS: ```bash - codesign --sign - hello + codesign --sign - sea ``` * On Windows (optional): @@ -80,7 +80,7 @@ binary. binary would still be runnable. ```powershell - signtool sign /fd SHA256 hello.exe + signtool sign /fd SHA256 sea.exe ``` 5. Run the binary: @@ -88,14 +88,14 @@ binary. * On systems other than Windows ```console - $ ./hello world + $ ./sea world Hello, world! ``` * On Windows ```console - $ .\hello.exe world + $ .\sea.exe world Hello, world! ``` From 4bec1915dccb161c5d93075ab587b7ba2dd94389 Mon Sep 17 00:00:00 2001 From: AkshatOP <74758662+AkshatOP@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:07:41 +0530 Subject: [PATCH 4/7] vfs: make recursive readdir iterative MemoryProvider recursive readdir walked the directory tree with a recursive helper. Rewrite it to traverse iteratively with an explicit stack so a deeply nested tree can no longer exhaust the call stack. The set of directories on the active traversal path is still tracked, so a circular symlink stops descending while its entry remains listed; the output and observable behavior are unchanged. Refs: https://github.com/nodejs/node/pull/64168 Signed-off-by: AkshatOP PR-URL: https://github.com/nodejs/node/pull/64149 Fixes: https://github.com/nodejs/node/issues/64148 Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- lib/internal/vfs/providers/memory.js | 105 ++++++++++-------- .../test-vfs-readdir-symlink-recursive.js | 29 +++++ 2 files changed, 89 insertions(+), 45 deletions(-) diff --git a/lib/internal/vfs/providers/memory.js b/lib/internal/vfs/providers/memory.js index 8490340101bc..a46d1e71e843 100644 --- a/lib/internal/vfs/providers/memory.js +++ b/lib/internal/vfs/providers/memory.js @@ -2,6 +2,7 @@ const { ArrayFrom, + ArrayPrototypePop, ArrayPrototypePush, DateNow, SafeMap, @@ -613,59 +614,73 @@ class MemoryProvider extends VirtualProvider { */ #readdirRecursive(dirEntry, dirPath, withFileTypes) { const results = []; + // Directories on the current traversal path. A directory reached again + // through a symlink cycle is not descended into (but is still listed). const active = new SafeSet(); - const walk = (entry, currentPath, relativePath) => { - if (active.has(entry)) { - return; + // Traverse depth-first with an explicit stack instead of recursion, so a + // deeply nested tree cannot exhaust the call stack. Each frame is a + // directory being walked together with a snapshot of its children and the + // index of the next child to visit. + const enter = (entry, currentPath, relativePath) => { + this.#ensurePopulated(entry, currentPath); + active.add(entry); + ArrayPrototypePush(stack, { + entry, + currentPath, + relativePath, + children: ArrayFrom(entry.children), + index: 0, + }); + }; + + const stack = []; + enter(dirEntry, dirPath, ''); + + while (stack.length > 0) { + const frame = stack[stack.length - 1]; + if (frame.index >= frame.children.length) { + active.delete(frame.entry); + ArrayPrototypePop(stack); + continue; } - active.add(entry); - try { - this.#ensurePopulated(entry, currentPath); - - for (const { 0: name, 1: childEntry } of entry.children) { - const childRelative = relativePath ? - relativePath + '/' + name : name; - - if (withFileTypes) { - let type; - if (childEntry.isSymbolicLink()) { - type = UV_DIRENT_LINK; - } else if (childEntry.isDirectory()) { - type = UV_DIRENT_DIR; - } else { - type = UV_DIRENT_FILE; - } - ArrayPrototypePush(results, - new Dirent(childRelative, type, dirPath)); - } else { - ArrayPrototypePush(results, childRelative); - } + const { 0: name, 1: childEntry } = frame.children[frame.index++]; + const childRelative = frame.relativePath ? + frame.relativePath + '/' + name : name; - // Follow symlinks to directories for recursive traversal. - // Track the active traversal path to avoid symlink cycles. - let resolvedChild = childEntry; - if (childEntry.isSymbolicLink()) { - const targetPath = this.#resolveSymlinkTarget( - pathPosix.join(currentPath, name), childEntry.target, - ); - const result = this.#lookupEntry(targetPath, true, 0); - if (result.entry) { - resolvedChild = result.entry; - } - } - if (resolvedChild.isDirectory()) { - const childPath = pathPosix.join(currentPath, name); - walk(resolvedChild, childPath, childRelative); - } + if (withFileTypes) { + let type; + if (childEntry.isSymbolicLink()) { + type = UV_DIRENT_LINK; + } else if (childEntry.isDirectory()) { + type = UV_DIRENT_DIR; + } else { + type = UV_DIRENT_FILE; } - } finally { - active.delete(entry); + ArrayPrototypePush(results, new Dirent(childRelative, type, dirPath)); + } else { + ArrayPrototypePush(results, childRelative); } - }; - walk(dirEntry, dirPath, ''); + // Follow symlinks to directories for recursive traversal, skipping any + // directory already on the active path to avoid symlink cycles. + let resolvedChild = childEntry; + if (childEntry.isSymbolicLink()) { + const targetPath = this.#resolveSymlinkTarget( + pathPosix.join(frame.currentPath, name), childEntry.target, + ); + const result = this.#lookupEntry(targetPath, true, 0); + if (result.entry) { + resolvedChild = result.entry; + } + } + if (resolvedChild.isDirectory() && !active.has(resolvedChild)) { + enter(resolvedChild, pathPosix.join(frame.currentPath, name), + childRelative); + } + } + return results; } diff --git a/test/parallel/test-vfs-readdir-symlink-recursive.js b/test/parallel/test-vfs-readdir-symlink-recursive.js index 33b52bb5d4a4..f46a12da7b9a 100644 --- a/test/parallel/test-vfs-readdir-symlink-recursive.js +++ b/test/parallel/test-vfs-readdir-symlink-recursive.js @@ -105,3 +105,32 @@ assert.ok( assert.ok(dirents.some((d) => d.name === 'sub' && d.isDirectory())); assert.ok(dirents.some((d) => d.name === 'lnk' && d.isSymbolicLink())); } + +// Recursive readdir on a deeply nested tree must not exhaust the call stack. +// The iterative traversal introduced in this fix handles arbitrarily deep trees +// without recursion, so this should complete without a RangeError. +{ + const DEPTH = 1000; + const v = vfs.create(); + + // Build /deep/0/1/2/.../999/leaf.txt + let path = '/deep'; + v.mkdirSync(path); + for (let i = 0; i < DEPTH; i++) { + path += `/${i}`; + v.mkdirSync(path); + } + v.writeFileSync(`${path}/leaf.txt`, 'deep'); + + const entries = v.readdirSync('/deep', { recursive: true }); + + // Every intermediate directory and the leaf file must appear. + assert.strictEqual(entries.length, DEPTH + 1); + + // Build the expected relative path to the leaf file. + const expectedLeaf = Array.from({ length: DEPTH }, (_, i) => i).join('/') + '/leaf.txt'; + assert.ok( + entries.includes(expectedLeaf), + `Expected '${expectedLeaf}' in deep-tree entries`, + ); +} From c76aff4e0ac54656c9a72baabe1b3334995e5eeb Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 21 Jul 2026 02:37:50 -0400 Subject: [PATCH 5/7] tools: update nixpkgs-unstable to 20535e48e12c86043b577b8518234ff5dbb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/64589 Reviewed-By: René Reviewed-By: Colin Ihrig Reviewed-By: Filip Skokan Reviewed-By: James M Snell --- tools/nix/pkgs-26.05.nix | 4 ++-- tools/nix/pkgs.nix | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/nix/pkgs-26.05.nix b/tools/nix/pkgs-26.05.nix index c87a0cad2262..e0697c7a99b9 100644 --- a/tools/nix/pkgs-26.05.nix +++ b/tools/nix/pkgs-26.05.nix @@ -1,10 +1,10 @@ arg: let repo = "https://github.com/NixOS/nixpkgs"; - rev = "572a2c2b6faebd71246e3162e4217d7ca63a9300"; + rev = "fc51889f81924f15fba77a3c0b79cfb3f78fe0d4"; nixpkgs = import (builtins.fetchTarball { url = "${repo}/archive/${rev}.tar.gz"; - sha256 = "0c5xyqgip1kf7hinqbmfvsf8c7jwipbyj7dlb337gd058kz7zmwm"; + sha256 = "16397a8zmfj9gygm0yj4fhp9sj6hw954k246jyzsi0xpgwdzr76h"; }) arg; in # Unstable channel no longer supports Intel architecture for macOS. We can use the 26.05 channel diff --git a/tools/nix/pkgs.nix b/tools/nix/pkgs.nix index 80954b54ad42..54fde3a51b80 100644 --- a/tools/nix/pkgs.nix +++ b/tools/nix/pkgs.nix @@ -1,10 +1,10 @@ arg: let repo = "https://github.com/NixOS/nixpkgs"; - rev = "2065d53daf2c81ed7b57947e2e56682ded62723a"; + rev = "20535e48e12c86043b577b8518234ff5dbb26957"; nixpkgs = import (builtins.fetchTarball { url = "${repo}/archive/${rev}.tar.gz"; - sha256 = "0ib5k850qp6zgzzvkbkpv0iwy649hwd8ab6id5w2jq4jplnf6zr3"; + sha256 = "1dmdschkpmhjp67rhsig7k2qhgd918j5g30s6yxmjljqsxh2vlh9"; }) arg; in # Unstable channel no longer supports Intel architecture for macOS. We can use the 26.05 channel From f3ecafac447dd5449c4b816ed6b81cb1de552dd5 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 21 Jul 2026 03:33:36 -0400 Subject: [PATCH 6/7] doc: mention crypto.hash() for better perf Signed-off-by: Steven PR-URL: https://github.com/nodejs/node/pull/63420 Reviewed-By: Yagiz Nizipli Reviewed-By: Joyee Cheung --- doc/api/crypto.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index c53d4d649778..202f49910cda 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -3806,6 +3806,8 @@ behavior. For XOF hash functions such as `'shake256'`, the `outputLength` option specifies the desired output length in bytes. It is required for XOF hash functions without a default output length. +When the data is small (< 5MB) and readily available, [`crypto.hash()`][] is usually faster. + The `algorithm` is dependent on the available algorithms supported by the version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc. On recent releases of OpenSSL, `openssl list -digest-algorithms` will @@ -7018,6 +7020,7 @@ See the [list of SSL OP Flags][] for details. [`crypto.getCurves()`]: #cryptogetcurves [`crypto.getDiffieHellman()`]: #cryptogetdiffiehellmangroupname [`crypto.getHashes()`]: #cryptogethashes +[`crypto.hash()`]: #cryptohashalgorithm-data-options [`crypto.privateDecrypt()`]: #cryptoprivatedecryptprivatekey-buffer [`crypto.privateEncrypt()`]: #cryptoprivateencryptprivatekey-buffer [`crypto.publicDecrypt()`]: #cryptopublicdecryptkey-buffer From 4efa0c6311ce6432565d685a4f16fc93ded122cd Mon Sep 17 00:00:00 2001 From: Muhammad Zeeshan <61280174+zeeshan56656@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:33:45 +0500 Subject: [PATCH 7/7] doc: fix import.meta example for vm.SourceTextModule The import.meta example for new vm.SourceTextModule() in vm.md does not run as written. It fails in two separate ways. First, the constructor is missing the context: contextifiedObject option, so the module evaluates in the top context where secret is not defined, and the snippet throws ReferenceError: secret is not defined. Second, the trailing note suggests replacing meta.prop = {} with vm.runInContext('{}', contextifiedObject), but '{}' is parsed as an empty block and evaluates to undefined. That makes the following Object.getPrototypeOf(import.meta.prop) throw TypeError. Wrapping it as '({})' returns an object, which is what the note intends. This adds the context option and corrects the suggested replacement to '({})' in both the mjs and cjs variants. Fixes: https://github.com/nodejs/node/issues/64076 Signed-off-by: Muhammad Zeeshan <61280174+zeeshan56656@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64112 Reviewed-By: Joyee Cheung --- doc/api/vm.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/api/vm.md b/doc/api/vm.md index 29d9bc732d61..14cd1f269b12 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -919,6 +919,7 @@ const contextifiedObject = vm.createContext({ secret: 42 }); const module = new vm.SourceTextModule( 'Object.getPrototypeOf(import.meta.prop).secret = secret;', { + context: contextifiedObject, initializeImportMeta(meta) { // Note: this object is created in the top context. As such, // Object.getPrototypeOf(import.meta.prop) points to the @@ -937,7 +938,7 @@ await module.evaluate(); // To fix this problem, replace // meta.prop = {}; // above with -// meta.prop = vm.runInContext('{}', contextifiedObject); +// meta.prop = vm.runInContext('({})', contextifiedObject); ``` ```cjs @@ -947,6 +948,7 @@ const contextifiedObject = vm.createContext({ secret: 42 }); const module = new vm.SourceTextModule( 'Object.getPrototypeOf(import.meta.prop).secret = secret;', { + context: contextifiedObject, initializeImportMeta(meta) { // Note: this object is created in the top context. As such, // Object.getPrototypeOf(import.meta.prop) points to the @@ -964,7 +966,7 @@ const contextifiedObject = vm.createContext({ secret: 42 }); // To fix this problem, replace // meta.prop = {}; // above with - // meta.prop = vm.runInContext('{}', contextifiedObject); + // meta.prop = vm.runInContext('({})', contextifiedObject); })(); ```