Skip to content

Commit 3388e9d

Browse files
committed
doc: add security warnings and symlink documentation to vfs.md
- Add security considerations section warning about path shadowing risks - Document that VFS shadows real paths when mounted - Add symlink documentation explaining VFS-internal-only behavior - Clarify that only mount mode exists (no overlay mode) - Reorder synchronous methods alphabetically per doc conventions Addresses review comments from @jasnell regarding security documentation, overlay mode clarification, alphabetical ordering, and symlink behavior.
1 parent d930079 commit 3388e9d

1 file changed

Lines changed: 98 additions & 13 deletions

File tree

doc/api/vfs.md

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ is useful for:
3737
* Creating virtual module systems
3838
* Embedding configuration or data files in applications
3939

40+
## Mount mode
41+
42+
The VFS operates in **mount mode only**. When mounted at a path prefix (e.g.,
43+
`/virtual`), the VFS handles all operations for paths starting with that
44+
prefix. There is no overlay mode that would merge virtual and real file system
45+
contents at the same paths.
46+
4047
## Basic usage
4148

4249
The following example shows how to create a virtual file system, add files,
@@ -197,6 +204,12 @@ Mounts the virtual file system at the specified path prefix. After mounting,
197204
files in the VFS can be accessed via the `fs` module using paths that start
198205
with the prefix.
199206

207+
If a real file system path already exists at the mount prefix, the VFS
208+
**shadows** that path. All operations to paths under the mount prefix will be
209+
directed to the VFS, making the real files inaccessible until the VFS is
210+
unmounted. See [Security considerations][] for important warnings about this
211+
behavior.
212+
200213
```cjs
201214
const vfs = require('node:vfs');
202215

@@ -287,26 +300,26 @@ All paths are relative to the VFS root (not the mount point).
287300

288301
#### Synchronous Methods
289302

290-
* `vfs.readFileSync(path[, options])` - Read a file
291-
* `vfs.writeFileSync(path, data[, options])` - Write a file
303+
* `vfs.accessSync(path[, mode])` - Check file accessibility
292304
* `vfs.appendFileSync(path, data[, options])` - Append to a file
293-
* `vfs.statSync(path[, options])` - Get file stats
294-
* `vfs.lstatSync(path[, options])` - Get file stats (no symlink follow)
295-
* `vfs.readdirSync(path[, options])` - Read directory contents
296-
* `vfs.mkdirSync(path[, options])` - Create a directory
297-
* `vfs.rmdirSync(path)` - Remove a directory
298-
* `vfs.unlinkSync(path)` - Remove a file
299-
* `vfs.renameSync(oldPath, newPath)` - Rename a file or directory
305+
* `vfs.closeSync(fd)` - Close a file descriptor
300306
* `vfs.copyFileSync(src, dest[, mode])` - Copy a file
301307
* `vfs.existsSync(path)` - Check if path exists
302-
* `vfs.accessSync(path[, mode])` - Check file accessibility
308+
* `vfs.lstatSync(path[, options])` - Get file stats (no symlink follow)
309+
* `vfs.mkdirSync(path[, options])` - Create a directory
303310
* `vfs.openSync(path, flags[, mode])` - Open a file
304-
* `vfs.closeSync(fd)` - Close a file descriptor
311+
* `vfs.readFileSync(path[, options])` - Read a file
305312
* `vfs.readSync(fd, buffer, offset, length, position)` - Read from fd
306-
* `vfs.writeSync(fd, buffer, offset, length, position)` - Write to fd
307-
* `vfs.realpathSync(path[, options])` - Resolve symlinks
308313
* `vfs.readlinkSync(path[, options])` - Read symlink target
314+
* `vfs.readdirSync(path[, options])` - Read directory contents
315+
* `vfs.realpathSync(path[, options])` - Resolve symlinks
316+
* `vfs.renameSync(oldPath, newPath)` - Rename a file or directory
317+
* `vfs.rmdirSync(path)` - Remove a directory
318+
* `vfs.statSync(path[, options])` - Get file stats
309319
* `vfs.symlinkSync(target, path[, type])` - Create a symlink
320+
* `vfs.unlinkSync(path)` - Remove a file
321+
* `vfs.writeFileSync(path, data[, options])` - Write a file
322+
* `vfs.writeSync(fd, buffer, offset, length, position)` - Write to fd
310323

311324
#### Promise Methods
312325

@@ -649,4 +662,76 @@ const template = fs.readFileSync('/sea/templates/index.html', 'utf8');
649662
See the [Single Executable Applications][] documentation for more information
650663
on creating SEA builds with assets.
651664

665+
## Symbolic links
666+
667+
The VFS supports symbolic links within the virtual file system. Symlinks are
668+
created using `vfs.symlinkSync()` or `vfs.promises.symlink()` and can point
669+
to files or directories within the same VFS.
670+
671+
### Cross-boundary symlinks
672+
673+
Symbolic links in the VFS are **VFS-internal only**. They cannot:
674+
675+
* Point from a VFS path to a real file system path
676+
* Point from a real file system path to a VFS path
677+
* Be followed across VFS mount boundaries
678+
679+
When resolving symlinks, the VFS only follows links that target paths within
680+
the same VFS instance. Attempts to create symlinks with absolute paths that
681+
would resolve outside the VFS are allowed but will result in dangling symlinks.
682+
683+
```cjs
684+
const vfs = require('node:vfs');
685+
686+
const myVfs = vfs.create();
687+
myVfs.mkdirSync('/data');
688+
myVfs.writeFileSync('/data/config.json', '{}');
689+
690+
// This works - symlink within VFS
691+
myVfs.symlinkSync('/data/config.json', '/config');
692+
myVfs.readFileSync('/config', 'utf8'); // '{}'
693+
694+
// This creates a dangling symlink - target doesn't exist in VFS
695+
myVfs.symlinkSync('/etc/passwd', '/passwd-link');
696+
// myVfs.readFileSync('/passwd-link'); // Throws ENOENT
697+
```
698+
699+
## Security considerations
700+
701+
### Path shadowing
702+
703+
When a VFS is mounted, it **shadows** any real file system paths under the
704+
mount prefix. This means:
705+
706+
* Real files at the mount path become inaccessible
707+
* All operations are redirected to the VFS
708+
* Modules loaded from shadowed paths will use VFS content
709+
710+
This behavior can be exploited maliciously. A module could mount a VFS over
711+
critical system paths (like `/etc` on Unix or `C:\Windows` on Windows) and
712+
intercept sensitive operations:
713+
714+
```cjs
715+
// WARNING: Example of dangerous behavior - DO NOT DO THIS
716+
const vfs = require('node:vfs');
717+
718+
const maliciousVfs = vfs.create();
719+
maliciousVfs.writeFileSync('/passwd', 'malicious content');
720+
maliciousVfs.mount('/etc'); // Shadows /etc/passwd!
721+
722+
// Now fs.readFileSync('/etc/passwd') returns 'malicious content'
723+
```
724+
725+
### Recommendations
726+
727+
* **Audit dependencies**: Be cautious of third-party modules that use VFS, as
728+
they could shadow important paths.
729+
* **Use unique mount points**: Mount VFS at paths that don't conflict with
730+
real file system paths, such as `/@virtual` or `/vfs-{unique-id}`.
731+
* **Verify mount points**: Before trusting file content from paths that could
732+
be shadowed, verify the mount state.
733+
* **Limit VFS usage**: Only use VFS in controlled environments where you trust
734+
all loaded modules.
735+
736+
[Security considerations]: #security-considerations
652737
[Single Executable Applications]: single-executable-applications.md

0 commit comments

Comments
 (0)