# Behavior traces — nodejs/node

> Canonical page: https://codevetter.com/unpack/node/behavior-traces

# Behavior traces — nodejs/node

Scanned commit 976a36a5b6ce737c9e695f6cde8e141427f13737 (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Three flows characterise the runtime: cold startup of a script, mounting a VFS and loading an entry point from inside it, and a permission-checked FFI `dlopen`. All three share the same spine — C++ picks a main script, that script calls `prepareExecution()`, and `prepareExecution()` decides what the realm is allowed to do. The VFS trace is the most intricate because mounting is deliberately deferred past `--import` preloads so a preload can register a custom provider. The FFI trace is the clearest example of a two-check security path, where a purely in-memory operation still demands filesystem-write permission for platform-consistency reasons.
- Cold start (script): `main()` → `node::Start` → `sea::FixupArgsForSEA` → `StartInternal` → `LoadSnapshotData` → `NodeMainInstance::Run` → C++ `StartExecution` picks `internal/main/run_main_module`. — [src/node_main.cc#L95-99](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/src/node_main.cc#L95-L99) [src/node.cc#L1716-1735](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/src/node.cc#L1716-L1735) [src/node.cc#L404-407](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/src/node.cc#L404-L407)
- `run_main_module.js` then calls `prepareMainThreadExecution(!isEntryURL)` → `markBootstrapComplete()` → `RegExpPrototypeExec(/^/, '')` to reset RegExp statics before user code → `Module.runMain(mainEntry)`. — [lib/internal/main/run_main_module.js#L13-33](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/main/run_main_module.js#L13-L33)
- `Module.runMain` is invoked even for ESM entry points solely to preserve legacy monkey-patchability of `Module.runMain` by `--require` preloads; the file comments this as undesirable and slated for removal once module customization hooks suffice. — [lib/internal/main/run_main_module.js#L20-33](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/main/run_main_module.js#L20-L33) [lib/internal/modules/cjs/loader.js#L520](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/modules/cjs/loader.js#L520)
- Inside `prepareExecution()` the order is fixed: `refreshRuntimeOptions()` → `patchProcessObject()` → trace/inspector/navigator/warning setup → the feature `setupX()` block → `initializeReport()` → `setupDiagnosticsChannel()` → `initializePermission()` → source maps → deprecations → `setupVmModules()` → `initializeModuleLoaders()` → `finishVfsMounts()` → `setupHttpProxy()`. — [lib/internal/process/pre_execution.js#L109-198](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L109-L198)
- Each flag-gated builtin is unlocked by the same three lines — check `getOptionValue('--experimental-X')`, require `BuiltinModule` from `internal/bootstrap/realm`, call `allowRequireByUsers('X')` — repeated verbatim in `setupBench`, `setupFFI`, `setupSQLite`, `setupStreamIter`, `setupDTLS`, `setupQuic`, `setupVfs`. — [lib/internal/process/pre_execution.js#L499-569](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L499-L569) [lib/internal/bootstrap/realm.js#L275-285](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/bootstrap/realm.js#L275-L285)
- VFS mount trace: `finishVfsMounts()` guards on `vfsMounted` → reads `--vfs-mount` entries → `path.resolve` + `fs.statSync` each (throwing `ERR_VFS_INVALID_TARGET` on failure or on a non-file/non-dir) → `selectProvider()` → `new VirtualFileSystem(provider)` → `vfs.mount()`. — [lib/internal/process/pre_execution.js#L259-296](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L259-L296) [lib/internal/vfs/file_system.js#L179-196](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/vfs/file_system.js#L179-L196)
- Mounting is called from two places with an idempotence guard: from `prepareExecution()` only when `--import` is empty, otherwise from `run_main` after the `--import` loop — so an `--import` preload can `registerProvider()` before any source's provider is chosen. — [lib/internal/process/pre_execution.js#L186-192](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L186-L192) [lib/internal/modules/run_main.js#L144-152](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/modules/run_main.js#L144-L152)
- For `--vfs-load`, `getVfsLoadIndex()` re-walks `process.execArgv`-style option names to recover which position in the shared `vfs_mounts` list came from `--vfs-load`, because the option parser stores plain strings and cannot record provenance. — [lib/internal/process/pre_execution.js#L219-253](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L219-L253)
- The `--vfs-load` source is spliced into `process.argv` at index 1 rather than overwriting it, so the program's own first positional argument is not silently dropped; `argv[1]` names the real source while the reserved mount point stays an implementation detail. — [lib/internal/process/pre_execution.js#L291-303](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L291-L303)
- `src/node.cc` routes to `run_main_module` when `vfs_load` is set even with no positional argument, instead of falling through to the REPL or stdin-eval — a dedicated branch added for this flag. — [src/node.cc#L400-407](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/src/node.cc#L400-L407)
- FFI trace: `new ffi.DynamicLibrary(path)` → `DynamicLibrary::New` → construct-call check → `THROW_IF_INSUFFICIENT_PERMISSIONS(kFFI)` → null-byte check on the path → optional byte materialisation → `uv_dlopen` → `image.AfterOpen()`. — [src/node_ffi.cc#L493-570](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/src/node_ffi.cc#L493-L570)
- When an FFI library is supplied as bytes (a library living inside a VFS that `dlopen` cannot reach by path), the code additionally demands `kFileSystemWrite` on `AddonImage::TempDir()` — and comments that the check is enforced even on Linux where an anonymous memfd is used, so grants do not vary by platform. — [src/node_ffi.cc#L533-556](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/src/node_ffi.cc#L533-L556)
- `initializePermission()` hardens the realm before granting: it replaces `process.binding` with a thrower, `ObjectFreeze`s the `path` module so it cannot be monkey-patched to defeat path checks, emits `SecurityWarning` codes PERM0001–PERM0007 for each dangerous `--allow-*`, and only then defines `process.permission`. — [lib/internal/process/permission.js#L1-10](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/permission.js#L1-L10) [lib/internal/process/pre_execution.js#L815-878](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L815-L878)
- Without `--permission`, passing any `--allow-*` flag is a hard error: the else-branch iterates `availableFlags()` and throws `ERR_MISSING_OPTION`, preventing a false sense of restriction. — [lib/internal/process/pre_execution.js#L878-886](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L878-L886) [lib/internal/process/permission.js#L66-88](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/permission.js#L66-L88)
- Worker startup diverges: `internal/main/worker_thread.js` calls `prepareWorkerThreadExecution()` (which asserts `initializeModules === false`), waits for a `LOAD_SCRIPT` message on the env message port, and only then calls `initializeModuleLoaders` and `finishVfsMounts`. — [lib/internal/main/worker_thread.js#L16-45](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/main/worker_thread.js#L16-L45) [lib/internal/process/pre_execution.js#L69-80](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L69-L80) [lib/internal/process/pre_execution.js#L170-176](https://github.com/nodejs/node/blob/976a36a5b6ce737c9e695f6cde8e141427f13737/lib/internal/process/pre_execution.js#L170-L176)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
