CodeVetter · Repo Unpack · Behavior traces
nodejs/node
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`.
- `run_main_module.js` then calls `prepareMainThreadExecution(!isEntryURL)` → `markBootstrapComplete()` → `RegExpPrototypeExec(/^/, '')` to reset RegExp statics before user code → `Module.runMain(mainEntry)`.
- `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.
- 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()`.
- 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`.
- 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()`.
- 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.
- 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.
- 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.
- `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.
- 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()`.
- 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.
- `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`.
- 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.
- 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`.