Enable nodeIntegration or Use Preload Scripts: Which Safeguards the Renderer Process in NW.js?
0 reputation · 15 Apr 2025, 22:13 UTC
0 reputation · 15 Apr 2025, 22:13 UTC
The aim is to protect the renderer from malicious or accidental access to the full Node.js runtime while still allowing developers to use native modules when needed.
nodeIntegration by default for security.nodeIntegration is disabled, developers can expose a limited API surface via a preload script and contextBridge.contextIsolation is automatically enforced when nodeIntegration is off; some builds still allow direct access to Node globals due to legacy flags.Which approach—enabling nodeIntegration for convenience or using a preload script for isolation—offers the best balance between security and developer ergonomics, and how does NW.js currently enforce contextIsolation when nodeIntegration is disabled?
contextIsolation when nodeIntegration is disabled, or can legacy flags still expose Node globals?For the safest renderer environment, disable nodeIntegration and use a preload script that exposes only the APIs the renderer needs. Enabling nodeIntegration is convenient but gives the renderer full Node.js access, which is a security risk if the page is not fully trusted.
NW.js 0.54 and newer turn nodeIntegration off by default. When it is off, the global Node objects (require, process, __dirname, etc.) are undefined in the renderer. However, NW.js does not automatically enable a separate “context” sandbox; the renderer still runs in the same process and can access native modules only through the preload script. If a build or launch script supplies legacy flags such as --nodejs or --allow-natives-syntax, Node globals can leak back into the renderer, even when nodeIntegration is set to false. Therefore the only guaranteed way to enforce isolation is to set nodeIntegration:false and to avoid legacy flags that re‑enable Node.
A preload script runs before any page script and has full Node access. By exposing a tiny, vetted API surface on window, you keep the renderer sandboxed while still allowing native module usage. Example:
/* preload.js */
const fs = require('fs');
const path = require('path');
// Expose only the helpers the renderer needs
window.myAPI = {
readFile: (filePath) => fs.readFileSync(path.resolve(filePath), 'utf8'),
// add more functions as required
};
In your window options (in package.json or when creating a window programmatically) set:
{
"nodeIntegration": false,
"preload": "preload.js"
}
With nodeIntegration disabled, require and other Node globals are not available in the renderer. The only way to reach Node functionality is through window.myAPI (or whatever name you choose).
require, process, __dirname, global, or any direct Node module imports.fs.readFileSync, path.join).window as shown above.nodeIntegration:false and point preload to your script.const data = require('fs').readFileSync('config.json'); to const data = window.myAPI.readFile('config.json');.require or process. A ReferenceError indicates isolation is active.window.myAPI.readFile('config.json')) to confirm it works.To be certain that legacy flags are not re‑enabling Node in your build, check whether your packaging or launch command includes any of the following options: --nodejs, --allow-natives-syntax, or a custom nodeIntegration flag set to true.
NW.js does not provide a built‑in contextBridge like Electron. Isolation relies solely on disabling nodeIntegration and carefully controlling what the preload script exposes. If your project uses older NW.js releases or custom build flags, the behavior may differ; manual verification is recommended.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 16 Apr 2025, 08:30 UTC
NW.js 0.54+ turns off nodeIntegration by default, but it does not automatically enable contextIsolation. If a legacy flag like --disable-context-isolation is present, the renderer can still see process and require.
Set contextIsolation:true in the window options and confirm that the renderer console reports process and require as undefined:
{ "nodeIntegration": false, "contextIsolation": true, "preload": "preload.js" }
Open DevTools in the renderer and run console.log(typeof process, typeof require); the output should be undefined undefined.
nodeIntegration off.preload.js that requires the needed Node modules.contextBridge.exposeInMainWorld.