From 36a4f47993c85f38f3d93fe9f7f6a5f471b4a0cf Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Tue, 4 Aug 2026 11:54:22 -0300 Subject: [PATCH] permission: block FileHandle fsync and fdatasync Signed-off-by: RafaelGSS --- doc/api/permissions.md | 8 +++++ lib/internal/fs/promises.js | 6 ++++ .../test-permission-fs-filehandle-sync.js | 33 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 test/parallel/test-permission-fs-filehandle-sync.js diff --git a/doc/api/permissions.md b/doc/api/permissions.md index 05a18db0457d..9ced1a895dc0 100644 --- a/doc/api/permissions.md +++ b/doc/api/permissions.md @@ -251,6 +251,14 @@ does not exist, the wildcard will not be added, and access will be limited to yet, make sure to explicitly include the wildcard: `/my-path/folder-do-not-exist/*`. +Some `node:fs` operations act on an already-open file descriptor rather than a +path, so they cannot be tied to a `--allow-fs-read` or `--allow-fs-write` grant. +When the permission model is enabled these operations are disabled and throw +`ERR_ACCESS_DENIED`, regardless of how the descriptor was obtained. This applies +both to the top-level `node:fs` functions and to the equivalent +`FileHandle` methods, and currently includes `fsync`/`fdatasync`, +`fchmod`, and `fchown` (and their synchronous variants). + #### Configuration file support In addition to passing permission flags on the command line, they can also be diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 3e336024a15a..94a255a8d7dd 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -1571,6 +1571,9 @@ async function rmdir(path, options) { } async function fdatasync(handle) { + if (permission.isEnabled()) { + throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'); + } return await PromisePrototypeThen( binding.fdatasync(handle.fd, kUsePromises), undefined, @@ -1579,6 +1582,9 @@ async function fdatasync(handle) { } async function fsync(handle) { + if (permission.isEnabled()) { + throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'); + } return await PromisePrototypeThen( binding.fsync(handle.fd, kUsePromises), undefined, diff --git a/test/parallel/test-permission-fs-filehandle-sync.js b/test/parallel/test-permission-fs-filehandle-sync.js new file mode 100644 index 000000000000..28e9da734c85 --- /dev/null +++ b/test/parallel/test-permission-fs-filehandle-sync.js @@ -0,0 +1,33 @@ +// Flags: --permission --allow-fs-read=* +'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 { open } = require('fs/promises'); +const fixtures = require('../common/fixtures'); + +const regularFile = fixtures.path('permission', 'deny', 'regular-file.md'); + +// FileHandle sync operations must be blocked when the permission model is +// enabled, consistent with fs.fsync() / fs.fsyncSync() and fdatasync variants. +(async () => { + const fh = await open(regularFile, 'r'); + try { + await assert.rejects( + fh.sync(), + common.expectsError({ code: 'ERR_ACCESS_DENIED' }), + ); + await assert.rejects( + fh.datasync(), + common.expectsError({ code: 'ERR_ACCESS_DENIED' }), + ); + } finally { + await fh.close(); + } +})().then(common.mustCall());