René's URL Explorer Experiment


Title: Virtual File System Follow Ups · Issue #62328 · nodejs/node · GitHub

Open Graph Title: Virtual File System Follow Ups · Issue #62328 · nodejs/node

X Title: Virtual File System Follow Ups · Issue #62328 · nodejs/node

Description: /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. Code Review: PR #61478 — Virtual File Sy...

Open Graph Description: /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. ...

X Description: /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. ...

Opengraph URL: https://github.com/nodejs/node/issues/62328

X: @github

direct link

Domain: github.com


Hey, it has json ld scripts:
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Virtual File System Follow Ups","articleBody":"/cc @mcollina \n\nWith The [Virtual File System](https://github.com/nodejs/node/pull/61478) PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion.\n\n# Code Review: PR #61478 — Virtual File System for Node.js\n\n1. [Security \u0026 Permission Model](#1-security--permission-model)\n2. [API Compatibility Gaps](#2-api-compatibility-gaps)\n3. [Correctness Bugs](#3-correctness-bugs)\n4. [Windows Path Handling](#4-windows-path-handling)\n5. [Performance Concerns](#5-performance-concerns)\n6. [Architecture \u0026 Design](#6-architecture--design)\n7. [Test Runner Mock Integration](#7-test-runner-mock-integration)\n8. [Provider-Specific Issues](#8-provider-specific-issues)\n9. [Test Coverage Gaps](#9-test-coverage-gaps)\n10. [Code Quality \u0026 Cleanup](#10-code-quality--cleanup)\n\n---\n\n## 1. Security \u0026 Permission Model\n\n**We need to decide if VFS should participate in the permissions model and how.**\n\n### 1.1 VFS interception bypasses the Node.js permission model — CRITICAL\n\n**Disposition: follow-up**\n\nIn `lib/fs.js`, VFS interception happens *before* the permission model\ncheck in every intercepted function. Pattern (repeated 96+ times):\n\n```js\nfunction readFileSync(path, options) {\n  const h = vfsState.handlers;\n  if (h !== null) {\n    const result = h.readFileSync(path, options);\n    if (result !== undefined) return result;  // ← returns BEFORE permission check\n  }\n  // permission check is down here\n  if (permission.isEnabled() \u0026\u0026 !permission.has('fs.read', path)) { ... }\n}\n```\n\nWhen `--experimental-permission` is active, a VFS mount can serve\ncontent for any path without triggering the permission gate. This is\nthe most significant security issue in the PR.\n\n**Recommendation:** At minimum, when `permission.isEnabled()`, the\nVFS handler path should still check `permission.has('fs.read', ...)` /\n`permission.has('fs.write', ...)` as appropriate. Alternatively,\ndisable VFS mounting entirely when the permission model is active\nuntil a proper integration is designed.\n\n### 1.2 Any loaded code can call `mount()` and shadow real paths — HIGH\n\n**Disposition: follow-up**\n\n`require('node:vfs')` is a public module with no gating. Any npm\ndependency can create a VFS, write arbitrary content, and `mount('/')`\nto intercept every `fs` call process-wide. There are no restrictions\non mount paths — `/etc`, `/usr`, `node_modules`, or `/` itself are\nall valid targets.\n\nCombined with overlay mode, this enables targeted, nearly undetectable\ninterception of specific files (e.g., `.env`, credentials, SSH keys)\nwhile passing all other reads through to the real filesystem.\n\nThis was raised by @Qard in\nhttps://github.com/nodejs/node/pull/61478#issuecomment-2868746178\nand the `vfs-mount` event was removed without a replacement gate.\n\n**Recommendation:** Consider:\n- Requiring `--experimental-vfs` flag to enable the module\n- Emitting an observable event on mount/unmount for security tooling\n- Restricting mount paths (blocklist for system-critical paths, or allowlist)\n\n### 1.3 Overlapping mounts have undefined behavior — MEDIUM\n\n**Disposition: follow-up**\n\n`activeVFSList` in `setup.js` is iterated linearly. First-registered\nVFS wins. If VFS-A mounts at `/app` and VFS-B at `/app/src`, reads\nto `/app/src/file.js` hit VFS-A. In mounted (non-overlay) mode,\nVFS-A returns ENOENT without consulting VFS-B. No conflict detection\nor warning exists.\n\n**Recommendation:** `registerVFS()` should detect and warn (or reject)\noverlapping mount points.\n\n### 1.4 `RealFSProvider` symlink target not validated — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/real.js:315-318` — `symlinkSync(target, vfsPath, type)`\nvalidates `vfsPath` via `#resolvePath()` but passes `target` through\nto `fs.symlinkSync` without validation. A user can create a symlink\ninside the VFS root pointing to any path on the real filesystem,\nenabling a jail escape.\n\nSimilarly, `readlinkSync` may return absolute real-filesystem paths\nwithout translating them into VFS coordinates, leaking path info.\n\nAnd `realpathSync` silently returns the original VFS path when the\nresolved path escapes the root (lines 336-337, 350-351), masking\nthe escape rather than throwing.\n\n---\n\n## 2. API Compatibility Gaps\n\nThese are places where VFS objects don't match the behavior of their\nreal `fs` counterparts. Code that works with real `fs` will break in\nsubtle ways when given VFS equivalents.\n\n### 2.1 `VirtualFileHandle` missing ~15 methods — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_handle.js` — `VirtualFileHandle` is missing\nthese methods that exist on real `fs.promises.FileHandle`:\n\n- `chmod(mode)`, `chown(uid, gid)`, `utimes(atime, mtime)`\n- `datasync()`, `sync()`\n- `readv(buffers)`, `writev(buffers)`\n- `appendFile(data)`, `readLines()`, `readableWebStream()`\n- `createReadStream()`, `createWriteStream()`\n- `fd` property (numeric file descriptor)\n- `[Symbol.asyncDispose]()`\n\nAdditionally, `VirtualFileHandle` does not extend `EventEmitter`, so\nthe `'close'` event (available since v15.4.0) doesn't fire. Code\ncalling `filehandle.on('close', ...)` will throw.\n\n**Recommendation:** Add stub methods that throw\n`ERR_METHOD_NOT_IMPLEMENTED` for unimplemented APIs, and extend\n`EventEmitter`.\n\n### 2.2 `fsPromises.open()` not intercepted by VFS — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/fs/promises.js` — The `open()` function goes directly\nto `binding.openFileHandle()` without any VFS check. Any code using\nthe modern `FileHandle` API via `fsPromises.open()` bypasses VFS\nentirely. This includes all `FileHandle` methods (`fh.read()`,\n`fh.write()`, `fh.stat()`, `fh.close()`).\n\n### 2.3 Streams missing standard properties — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/streams.js`:\n\n- `VirtualReadStream` — missing `bytesRead` and `pending` properties\n- `VirtualWriteStream` — missing `bytesWritten`, `pending` properties,\n  and explicit `close(callback)` method\n\n### 2.4 `VirtualDir` missing disposal and auto-close — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/dir.js`:\n\n- Missing `[Symbol.asyncDispose]()` and `[Symbol.dispose]()`\n- `entries()` async iterator doesn't auto-close the directory after\n  iteration (real `fs.Dir` does)\n- `read()` and `close()` are `async` methods that always return a\n  Promise, even in callback mode (real `fs.Dir` returns `undefined`\n  when a callback is provided)\n\n### 2.5 Stats objects have `ino: 0` and `dev: 0` for all files — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/stats.js` — Every virtual file gets `ino = 0` and\n`dev = 0`. This means:\n\n- Code that uses `stat.ino` for identity checks (e.g., detecting hard\n  links, circular references in `fs.cp`) treats all VFS files as the\n  same file\n- `stat.dev === 0` could collide with real device IDs\n\n**Recommendation:** Generate unique inode numbers (incrementing\ncounter) and use a distinctive `dev` number for VFS files.\n\n### 2.6 No `bigint` Stats support — LOW\n\n**Disposition: note**\n\nThe stats factory functions don't handle `{ bigint: true }`. Callers\npassing this option get regular Stats without bigint values. Unlikely\nto matter for most use cases but worth documenting.\n\n### 2.7 `VFSWatcher` / `VFSStatWatcher` API mismatches — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/watcher.js`:\n\n- `VFSWatcher` never emits `'error'` event — errors during polling\n  are silently swallowed in `#getStats()` and `#getStatsFor()`\n- `VFSStatWatcher.addListener(listener)` and\n  `removeListener(listener)` break the `EventEmitter` contract — they\n  take only one argument instead of `(event, listener)`, shadowing\n  the inherited methods with incompatible signatures\n\n---\n\n## 3. Correctness Bugs\n\n### 3.1 Streams stall on destroyed/null fd — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/streams.js`:\n\n- `VirtualWriteStream._write()` (lines 255-258): When\n  `this.#destroyed || this.#fd === null`, the method returns *without*\n  calling `callback()`. The writable stream will hang indefinitely.\n  **Must** call `callback(error)` or `callback()`.\n\n- `VirtualReadStream._read()` (lines 94-97): Same pattern — returns\n  without pushing `null` or calling `destroy()`. The readable stream\n  stalls.\n\n### 3.2 `#checkClosed()` always reports `'read'` syscall — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_handle.js` — Both `VirtualFileHandle` and\n`MemoryFileHandle` implementations of `#checkClosed()` always throw\n`createEBADF('read')`, even when called from write, truncate, or stat\noperations. The error message will be misleading.\n\n### 3.3 `writeFileSync` misses `'ax'` / `'ax+'` append flags — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_handle.js:508` — The append-mode check only\nhandles `'a'` and `'a+'`, missing `'ax'` and `'ax+'`. Data written\nvia `writeFileSync` with `flags: 'ax'` will replace content instead\nof appending. The `#isAppend()` method already handles all four\nvariants and should be used instead.\n\n### 3.4 Dead ternary in `#hookProcessCwd` — LOW\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_system.js:282-283`:\n\n```js\nconst normalized = isAbsolute(directory) ?\n  resolvePath(directory) :\n  resolvePath(directory);\n```\n\nBoth branches are identical. Likely a leftover from a refactor.\n\n### 3.5 `SEAProvider.statSync` reads full asset to get size — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/providers/sea.js:304` — `statSync` calls\n`this.#getAssetContent(normalized)` which copies the entire asset into\na new Buffer just to measure `.length`. For large embedded assets,\nthis is wasteful. A size-only path or caching would help.\n\n### 3.6 `SEAProvider` doesn't handle recursive `readdir` — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/sea.js` — `readdirSync` does not handle `{ recursive: true }`.\nCalled with that option, it silently returns only immediate children.\n\n### 3.7 `SEAProvider.openSync` only accepts string flag `'r'` — LOW\n\n**Disposition: follow-up**\n\n`providers/sea.js:273` — Rejects numeric flags like `O_RDONLY = 0`.\nThe `MemoryProvider` handles this via `normalizeFlags()` but\n`SEAProvider` does not.\n\n### 3.8 Shared mutable `statsArray` is fragile — MEDIUM\n\n**Disposition: note**\n\n`lib/internal/vfs/stats.js:27` — A module-level `Float64Array(18)`\nsingleton is reused across all stats creation calls. This is safe only\nbecause `getStatsFromBinding()` copies synchronously. Any future\nrefactor that makes it async will introduce silent data corruption.\nShould at minimum have a comment asserting this invariant.\n\n---\n\n## 4. Windows Path Handling\n\n### 4.1 Case-insensitive path comparison missing — CRITICAL\n\n**Disposition: follow-up (depending on Windows CI status)**\n\nAll path comparisons in the VFS are case-sensitive:\n\n- `router.js:30` — `normalizedPath === mountPoint` (strict equality)\n- `router.js:40` — `StringPrototypeStartsWith(normalizedPath, prefix)`\n- `memory.js:293` — `current.children.get(segment)` (Map is case-sensitive)\n\nOn Windows, `C:\\Virtual` and `c:\\virtual` and `C:\\VIRTUAL` are the\nsame path, but the VFS treats them as different. A case-variant access\nsilently bypasses the VFS and falls through to the real filesystem.\n\nThis is both a correctness bug and a security vulnerability if the VFS\nis used for sandboxing.\n\n**Recommendation:** On `process.platform === 'win32'`, normalize paths\nto a canonical case before comparison. Provider Map lookups should\nalso be case-insensitive on Windows.\n\n### 4.2 UNC paths (`\\\\server\\share`) unsupported — HIGH\n\n**Disposition: follow-up**\n\nZero UNC path handling exists. The `MemoryProvider`'s\n`#normalizePath` converts `\\\\server\\share\\file` →\n`//server/share/file` → `/server/share/file` (POSIX normalize\ncollapses `//`), destroying the UNC prefix. Should either be\nexplicitly rejected with a clear error or properly supported.\n\n### 4.3 Virtual CWD uses hardcoded `/` separator — MEDIUM\n\n**Disposition: follow-up**\n\n`file_system.js:211` — `const resolved = \\`${this[kVirtualCwd]}/${inputPath}\\``\nuses a hardcoded `/` to join paths. On Windows this produces mixed\nseparators like `C:\\virtual\\subdir/relative/path`. While `resolvePath()`\nnormalizes this on the next line, it's fragile. Should use\n`resolvePath(this[kVirtualCwd], inputPath)` directly.\n\n### 4.4 `findVFSPackageJSON` mixed separator check — LOW\n\n**Disposition: note**\n\n`setup.js:352-353` checks both `'/node_modules'` and\n`'\\\\node_modules'`. On each platform one branch is dead code. Using\n`sep + 'node_modules'` would be cleaner.\n\n---\n\n## 5. Performance Concerns\n\n### 5.1 Async callback APIs are sync under the hood — HIGH\n\n**Disposition: follow-up**\n\nThroughout the codebase, callback-based async fs functions delegate to\nsync VFS operations wrapped in `process.nextTick`. This affects:\n\n- **`lib/fs.js`** — All 96+ callback interception points call the\n  sync handler and deliver results via `nextTick`\n- **`file_system.js`** — `rm()`, `truncate()`, `ftruncate()`,\n  `link()`, `mkdtemp()`, `opendir()` all call their sync counterparts\n- **`MemoryProvider`** — Every async method calls the sync counterpart\n- **`SEAProvider`** — Same pattern\n\nThis means the async API provides zero concurrency benefit and blocks\nthe event loop. For the in-memory and SEA providers this is pragmatic,\nbut for `RealFSProvider` (which has genuine async I/O) or user-created\ncustom providers, this is a real problem since the `lib/fs.js`\ninterception layer forces sync execution regardless of provider\ncapability.\n\n**Recommendation:** Document this limitation. Consider allowing the\nhandlers in `setup.js` to return a Promise that the `lib/fs.js`\ncallback path can handle asynchronously.\n\n### 5.2 `existsSync` called in async code paths — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/setup.js:236` — `findVFSWithAsync()` calls\n`vfs.existsSync()` (sync!) inside an async function. If a custom\nprovider's `existsSync` is expensive (e.g., network-backed), this\nblocks the event loop in a nominally async path.\n\n### 5.3 Linear scan of `activeVFSList` — LOW\n\n**Disposition: note**\n\nAll `findVFSFor*` functions iterate the active VFS list linearly.\nWith many mounted VFS instances this could become slow, though in\npractice the list is likely very small (1-3 instances).\n\n---\n\n## 6. Architecture \u0026 Design\n\n### 6.1 Maintenance burden of 164+ interception points — HIGH\n\n**Disposition: follow-up (design discussion)**\n\nThe VFS integration adds:\n- ~96 interception blocks in `lib/fs.js`\n- ~68 interception blocks in `lib/internal/fs/promises.js`\n\nEvery new `fs` API must remember to add a VFS hook. This is a\nsignificant ongoing maintenance burden and a source of future bugs\nwhen hooks are forgotten.\n\n**Recommendation:** Consider whether the interception could be\ncentralized (e.g., a Proxy-based approach, or a single dispatch\nfunction that handles all operations) to reduce the per-function\nboilerplate.\n\n### 6.2 Cross-VFS operations not handled — MEDIUM\n\n**Disposition: follow-up**\n\n`setup.js` — `renameSync`, `copyFileSync`, `linkSync` handlers\nresolve `newPath`/`dest` using the source VFS. If `newPath` is in a\ndifferent VFS mount or outside any VFS, the operation is still\ndispatched to the source VFS, which will likely fail in confusing\nways.\n\n### 6.3 Package.json cache not invalidated on unmount — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/modules/package_json_reader.js` caches in\n`moduleToParentPackageJSONCache` and `deserializedPackageJSONCache`\nare never cleared when a VFS is unmounted. Only the CJS `_pathCache`\nand stat cache are cleared (in `setup.js:92-93`). Stale VFS\npackage.json data persists after unmount.\n\n### 6.4 C++ format coupling in package.json serialization — MEDIUM\n\n**Disposition: note**\n\n`setup.js` `serializePackageJSON()` must produce tuples matching the\nexact format of the C++ `readPackageJSON` binding. The VFS does a\nparse→serialize→deserialize round-trip (JSON parse the file, extract\nfields, stringify certain fields, then the reader deserializes again).\nIf the C++ format changes, the JS VFS serialization must be updated\ntoo. This is a fragile contract that should be documented with a\ncross-reference.\n\n### 6.5 `legacyMainResolve` has hardcoded extension arrays — LOW\n\n**Disposition: note**\n\n`setup.js` — The VFS implementation of `legacyMainResolve` hardcodes\nextension arrays matching the C++ implementation. If the C++ side\nchanges the extension mapping, this will silently diverge.\n\n---\n\n## 7. Test Runner Mock Integration\n\n### 7.1 `MockFSContext.restore()` error prevents other mock cleanup — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/test_runner/mock/mock.js` — If `vfs.unmount()` throws\ninside `restore()`, the error propagates through `restoreAll()` which\nhas no try/catch, preventing subsequent mocks from being restored.\n\n### 7.2 `MockFSContext.vfs` property exposes full VFS instance — LOW\n\n**Disposition: note**\n\nThe public `.vfs` property gives test code direct access to the full\n`VirtualFileSystem` instance, allowing unintended operations like\ncalling `mount()` at a different prefix. Consider making this a\nprivate field.\n\n### 7.3 Platform-specific `parentDir !== '/'` check — LOW\n\n**Disposition: follow-up**\n\n`mock.js:441` — `if (parentDir !== '/')` doesn't account for Windows\nroots like `C:\\`. Should use `path.parse()` or compare against the\nmount prefix root.\n\n---\n\n## 8. Provider-Specific Issues\n\n### 8.1 `MemoryProvider`: `statSync` reports size 0 for dynamic files — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/memory.js:408` — When a file has a `contentProvider` (dynamic\ncontent) and no explicit `size`, `statSync` reads `entry.content.length`\nwhich is the initial empty buffer (`Buffer.alloc(0)`), reporting size 0.\n\n### 8.2 `MemoryProvider`: `symlinkSync` auto-creates parent dirs — LOW\n\n**Disposition: note**\n\n`providers/memory.js:808` — `symlinkSync` passes `create=true` to\n`#ensureParent`, auto-creating intermediate directories. Other\nmutating operations pass `create=false`. This is inconsistent and\ncould surprise users.\n\n### 8.3 `MemoryProvider`: Recursive `readdir` doesn't follow symlinks — LOW\n\n**Disposition: note**\n\n`providers/memory.js:597` — `#readdirRecursive` checks\n`childEntry.isDirectory()` directly without following symlinks. This\nmay differ from real `readdir` behavior where `{ recursive: true }`\nfollows symlinks.\n\n### 8.4 `RealFSProvider`: Path traversal via symlinks — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/real.js` — `#resolvePath` validates the logical path but\ndoes NOT resolve symlinks before checking. If the real filesystem has\na symlink inside `rootPath` pointing outside it, operations follow\nthat symlink and access files outside the jail. A proper chroot would\nneed `fs.realpathSync` in `#resolvePath` or a post-resolution check.\n\n### 8.5 `RealFSProvider`: No watch support — LOW\n\n**Disposition: note**\n\nThe real filesystem provider doesn't implement `watch`/`watchFile`/\n`unwatchFile` despite wrapping a real filesystem that could easily\nsupport them.\n\n---\n\n## 9. Test Coverage Gaps\n\n### 9.1 Windows tests are minimal — HIGH\n\n**Disposition: follow-up**\n\n`test/parallel/test-vfs-windows.js` has only 4 test cases (100 lines).\nMissing coverage:\n\n| Missing Test | Priority |\n|-------------|----------|\n| Case-insensitive path matching | CRITICAL |\n| UNC paths | HIGH |\n| Mixed separators (`C:\\virtual/subdir\\file.txt`) | HIGH |\n| Path traversal with `..` on Windows | HIGH |\n| Forward-slash Windows paths (`C:/virtual/file.txt`) | MEDIUM |\n| Overlay mode on Windows | MEDIUM |\n| Virtual CWD on Windows | MEDIUM |\n| Write operations through `fs` on Windows | MEDIUM |\n\n### 9.2 `fsPromises.open()` / FileHandle path untested — HIGH\n\n**Disposition: follow-up**\n\nNo tests verify that `fsPromises.open()` works with VFS paths\n(because it doesn't — see §2.2). Tests should be added once this\nis supported.\n\n### 9.3 Overlapping mount behavior untested — MEDIUM\n\n**Disposition: follow-up**\n\nNo tests verify behavior when multiple VFS instances mount at\noverlapping paths.\n\n### 9.4 Permission model interaction untested — MEDIUM\n\n**Disposition: follow-up**\n\nNo tests verify VFS behavior when `--experimental-permission` is\nactive.\n\n---\n\n## 10. Code Quality \u0026 Cleanup\n\n### 10.1 Watcher unbounded memory growth — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/watcher.js`:\n\n- `VFSWatchAsyncIterable.#pendingEvents` grows without bound if\n  the consumer reads slower than events arrive\n- `#pendingResolvers` grows without bound if `next()` is called\n  many times without events\n- No backpressure mechanism exists\n\n### 10.2 Redundant `#destroyed` field in streams — LOW\n\n**Disposition: note**\n\nBoth `VirtualReadStream` and `VirtualWriteStream` track `#destroyed`\nmanually, but `Readable` and `Writable` already have a `destroyed`\nproperty.\n\n### 10.3 `SEAProvider` primordials inconsistency — LOW\n\n**Disposition: follow-up**\n\n`providers/sea.js`:\n- Line 226: Uses `path.replace()` regex instead of\n  `StringPrototypeReplaceAll` from primordials\n- Line 335: Uses spread `[...children]` instead of `ArrayFrom`\n  from primordials\n\n### 10.4 `VirtualFileHandle` private method inaccessible to subclass — LOW\n\n**Disposition: note**\n\n`file_handle.js` — `#checkClosed()` is a private method on the base\nclass, but private methods aren't inherited. `MemoryFileHandle`\nre-declares its own `#checkClosed()` at line 261. The duplication\nconfirms the pattern doesn't work cleanly. Consider making it a\nregular method or using the symbol-based pattern already used for\nproperties.\n\n### 10.5 `file_system.js` and `setup.js` are very large — LOW\n\n**Disposition: note**\n\n`file_system.js` (1347 lines) and `setup.js` (1080 lines) are\nsubstantial, driven by the three API surfaces (sync/callback/promise)\nand comprehensive fs handler delegation. The code is functional but\ndifficult to navigate. Consider splitting in follow-ups.\n\n### 10.6 `VirtualFD` range starts at 10,000 — LOW\n\n**Disposition: note**\n\n`lib/internal/vfs/fd.js:18` — Virtual FDs start at 10,000 to avoid\ncollision with real OS FDs. As noted in a PR comment by @arcanis, this\nis low for long-running apps that open/close many files. The fslib\nproject reserves the upper byte of an i32 instead. No FD reuse or\nwraparound exists. Worth revisiting in a follow-up.\n\n---\n","author":{"url":"https://github.com/jasnell","@type":"Person","name":"jasnell"},"datePublished":"2026-03-18T21:10:52.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":9},"url":"https://github.com/62328/node/issues/62328"}

route-pattern/_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format)
route-controllervoltron_issues_fragments
route-actionissue_layout
fetch-noncev2:75259c7c-d9dd-5cb7-64a6-5a5f3268f30b
current-catalog-service-hash81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114
request-id902E:13B1D:279BC2:36B0FF:6A4C6CC9
html-safe-nonce9d9e739e61c4c41e13d8ac5315c0c8f976d2db751874f3b299f332742b996f29
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5MDJFOjEzQjFEOjI3OUJDMjozNkIwRkY6NkE0QzZDQzkiLCJ2aXNpdG9yX2lkIjoiMjY4NDA3MTkyNzE2NTUxMjkwNSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9
visitor-hmac501add35bdb4eeeebe2d43dd0e28838125a453be3b3e8e1ba8f5e4f17f9b4964
hovercard-subject-tagissue:4097736900
github-keyboard-shortcutsrepository,issues,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///voltron/issues_fragments/issue_layout
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/62328/issue_layout
twitter:imagehttps://opengraph.githubassets.com/e68d47864ac76f3bf44df5bd4750219682ba5791ace54650e7e226fb92bb5aee/nodejs/node/issues/62328
twitter:cardsummary_large_image
og:imagehttps://opengraph.githubassets.com/e68d47864ac76f3bf44df5bd4750219682ba5791ace54650e7e226fb92bb5aee/nodejs/node/issues/62328
og:image:alt/cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. ...
og:image:width1200
og:image:height600
og:site_nameGitHub
og:typeobject
og:author:usernamejasnell
hostnamegithub.com
expected-hostnamegithub.com
None3d11bb817438277de2a940854450e83a7d32b6aeb5014e9e6b00a6423900251c
turbo-cache-controlno-preview
go-importgithub.com/nodejs/node git https://github.com/nodejs/node.git
octolytics-dimension-user_id9950313
octolytics-dimension-user_loginnodejs
octolytics-dimension-repository_id27193779
octolytics-dimension-repository_nwonodejs/node
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id27193779
octolytics-dimension-repository_network_root_nwonodejs/node
turbo-body-classeslogged-out env-production page-responsive
disable-turbofalse
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
release14099438da5379150f15a2892474c7c7e6c0e55e
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://github.com/nodejs/node/issues/62328#start-of-content
https://github.com/
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fissues%2F62328
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub Copilot appDirect agents from issue to mergehttps://github.com/features/ai/github-app
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
View all resourceshttps://github.com/resources
GitHub SponsorsFund open source developershttps://github.com/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/accelerator
GitHub Starshttps://stars.github.com
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fissues%2F62328
Sign up https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fvoltron%2Fissues_fragments%2Fissue_layout&source=header-repo&source_repo=nodejs%2Fnode
Reloadhttps://github.com/nodejs/node/issues/62328
Reloadhttps://github.com/nodejs/node/issues/62328
Reloadhttps://github.com/nodejs/node/issues/62328
Please reload this pagehttps://github.com/nodejs/node/issues/62328
nodejs https://github.com/nodejs
nodehttps://github.com/nodejs/node
Please reload this pagehttps://github.com/nodejs/node/issues/62328
Notifications https://github.com/login?return_to=%2Fnodejs%2Fnode
Fork 36k https://github.com/login?return_to=%2Fnodejs%2Fnode
Star 118k https://github.com/login?return_to=%2Fnodejs%2Fnode
Code https://github.com/nodejs/node
Issues 1.4k https://github.com/nodejs/node/issues
Pull requests 964 https://github.com/nodejs/node/pulls
Actions https://github.com/nodejs/node/actions
Projects https://github.com/nodejs/node/projects
Security and quality 0 https://github.com/nodejs/node/security
Insights https://github.com/nodejs/node/pulse
Code https://github.com/nodejs/node
Issues https://github.com/nodejs/node/issues
Pull requests https://github.com/nodejs/node/pulls
Actions https://github.com/nodejs/node/actions
Projects https://github.com/nodejs/node/projects
Security and quality https://github.com/nodejs/node/security
Insights https://github.com/nodejs/node/pulse
Virtual File System Follow Upshttps://github.com/nodejs/node/issues/62328#top
vfsIssues and PRs related to the virtual filesystem subsystem.https://github.com/nodejs/node/issues?q=state%3Aopen%20label%3A%22vfs%22
https://github.com/jasnell
jasnellhttps://github.com/jasnell
on Mar 18, 2026https://github.com/nodejs/node/issues/62328#issue-4097736900
@mcollinahttps://github.com/mcollina
Virtual File Systemhttps://github.com/nodejs/node/pull/61478
#61478https://github.com/nodejs/node/pull/61478
Security & Permission Modelhttps://github.com/nodejs/node/issues/62328#1-security--permission-model
API Compatibility Gapshttps://github.com/nodejs/node/issues/62328#2-api-compatibility-gaps
Correctness Bugshttps://github.com/nodejs/node/issues/62328#3-correctness-bugs
Windows Path Handlinghttps://github.com/nodejs/node/issues/62328#4-windows-path-handling
Performance Concernshttps://github.com/nodejs/node/issues/62328#5-performance-concerns
Architecture & Designhttps://github.com/nodejs/node/issues/62328#6-architecture--design
Test Runner Mock Integrationhttps://github.com/nodejs/node/issues/62328#7-test-runner-mock-integration
Provider-Specific Issueshttps://github.com/nodejs/node/issues/62328#8-provider-specific-issues
Test Coverage Gapshttps://github.com/nodejs/node/issues/62328#9-test-coverage-gaps
Code Quality & Cleanuphttps://github.com/nodejs/node/issues/62328#10-code-quality--cleanup
@Qardhttps://github.com/Qard
#61478 (comment)https://github.com/nodejs/node/pull/61478#issuecomment-2868746178
@arcanishttps://github.com/arcanis
vfsIssues and PRs related to the virtual filesystem subsystem.https://github.com/nodejs/node/issues?q=state%3Aopen%20label%3A%22vfs%22
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.