Skip to content

Bad Practice 101

Tldr

It is sometimes helpful to know what not to do. Just so we're clear: don't do this! ;)

This is only to show how easy it is for any JavaScript code running in the renderer process to behave like a full-blown Node.js program including access to Node.js require and its native APIs.

We have also disabled context isolation meaning that any script (including third-party script) can also manipulate the page and its data (e.g. cookies).

If your app gets compromised (e.g. XSS, rogue third-party dependency, etc.) the potential for damage is huge.

Main Process

We have enabled the node integration and disabled context isolation:

main.js
const {app, BrowserWindow} = require('electron');

app.whenReady().then(async () => {
  const bwin = new BrowserWindow({
    width: 300,
    height: 300,
    webPreferences: {
      nodeIntegration: true,  // <- Don't do this!
      contextIsolation: false // <- Don't do this!
    }
  });
  await bwin.loadFile('renderer.html');
  bwin.show();
});

Renderer Page

We import a script into the page. Third-party scripts will run with the exact same privileges.

renderer.html
<html>
  <head>
    <style>
      body {background-color:black;color:limegreen}
    </style>
  </head>
  <body>
    <pre></pre>
    <script src="./renderer.js"></script>
  </body>
</html>

Renderer Script

Because node integration is enabled this script has access to the Node.js APIs. We can see that this script is attempting to read a file that was meant to remain secret.

Also since the context isolation is disabled this script is sharing the same context execution as the renderer page so can directly modify the DOM and/or read cookies.

renderer.js
const fs = require('fs');
const path = require('path');
const secret = fs.readFileSync(path.join(__dirname, 'secret.txt'));
document.querySelector('pre').innerHTML = secret;

Screenshot