node/test/parallel/test-vfs-real-provider-symlinks.js
Matteo Collina c9562ddb82
vfs: add minimal node:vfs subsystem
Adds the node:vfs builtin module with VirtualFileSystem and
provider classes. No integration with fs, modules, or SEA.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/63115
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-05-23 18:43:31 +00:00

111 lines
4.1 KiB
JavaScript

// Flags: --experimental-vfs
'use strict';
// Symlink and path-escape behaviour for RealFSProvider.
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vfs = require('node:vfs');
tmpdir.refresh();
const root = path.join(tmpdir.path, 'real-symlinks');
fs.mkdirSync(root, { recursive: true });
const myVfs = vfs.create(new vfs.RealFSProvider(root));
(async () => {
// .. traversal in VFS paths can't escape root
{
const subDir = path.join(root, 'sandbox');
fs.mkdirSync(subDir, { recursive: true });
const subVfs = vfs.create(new vfs.RealFSProvider(subDir));
assert.throws(() => subVfs.statSync('/../hello.txt'), { code: 'ENOENT' });
assert.throws(() => subVfs.readFileSync('/../../../etc/passwd'),
{ code: 'ENOENT' });
fs.rmdirSync(subDir);
}
// Path traversal via a non-leading-slash relative path
{
const escapeProvider = new vfs.RealFSProvider(root);
assert.throws(() => escapeProvider.statSync('../etc/passwd'),
{ code: 'ENOENT' });
}
// Symlinks: absolute target rejected with EACCES
{
assert.throws(() => myVfs.symlinkSync('/etc/passwd', '/escape'),
{ code: 'EACCES' });
await assert.rejects(myVfs.promises.symlink('/etc/passwd', '/escape2'),
{ code: 'EACCES' });
}
// Symlinks: relative target outside root rejected with EACCES
{
assert.throws(() => myVfs.symlinkSync('../../escape', '/bad-link'),
{ code: 'EACCES' });
await assert.rejects(
myVfs.promises.symlink('../../escape', '/bad-link2'),
{ code: 'EACCES' });
}
// Symlink with relative target inside root — readlink returns target as-is
{
fs.writeFileSync(path.join(root, 'rel-target.txt'), 'ok');
fs.symlinkSync('rel-target.txt', path.join(root, 'rel-link'));
assert.strictEqual(myVfs.readlinkSync('/rel-link'), 'rel-target.txt');
assert.strictEqual(await myVfs.promises.readlink('/rel-link'),
'rel-target.txt');
}
// Symlink whose absolute target is inside root → translated to VFS path
{
fs.writeFileSync(path.join(root, 'target.txt'), 'x');
fs.symlinkSync(path.join(root, 'target.txt'),
path.join(root, 'abs-link'));
assert.strictEqual(myVfs.readlinkSync('/abs-link'), '/target.txt');
assert.strictEqual(await myVfs.promises.readlink('/abs-link'),
'/target.txt');
}
// Symlink whose absolute target equals root → '/'
{
fs.symlinkSync(root, path.join(root, 'root-link'));
assert.strictEqual(myVfs.readlinkSync('/root-link'), '/');
assert.strictEqual(await myVfs.promises.readlink('/root-link'), '/');
}
// Symlink that points outside root: realpath rejects with EACCES
{
fs.writeFileSync(path.join(tmpdir.path, 'outside.txt'), 'forbidden');
fs.symlinkSync(path.join(tmpdir.path, 'outside.txt'),
path.join(root, 'esc-link'));
// Readlink returns the absolute target as-is (no translation since it's
// outside the root)
const target = myVfs.readlinkSync('/esc-link');
assert.strictEqual(target, path.join(tmpdir.path, 'outside.txt'));
// Realpath rejects: the resolved target escapes root
assert.throws(() => myVfs.realpathSync('/esc-link'), { code: 'EACCES' });
await assert.rejects(myVfs.promises.realpath('/esc-link'),
{ code: 'EACCES' });
}
// Realpath on root and on a subdir
{
fs.mkdirSync(path.join(root, 'sub2'), { recursive: true });
assert.strictEqual(myVfs.realpathSync('/'), '/');
assert.strictEqual(myVfs.realpathSync('/sub2'), '/sub2');
assert.strictEqual(await myVfs.promises.realpath('/sub2'), '/sub2');
}
// RealFSProvider with a rootPath that already ends in path.sep
{
fs.writeFileSync(path.join(root, 'tr.txt'), 'tr');
const trailingProvider = new vfs.RealFSProvider(root + path.sep);
assert.strictEqual(trailingProvider.readFileSync('/tr.txt', 'utf8'), 'tr');
}
})().then(common.mustCall());