Skip to content

Upstream pkg-fetch patches to Node.js core to eliminate custom binaries #231

@robertsLando

Description

@robertsLando

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)

  • child_process.fork() bug fix (patch 8) — one-line PR
  • Build fixes (patch 9) — individual small PRs for each fix

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)

  • Write RFC for bytecode-only execution mode
  • Socialize in nodejs/node discussions
  • Submit PR if RFC is accepted

Eliminates patch 4 (the most complex and fragile).

Tier 4 — Configure Flags (lower priority)

  • Propose --disable-inspector configure flag
  • Or rely on SEA's existing inspector restrictions

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions