npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sandbox-fs

v1.1.0

Published

A read-only virtual file system that maps Unix-style virtual paths to a real directory root

Downloads

194

Readme

sandbox-fs

A read-only virtual file system for Node.js that maps Unix-style virtual paths to a real directory root. Provides a secure sandbox environment where write operations are blocked and real filesystem paths are hidden from error messages.

Features

  • 🔒 Read-only: All write operations return EACCES (access denied)
  • 🌍 Cross-platform: Works with both Windows and Unix paths as root
  • 🔀 Unix-style virtual paths: All virtual paths use forward slashes (/)
  • 🚫 Path traversal protection: Prevents escaping the VFS root with ..
  • 🎭 Error filtering: Real paths in error messages are replaced with virtual paths
  • 📦 Full fs API: Compatible with Node.js fs module (both callback and promise APIs)
  • 🛤️ Path module: Virtual path module with fixed CWD of /
  • 🧹 Resource cleanup: Proper cleanup of file descriptors and streams

Installation

npm install sandbox-fs

Requirements

  • Node.js >= 18.0.0

Quick Start

import { VirtualFileSystem } from 'sandbox-fs';

// Create a VFS instance with a real directory as root
const vfs = new VirtualFileSystem({ root: '/opt/data' });

// Get fs and path modules
const fs = vfs.createNodeFSModule();
const path = vfs.createNodePathModule();

// Read files using virtual paths
// Virtual path: /config.json
// Real path: /opt/data/config.json
const config = fs.readFileSync('/config.json', 'utf8');

// Write operations are denied
try {
  fs.writeFileSync('/output.txt', 'data');
} catch (err) {
  console.log(err.code); // 'EACCES'
}

// Path operations use virtual CWD of '/'
console.log(path.resolve('.')); // '/'
console.log(path.resolve('foo/bar.txt')); // '/foo/bar.txt'

// Clean up when done
vfs.close();

API Reference

VirtualFileSystem

The main class for creating a virtual file system instance.

Constructor

new VirtualFileSystem(options: VFSOptions)

Creates a new VFS instance.

Parameters:

  • options.root (string): The real filesystem path to use as the VFS root. Can be a Windows path (C:\data) or Unix path (/opt/data).

Throws:

  • Error if the root path doesn't exist or is not a directory

Example:

// Unix
const vfs = new VirtualFileSystem({ root: '/opt/data' });

// Windows
const vfs = new VirtualFileSystem({ root: 'C:\\data' });

toRealPath(virtualPath: string): string

Converts a virtual path to a real filesystem path.

Parameters:

  • virtualPath (string): Virtual path starting with / (Unix-style)

Returns: Real filesystem path (platform-native)

Throws:

  • Error if path traversal is detected (e.g., /../etc/passwd)

Example:

const vfs = new VirtualFileSystem({ root: 'C:\\data' });
vfs.toRealPath('/foo/bar.txt'); // Returns: 'C:\data\foo\bar.txt'

toVirtualPath(realPath: string): string

Converts a real filesystem path to a virtual path.

Parameters:

  • realPath (string): Real filesystem path (platform-native)

Returns: Virtual path starting with / (Unix-style)

Throws:

  • Error if real path is outside the VFS root

Example:

const vfs = new VirtualFileSystem({ root: 'C:\\data' });
vfs.toVirtualPath('C:\\data\\foo\\bar.txt'); // Returns: '/foo/bar.txt'

createNodeFSModule(): any

Creates a Node.js fs-compatible module.

Returns: An object compatible with the Node.js fs module

The returned object includes:

  • Callback API: readFile(), stat(), readdir(), etc.
  • Promise API: fs.promises.readFile(), fs.promises.stat(), etc.
  • Sync API: readFileSync(), statSync(), readdirSync(), etc.
  • Stream API: createReadStream() (write streams throw EACCES)
  • Constants: fs.constants

Behavior:

  • Read operations: Work normally (readFile, stat, readdir, open, createReadStream, etc.)
  • Write operations: Throw EACCES (writeFile, mkdir, unlink, etc.)
  • Symlink operations: Throw EACCES (symlink, link, readlink)
  • 🔒 Error filtering: All error messages replace real paths with virtual paths

Example:

const fs = vfs.createNodeFSModule();

// Callback API
fs.readFile('/file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Promise API
const data = await fs.promises.readFile('/file.txt', 'utf8');

// Sync API
const data = fs.readFileSync('/file.txt', 'utf8');

// Stream API
const stream = fs.createReadStream('/large-file.txt');
stream.on('data', chunk => console.log(chunk));

createNodePathModule(): any

Creates a Node.js path-compatible module.

Returns: An object compatible with the Node.js path module

The returned object includes all path methods operating in virtual path space:

  • resolve(), join(), normalize(), relative()
  • dirname(), basename(), extname()
  • parse(), format(), isAbsolute()
  • sep, delimiter, posix, win32

Behavior:

  • All paths use Unix-style forward slashes (/)
  • Virtual CWD is always /
  • path.sep is always /
  • path.delimiter is always :
  • Both path.posix and path.win32 reference the same virtual implementation

Example:

const path = vfs.createNodePathModule();

path.resolve('.');                // Returns: '/'
path.resolve('foo/bar.txt');      // Returns: '/foo/bar.txt'
path.join('/foo', 'bar');         // Returns: '/foo/bar'
path.dirname('/foo/bar.txt');     // Returns: '/foo'
path.basename('/foo/bar.txt');    // Returns: 'bar.txt'
path.sep;                          // Returns: '/'

close(): void

Closes the VFS and releases all resources.

This method:

  • Closes all tracked file descriptors
  • Destroys all active streams
  • Marks the VFS as closed
  • Causes all subsequent fs operations to throw EBADF

Example:

const vfs = new VirtualFileSystem({ root: '/opt/data' });
const fs = vfs.createNodeFSModule();

const fd = fs.openSync('/file.txt', 'r');

vfs.close(); // Closes fd and all other resources

// All operations now throw EBADF
fs.readFileSync('/file.txt'); // Throws: EBADF (bad file descriptor)

closed (getter)

Returns: boolean - Whether the VFS has been closed

if (!vfs.closed) {
  vfs.close();
}

Security Features

Path Traversal Protection

The VFS validates all paths to prevent escaping the root directory:

const vfs = new VirtualFileSystem({ root: '/opt/data' });
const fs = vfs.createNodeFSModule();

// These throw errors:
fs.readFileSync('/../etc/passwd');      // Error: Path traversal detected
fs.readFileSync('/foo/../../etc/passwd'); // Error: Path traversal detected

Error Message Filtering

Real filesystem paths are automatically filtered from error messages:

const vfs = new VirtualFileSystem({ root: '/opt/data' });
const fs = vfs.createNodeFSModule();

try {
  fs.readFileSync('/nonexistent.txt');
} catch (err) {
  // Real error: ENOENT: no such file or directory, open '/opt/data/nonexistent.txt'
  // Filtered error: ENOENT: no such file or directory, open '/nonexistent.txt'
  console.log(err.message); // Real path is replaced with virtual path
}

Read-Only Enforcement

All write operations return EACCES:

const fs = vfs.createNodeFSModule();

// All of these throw EACCES:
fs.writeFileSync('/file.txt', 'data');
fs.appendFileSync('/file.txt', 'data');
fs.mkdirSync('/newdir');
fs.unlinkSync('/file.txt');
fs.rmdirSync('/dir');
fs.renameSync('/old.txt', '/new.txt');
fs.chmodSync('/file.txt', 0o644);
fs.createWriteStream('/file.txt'); // Throws immediately

Symlink Blocking

All symlink operations return EACCES:

const fs = vfs.createNodeFSModule();

// All of these throw EACCES:
fs.symlinkSync('/target', '/link');
fs.linkSync('/existing', '/new');
fs.readlinkSync('/symlink');

// stat/lstat also throw EACCES if the target is a symlink
const stats = fs.statSync('/symlink'); // Throws EACCES

Platform Support

Windows

  • VFS root can be a Windows path: C:\data, D:\projects, etc.
  • Virtual paths always use forward slashes: /foo/bar.txt
  • UNC paths are supported: \\server\share

Unix/Linux/macOS

  • VFS root is a standard Unix path: /opt/data, /home/user/sandbox, etc.
  • Virtual paths use forward slashes: /foo/bar.txt

Mounts (read-only)

You can mount external directories into the virtual namespace. Mounts are read-only and directory-only. The most-specific virtual path wins when resolving overlaps. Listings ignore symlinks and implicit parents are treated as directories if they have mount children.

const vfs = new VirtualFileSystem({
  root: '/opt/data',
  mounts: [{ virtual: '/app', real: '/opt/another' }]
});

// / maps to /opt/data; /app maps to /opt/another
// readdirSync('/') will include 'app' even if /opt/data/app is absent

Advanced Usage

AbortSignal Support

Many fs methods support AbortSignal for cancellation:

const controller = new AbortController();
const fs = vfs.createNodeFSModule();

// Cancel operation after 1 second
setTimeout(() => controller.abort(), 1000);

try {
  const data = await fs.promises.readFile('/large-file.txt', {
    signal: controller.signal
  });
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Operation cancelled');
  }
}

File Descriptor Tracking

The VFS tracks all open file descriptors:

const fs = vfs.createNodeFSModule();

const fd = fs.openSync('/file.txt', 'r');
const buffer = Buffer.alloc(100);

// Read using the file descriptor
fs.readSync(fd, buffer, 0, 100, 0);

// VFS tracks the FD and ensures it's valid
fs.closeSync(fd);

// After close, the FD is invalid
fs.readSync(fd, buffer, 0, 100, 0); // Throws EBADF

Stream Path Override

Streams have their path property overridden to return virtual paths:

const fs = vfs.createNodeFSModule();
const stream = fs.createReadStream('/data.txt');

console.log(stream.path); // '/data.txt' (virtual path)
// Real path is hidden

Working with Newer Node.js APIs

The VFS supports newer Node.js APIs (when available):

const fs = vfs.createNodeFSModule();

// glob (Node.js 22+)
if (fs.promises.glob) {
  for await (const file of fs.promises.glob('**/*.txt')) {
    console.log(file); // Virtual paths
  }
}

// statfs (Node.js 19+)
if (fs.promises.statfs) {
  const stats = await fs.promises.statfs('/');
  console.log(stats);
}

// openAsBlob (Node.js 19+)
if (fs.openAsBlob) {
  const blob = await fs.openAsBlob('/file.txt');
}

TypeScript Support

Full TypeScript support with type definitions:

import { VirtualFileSystem, VFSOptions } from 'sandbox-fs';

const options: VFSOptions = { root: '/opt/data' };
const vfs: VirtualFileSystem = new VirtualFileSystem(options);

const fs = vfs.createNodeFSModule();
const path = vfs.createNodePathModule();

// Full type inference for fs and path modules
const content: string = fs.readFileSync('/file.txt', 'utf8');
const resolved: string = path.resolve('foo', 'bar');

Limitations

  1. Read-only: Write operations are not supported and will throw EACCES
  2. Symlinks: Symlink operations and reading symlink targets throw EACCES
  3. Virtual CWD: The path module always uses / as the current working directory
  4. Performance: Path conversion adds minimal overhead to each operation
  5. Node.js version: Newer APIs (glob, statfs) require recent Node.js versions

License

MIT

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.