permission: check permission model in realpathSync · nodejs/node@8dc18a6 · GitHub
Skip to content

Commit 8dc18a6

Browse files
RafaelGSSaduh95
authored andcommitted
permission: check permission model in realpathSync
The equivalent entry points (fs.realpathSync.native(), fs.promises.realpath(), fs.lstatSync(), fs.existsSync()) already deny access for the same paths Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> Backport-PR-URL: nodejs-private/node-private#1011 Reviewed-By: Rich Trott <rtrott@gmail.com> PR-URL: nodejs-private/node-private#973 CVE-ID: CVE-2026-106073
1 parent b5f4152 commit 8dc18a6

4 files changed

Lines changed: 76 additions & 3 deletions

File tree

‎lib/fs.js‎

Lines changed: 10 additions & 0 deletions

‎test/common/tmpdir.js‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,20 @@ function rmSync(pathname, useSpawn) {
3232
}
3333
}
3434

35+
// Under the permission model, fs.realpathSync() requires read access to the
36+
// path, which tests running with a restricted --allow-fs-read may not grant
37+
// for NODE_TEST_DIR.
38+
function resolveTestRoot(dir) {
39+
try {
40+
return fs.realpathSync(dir);
41+
} catch (err) {
42+
if (err.code !== 'ERR_ACCESS_DENIED') throw err;
43+
return path.resolve(dir);
44+
}
45+
}
46+
3547
const testRoot = process.env.NODE_TEST_DIR ?
36-
fs.realpathSync(process.env.NODE_TEST_DIR) : path.resolve(__dirname, '..');
48+
resolveTestRoot(process.env.NODE_TEST_DIR) : path.resolve(__dirname, '..');
3749

3850
// Using a `.` prefixed name, which is the convention for "hidden" on POSIX,
3951
// gets tools to ignore it by default or by simple rules, especially eslint.

‎test/fixtures/permission/fs-read.js‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,57 @@ const regularFile = __filename;
698698
});
699699
}
700700

701+
// fs.realpath (JS implementation, walks the path with lstat)
702+
{
703+
assert.throws(() => {
704+
fs.realpathSync(blockedFile);
705+
}, common.expectsError({
706+
code: 'ERR_ACCESS_DENIED',
707+
permission: 'FileSystemRead',
708+
}));
709+
assert.throws(() => {
710+
fs.realpathSync(bufferBlockedFile);
711+
}, common.expectsError({
712+
code: 'ERR_ACCESS_DENIED',
713+
permission: 'FileSystemRead',
714+
}));
715+
assert.throws(() => {
716+
fs.realpathSync(path.join(blockedFolder, 'anyfile'));
717+
}, common.expectsError({
718+
code: 'ERR_ACCESS_DENIED',
719+
permission: 'FileSystemRead',
720+
}));
721+
// Must not act as a file-existence oracle: a non-existent path inside a
722+
// blocked folder must be denied, not reported as ENOENT.
723+
assert.throws(() => {
724+
fs.realpathSync(path.join(blockedFolder, 'does-not-exist'));
725+
}, common.expectsError({
726+
code: 'ERR_ACCESS_DENIED',
727+
permission: 'FileSystemRead',
728+
}));
729+
fs.realpath(blockedFile, common.expectsError({
730+
code: 'ERR_ACCESS_DENIED',
731+
permission: 'FileSystemRead',
732+
}));
733+
assert.throws(() => {
734+
fs.realpathSync(blockedFile);
735+
}, common.expectsError({
736+
message: 'Access to this API has been restricted. Use --allow-fs-read to manage permissions.',
737+
code: 'ERR_ACCESS_DENIED',
738+
permission: 'FileSystemRead',
739+
resource: blockedFile,
740+
}));
741+
assert.rejects(async () => {
742+
await fsPromises.realpath(blockedFile);
743+
}, common.expectsError({
744+
code: 'ERR_ACCESS_DENIED',
745+
permission: 'FileSystemRead',
746+
})).then(common.mustCall());
747+
748+
// doesNotThrow
749+
assert.strictEqual(typeof fs.realpathSync(regularFile), 'string');
750+
}
751+
701752
// fs.realpath.native
702753
{
703754
fs.realpath.native(blockedFile, common.expectsError({

‎test/parallel/test-permission-fs-require.js‎

Lines changed: 2 additions & 2 deletions

0 commit comments

Comments
 (0)