DEV Community

asaf g
asaf g

Posted on • Originally published at turtle-techies.com on

Securing Your Electron App

Written by Andres Acevedo

In our previous article, we explored how to create a useful Electron app focusing on functionality, but leaving aside some aspects like security and platform specific features.

In this article, we will continue from where we left, to progress in the path from a Prototype to a real world Application!

Iā€™d like to begin with an analogy: in healthcare studies, there's an important Latin maxim: Primum non nocere, it means:

"first, do no harm". Another way to state it is that, "given an existing problem, it may be better not to do something, or even to do nothing, than to risk causing more harm than good."

If we are introducing security problems to our clients, it could be best to go back a bit (pun intended) and solve these vulnerabilities before adding new features to our pretty piece of software.

Working according to the warnings

So, is our app secure? If we run it with npx electron . and open the Developer Tools (View>Toggle Developer Tools), we can see that the console is giving us several security warnings:

electron showing security warnings

One easy way to improve our app's security is to add a special meta header to our index.html file:


<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

Enter fullscreen mode Exit fullscreen mode

This instructs the Chromium rendering engine to only run local scripts.

You can also add other allowed domains in the following way:


Content-Security-Policy: script-src 'self' https://apis.example.com

Enter fullscreen mode Exit fullscreen mode

If you want to learn more about CSP, check:

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

Once we add that line to the header section of our website and refresh our app (View > Reload), we are left with just one security warning:

electron showing only one security warning

Getting rid of this security warning requires that we modify our main.js file and add the following webPreference to our BrowserWindow call:


webPreferences: {
            worldSafeExecuteJavaScript: true,
            contextIsolation: true
}

Enter fullscreen mode Exit fullscreen mode

What this does is to enforce isolation between the Renderer World (Chromium renderer) and Node World. This sandboxes any malicious code run from the renderer, reducing the possible damage of an exploited vulnerability.

Once we add the code, reloading the view is not enough - as we did changes in our main.js file, so we have to terminate our Electron App and relaunch it with: npx electron .

electron showing console errors

Good news: we got rid of the warning.

Bad news: now we have an actual error and our app does not work anymore

Fixing the code

Require is a node function, and because we configured electron to separate renderer and node worlds, we can not use require() on our renderer.js file.
Even if we have the nodeIntegration: true setting.

In fact, let's remove that setting, so our BrowserWindow call in main.js now looks like this:


const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      worldSafeExecuteJavaScript: true,
      contextIsolation: true,
    }
  })

Enter fullscreen mode Exit fullscreen mode

Electron provides us with a way to use node functions in the renderer world: Preload scripts.

Add the following line to webPreferences in our BrowserWindow call on main.js.

preload: path.join(app.getAppPath(), 'preload.js')
Enter fullscreen mode Exit fullscreen mode

It should look like this:


const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      worldSafeExecuteJavaScript: true,
      contextIsolation: true,
      preload: path.join(app.getAppPath(), 'preload.js')
    }
  })

Enter fullscreen mode Exit fullscreen mode

Don't forget to include the path module at the beginning of main.js:

const path = require('path');
Enter fullscreen mode Exit fullscreen mode

Now let's create the preload.js file and write the following on it:


const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld(
    "electron",
    {
        ipcRenderer: ipcRenderer
    }
);

Enter fullscreen mode Exit fullscreen mode

As you can see on the first line, we can use the require() function in preload.js without problems in order to import the ipcRenderer.

Now, we will remove the require line that is causing the error on the renderer.js file:


const { ipcRenderer } = require('electron');

Enter fullscreen mode Exit fullscreen mode

As we are not requiring ipcRenderer anymore, we can not continue using it in the same way as before.
But that's when the contextBridge.exposeInMainWorld function we added on preload.js comes in handy.
Our ipcRenderer can be accessed on the rendered.js file by using window.electron.ipcRenderer.

So it now should look like this:


async function fileSelected(e){    
    const loadedFilePath = e.target.files[0]?.path
    let data = await window.electron.ipcRenderer.invoke('read-file', loadedFilePath)
    document.getElementById("loadedText").value = data
}
document.getElementById("fileLoader").addEventListener("change", fileSelected);

Enter fullscreen mode Exit fullscreen mode

Result:

electron showing console

We've made it! Our app works again and without any security warnings!

You can find the code of our Secure Text Loader on the following repo:
https://github.com/mran3/Secure-Text-File-Loader/

Top comments (0)