Goal
Eliminate the need for pkg-fetch to compile and distribute custom-patched Node.js binaries. Every Node.js version requires ~600-850 lines of patches across ~25 files — this is the single biggest maintenance burden in the project. If we upstream the right changes to Node.js core, pkg can use stock Node.js binaries.
This issue tracks each patch category, its upstream path, and progress.
Related: #204 (SEA migration strategy)
Patch Inventory
Every pkg-fetch patch applies the same 9 conceptual changes, adapted per Node.js version. Here's what each does and how to eliminate it:
1. Binary Entrypoint Injection ("BAKERY") — node_main.cc
What: Replaces main() with reorder() that reads baked-in CLI flags from a 200-byte marker string. Uses volatile + optimization barriers to prevent the compiler from stripping it.
Why pkg needs it: Injects flags like --no-warnings and sets PKG_DUMMY_ENTRYPOINT as argv[1] to redirect execution to the embedded payload.
Upstream path: Eliminated by --build-sea — SEA has its own injection mechanism via sea-config.json execArgv. No upstream PR needed.
2. Bootstrap/Prelude Loader — bootstrap/pkg.js + node.cc
What: Adds StartExecution(env, "internal/bootstrap/pkg") early in Node.js startup. The JS bootstrap reads the executable, finds PAYLOAD_POSITION/PRELUDE_POSITION markers, and executes the prelude via vm.Script.
Why pkg needs it: Runs pkg's VFS setup before any user code.
Upstream path: Eliminated by node:vfs (nodejs/node#61478). VFS provides native mounting — no need for a custom bootstrap.
3. VFS Interception (Module Loading) — cjs/loader.js + package_json_reader.js
What: Replaces native C++ bindings (internalModuleStat, readPackageJSON) with JS equivalents that route through require('fs') instead of directly hitting the OS filesystem.
Why pkg needs it: Without this, require() resolution bypasses the prelude's fs monkey-patching entirely — modules can't be loaded from the VFS.
Upstream path: Eliminated by node:vfs (nodejs/node#61478). With native VFS hooks in the module loader, no interception is needed.
4. V8 Sourceless Bytecode — 8 V8 files + vm.js + node_contextify.cc (~100 lines)
What: The most complex patch. Adds 3 new V8 APIs:
EnableCompilationForSourcelessUse() — forces eager compilation (lazy=false, predictable=true)
DisableCompilationForSourcelessUse() — restores flags
FixSourcelessScript() — strips source from compiled script, leaving only bytecode
Also: prevents GC from flushing sourceless bytecode, adds parser guards for undefined source, removes code cache sanity checks (source hash, flags hash), adds Function.prototype.toString() fallback ("class {}") for sourceless classes, and adds sourceless option to vm.Script.
Why pkg needs it: Enables shipping V8 bytecode without source code for IP protection and potentially faster startup.
Upstream path: Propose as RFC — "V8 bytecode-only execution mode" behind --allow-sourceless-scripts or as a vm.Script option. Use cases: code protection for commercial apps, reduced deployment size, edge computing. This is the highest-impact upstream proposal but also the most contentious (security implications of disabling sanity checks).
5. SIGUSR1 Removal — node.cc + inspector_agent.cc
What: Removes SIGUSR1 signal handler setup and StartDebugSignalHandler() call.
Why pkg needs it: Packaged apps may use SIGUSR1 for their own purposes (log rotation, graceful reload). The default handler would unexpectedly try to start an inspector session.
Upstream path: Propose --disable-inspector configure flag or leverage SEA's existing inspector restrictions.
6. Debug Options Disabled — node_options.cc
What: Adds return; at the start of DebugOptionsParser constructor, preventing registration of --inspect* options.
Why pkg needs it: Prevents exposing embedded source/bytecode via the debugger.
Upstream path: Same as patch 5 — a --disable-inspector flag covers both.
7. Process Init Guards — pre_execution.js
What: Adds double-initialization guard (_alreadyPrepared flag), PKG_DUMMY_ENTRYPOINT check, and --require assertion.
Why pkg needs it: Prevents crashes from pkg's unusual startup sequence where prepareMainThreadExecution() is called twice.
Upstream path: Eliminated by node:vfs — with native VFS, there's no unusual startup sequence.
8. child_process.fork() Bug — child_process.js (1 line)
What: Changes spawn(...) to module.exports.spawn(...).
Why pkg needs it: Direct call to locally-scoped spawn bypasses any monkey-patching on child_process.spawn, so pkg's child process interception never fires.
Upstream path: Submit as bug fix PR to Node.js core. This is a legitimate bug — any tool that wraps child_process.spawn via exports is affected. One-line fix with a test case.
9. Build System Fixes — Various .gyp, .cc files (~150 lines)
What: Collection of portability/correctness fixes:
- GCC LTO: removes
-fuse-linker-plugin from common.gypi
- ICU: adds
-fno-lto for POSIX in icu-generic.gyp
- ngtcp2: fixes
__popcnt MSVC ARM conflict
- V8
CSA_HOLE_SECURITY_CHECK: strips __FILE__/__LINE__ for reproducible builds
- V8
wasm-compiler.cc: C++20 designated initializer compat
- V8
read-only-serializer.cc: memset before memcpy for deterministic output without pointer compression
Upstream path: Submit as individual PRs. These are general portability improvements, not pkg-specific. Some may already be fixed in newer dependency versions.
Upstream Strategy
Tier 1 — Submit Now (high acceptance, low risk)
Tier 2 — Help Land VFS PR (highest impact)
Landing VFS eliminates patches 2, 3, 7 and reduces the need for patch 1.
Tier 3 — RFC: V8 Sourceless Bytecode (high impact, needs socialization)
Eliminates patch 4 (the most complex and fragile).
Tier 4 — Configure Flags (lower priority)
Eliminates patches 5, 6.
Elimination Matrix
| Patch |
Eliminated by |
Depends on |
| 1. BAKERY |
--build-sea |
SEA mode (already available) |
| 2. Bootstrap |
node:vfs PR |
nodejs/node#61478 |
| 3. Module interception |
node:vfs PR |
nodejs/node#61478 |
| 4. V8 sourceless |
Upstream bytecode API |
New RFC |
| 5. SIGUSR1 |
--disable-inspector flag |
New PR or SEA restrictions |
| 6. Debug options |
--disable-inspector flag |
New PR or SEA restrictions |
| 7. Init guards |
node:vfs PR |
nodejs/node#61478 |
| 8. fork() bug |
Bug fix PR |
None — ready to submit |
| 9. Build fixes |
Individual PRs |
None — ready to submit |
End state: zero patches needed. pkg uses stock Node.js binaries via --build-sea + node:vfs.
Goal
Eliminate the need for
pkg-fetchto compile and distribute custom-patched Node.js binaries. Every Node.js version requires ~600-850 lines of patches across ~25 files — this is the single biggest maintenance burden in the project. If we upstream the right changes to Node.js core,pkgcan use stock Node.js binaries.This issue tracks each patch category, its upstream path, and progress.
Related: #204 (SEA migration strategy)
Patch Inventory
Every
pkg-fetchpatch applies the same 9 conceptual changes, adapted per Node.js version. Here's what each does and how to eliminate it:1. Binary Entrypoint Injection ("BAKERY") —
node_main.ccWhat: Replaces
main()withreorder()that reads baked-in CLI flags from a 200-byte marker string. Usesvolatile+ optimization barriers to prevent the compiler from stripping it.Why pkg needs it: Injects flags like
--no-warningsand setsPKG_DUMMY_ENTRYPOINTas argv[1] to redirect execution to the embedded payload.Upstream path: Eliminated by
--build-sea— SEA has its own injection mechanism viasea-config.jsonexecArgv. No upstream PR needed.2. Bootstrap/Prelude Loader —
bootstrap/pkg.js+node.ccWhat: Adds
StartExecution(env, "internal/bootstrap/pkg")early in Node.js startup. The JS bootstrap reads the executable, findsPAYLOAD_POSITION/PRELUDE_POSITIONmarkers, and executes the prelude viavm.Script.Why pkg needs it: Runs pkg's VFS setup before any user code.
Upstream path: Eliminated by
node:vfs(nodejs/node#61478). VFS provides native mounting — no need for a custom bootstrap.3. VFS Interception (Module Loading) —
cjs/loader.js+package_json_reader.jsWhat: Replaces native C++ bindings (
internalModuleStat,readPackageJSON) with JS equivalents that route throughrequire('fs')instead of directly hitting the OS filesystem.Why pkg needs it: Without this,
require()resolution bypasses the prelude'sfsmonkey-patching entirely — modules can't be loaded from the VFS.Upstream path: Eliminated by
node:vfs(nodejs/node#61478). With native VFS hooks in the module loader, no interception is needed.4. V8 Sourceless Bytecode — 8 V8 files +
vm.js+node_contextify.cc(~100 lines)What: The most complex patch. Adds 3 new V8 APIs:
EnableCompilationForSourcelessUse()— forces eager compilation (lazy=false,predictable=true)DisableCompilationForSourcelessUse()— restores flagsFixSourcelessScript()— strips source from compiled script, leaving only bytecodeAlso: prevents GC from flushing sourceless bytecode, adds parser guards for undefined source, removes code cache sanity checks (source hash, flags hash), adds
Function.prototype.toString()fallback ("class {}") for sourceless classes, and addssourcelessoption tovm.Script.Why pkg needs it: Enables shipping V8 bytecode without source code for IP protection and potentially faster startup.
Upstream path: Propose as RFC — "V8 bytecode-only execution mode" behind
--allow-sourceless-scriptsor as avm.Scriptoption. Use cases: code protection for commercial apps, reduced deployment size, edge computing. This is the highest-impact upstream proposal but also the most contentious (security implications of disabling sanity checks).5. SIGUSR1 Removal —
node.cc+inspector_agent.ccWhat: Removes SIGUSR1 signal handler setup and
StartDebugSignalHandler()call.Why pkg needs it: Packaged apps may use SIGUSR1 for their own purposes (log rotation, graceful reload). The default handler would unexpectedly try to start an inspector session.
Upstream path: Propose
--disable-inspectorconfigure flag or leverage SEA's existing inspector restrictions.6. Debug Options Disabled —
node_options.ccWhat: Adds
return;at the start ofDebugOptionsParserconstructor, preventing registration of--inspect*options.Why pkg needs it: Prevents exposing embedded source/bytecode via the debugger.
Upstream path: Same as patch 5 — a
--disable-inspectorflag covers both.7. Process Init Guards —
pre_execution.jsWhat: Adds double-initialization guard (
_alreadyPreparedflag),PKG_DUMMY_ENTRYPOINTcheck, and--requireassertion.Why pkg needs it: Prevents crashes from pkg's unusual startup sequence where
prepareMainThreadExecution()is called twice.Upstream path: Eliminated by
node:vfs— with native VFS, there's no unusual startup sequence.8.
child_process.fork()Bug —child_process.js(1 line)What: Changes
spawn(...)tomodule.exports.spawn(...).Why pkg needs it: Direct call to locally-scoped
spawnbypasses any monkey-patching onchild_process.spawn, so pkg's child process interception never fires.Upstream path: Submit as bug fix PR to Node.js core. This is a legitimate bug — any tool that wraps
child_process.spawnvia exports is affected. One-line fix with a test case.9. Build System Fixes — Various
.gyp,.ccfiles (~150 lines)What: Collection of portability/correctness fixes:
-fuse-linker-pluginfromcommon.gypi-fno-ltofor POSIX inicu-generic.gyp__popcntMSVC ARM conflictCSA_HOLE_SECURITY_CHECK: strips__FILE__/__LINE__for reproducible buildswasm-compiler.cc: C++20 designated initializer compatread-only-serializer.cc:memsetbeforememcpyfor deterministic output without pointer compressionUpstream path: Submit as individual PRs. These are general portability improvements, not pkg-specific. Some may already be fixed in newer dependency versions.
Upstream Strategy
Tier 1 — Submit Now (high acceptance, low risk)
child_process.fork()bug fix (patch 8) — one-line PRTier 2 — Help Land VFS PR (highest impact)
Landing VFS eliminates patches 2, 3, 7 and reduces the need for patch 1.
Tier 3 — RFC: V8 Sourceless Bytecode (high impact, needs socialization)
Eliminates patch 4 (the most complex and fragile).
Tier 4 — Configure Flags (lower priority)
--disable-inspectorconfigure flagEliminates patches 5, 6.
Elimination Matrix
--build-seanode:vfsPRnode:vfsPR--disable-inspectorflag--disable-inspectorflagnode:vfsPREnd state: zero patches needed.
pkguses stock Node.js binaries via--build-sea+node:vfs.