Summary
Server-side JavaScript resources on an FXServer run in an embedded Node.js (libnode 22). FiveM gates this runtime with a custom permission handler that intercepts access such as filesystem, child processes, workers and the inspector.
Native addon loading is not covered by that gate. Node 22 simply has no permission
scope for addons — the corresponding mechanism (--allow-addons) only exists from Node
24 onwards. The permission callback is therefore never invoked when an addon is loaded.
Any JavaScript resource can load a .node file (a compiled DLL) it ships in its own
folder and thereby execute arbitrary native code directly inside the server process.
This is the maximal sandbox escape: it bypasses every script-level mitigation (VFS boundaries, filesystem permissions, Lua/JS sandboxing).
Root cause
The permission handler is the only gate for server JS. Its scope list (filesystem, child
process, worker, inspector) matches the Node 22 upstream exactly — and Node 22 has no
addon scope. libnode's binding layer therefore contains no permission check for the
addon load path (dlopen). The installed handler is never consulted when a resource
loads a native module.
There is no compensating control: no module whitelisting, and the gated filesystem layer is irrelevant because the operating system's native loader reads the DLL directly.
Impact
- Full server compromise (code execution as the server process) from any installed third-party JS resource.
- Persistence and stealth: native code can evade resource introspection and survive script-runtime restarts.
- Availability: even a broken addon produces a persistent crash loop when combined with auto-restart.
Fix
- Backport the Node 24 addon permission scope (
kAddon, default-deny) into the patched libnode and enforce it in thedlopenpath via the existing permission handler. - In the interim: block
process.dlopen/require('*.node')in the runtime for non-system resources. - Long term: a module-type whitelist for third-party resources (JS/Lua only, no native modules).