Electron-Based App Security Testing Fundamentals Part 1 — Introduction to Electron Framework

A Brief Overview of Electron Framework and Building a Simple Application

YoKo Kho
HakTrak Cybersecurity Squad
14 min readMar 17, 2024

--

بسم الله الرحمن الرحيم

This marks the start of a series of articles exploring the fundamentals of security testing for Electron-based applications. We aim to cover various aspects, from understanding the essentials of the Electron framework to discussing case studies and insights gathered from a variety of references and real-world scenarios.

Through these articles, our goal is to offer insights and practical guidance for conducting security testing using common methods for Electron applications.

In this part, we will try to explain the overview of electron, how electron works, and simple steps needed to create a simple electron-based application.

I. Introduction

The evolution of cross-platform desktop applications development has brought forth a series of unique challenges, primarily associated with ensuring consistency in functionality and user experience across various operating systems such as Windows, macOS, and Linux-based systems. From a technical standpoint, achieving this consistency is a very important aspect for maintaining smooth and satisfying application performance for users, which of course will require developers to possess expertise in multiple technologies and maintain separate codebases for each platform.

As time progresses, several frameworks have emerged to aid developers in overcoming existing challenges. One notable solution is the Electron Framework, which provides a flexible platform for simplifying cross-platform development processes and addressing the inherent complexities of diverse operating environments.

In recent years, the Electron Framework has experienced a notable increase in popularity, evidenced by its adoption in various well-know applications like Notion, Microsoft Teams, Slack, and other. This widespread usage indirectly highlights Electron’s e effectiveness in streamlining cross-platform development workflows and addressing the challenges posed by varied operating environments.

Figure 1 Showcase of Electron-Based Application — electronjs.org/apps

1.1. What is Electron

So, what is Electron? In simple terms, Electron is an open-source framework designed for building desktop applications. It allows developers to utilize common web technologies like JavaScript, HTML, and CSS — to create desktop apps without needing to learn new programming languages. This means that developers who are already comfortable with web development can seamlessly transition into building desktop applications using Electron.

1.2. How it Works?

Basically, the flow in an Electron application starts with the main process, which is initiated by the Node.js runtime and typically starts based on configurations set within the package.json file. This process is responsible for the backend operations of the application, such as creating windows and managing system-level interactions.

Figure 2 General Flow of Electron Application Framework

Subsequently, the main process creates renderer processes, each of which will run an instance of Chromium. These processes are responsible for the frontend, rendering web content like HTML, CSS, and JavaScript to display the app’s user interface.

To facilitate the communication between the main and renderer processes, Inter-Process Communication (IPC) is utilized. At this stage, renderer processes can request the main process to perform actions that require native OS integrations, a function they are restricted from doing directly for security reasons.

Note: For a comprehensive guide to understanding and exploring the Electron framework, we can visit: https://www.electronjs.org/docs.

And now, after we’ve seen a general view of the Electron framework, let’s proceed to the section where we build our first simple hello-world application.

II. Let’s Build Our First Simple “Hello-World” Application

As we all know, to conduct security testing optimally, it is essential for us to understand the fundamentals of the object to be tested. Therefore, in this section, we will attempt to create a simple hello-world application using Electron (which displays a simple text with heading level 1 size and shows a simple alert).

2.1. Preparing Our Environment

2.1.1. Installing Node.js

As a first step, we need to install Node.js in the environment we will be using. Why is that? Simply put, the Electron Framework requires Node.js, which is a JavaScript runtime environment that allows us to run JavaScript on the server side. With Node.js, we can write JavaScript code that runs on the server side, not just within the browser environment. This allows us to create server-side applications that can perform various tasks such as managing files, accessing databases, and handling HTTP requests.

In the context of Electron, Node.js is used to run the main application process. This allows us to access operating system functionality, empowering developers to create advanced desktop applications using web technologies.

Figure 3 Node.js Official Portal

So to download Node.js, we can visit the official website at https://nodejs.org. From there, we can download the installer suitable for our operating system. Once the installer is downloaded, we can follow the provided instructions to complete the Node.js installation on our computer.

To ensure that Node.js has been installed, we can try executing the node command through the terminal / command prompt:

Figure 4 Node.js has been Installed

2.1.2. Setting Up Project Directory

Like creating any application in general, at this second step, we need to create a new directory specifically for our Electron project.

Figure 5 Create a Project Directory

To do this, we simply need to create a directory with the desired name for our project (for example: my-electron-app), and then navigate into that directory.

2.1.3. Initializing Project

After setting up our project directory, the next step is to initialize the project with a package.json file. This file basically serves as a manifest for our project, containing important metadata and configurations. Practically, we can generate this file by running the command npm init -y in our terminal or command prompt within the project directory.

Upon executing this command, the package.json file will be automatically created with default values for most parameters as follows:

{
"name": "my-electron-app",
"version": "1.0.0",
"description": "",
"main": "app/main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

Here are the general descriptions of each parameter displayed:

  • name: This parameter represents the name of our project or package. It uniquely identifies our project in the npm registry. In this example, the project name is “my-electron-app”.
  • version: The version of our project or package. It helps to track changes in the project and ensure different versions can be managed and downloaded.
  • description: A brief description of the project or package. It could help others understand the purpose and functionality of the project.
  • main: The main file or entry point of the project. It’s basically the JavaScript file that gets executed first when the project starts. In this example, we have set up the main file ia “main.js”, and located in app directory. And as a note, the “app” directory is not mandatory, it’s just a recommendation to organize our project structure for better clarity. In practice, we can use different directory names that suit with our preferences.
  • scripts: A list of scripts that can be executed using npm. It’s typically used to run tasks like testing, building, or running the application.

However, for Electron applications where the main entry point is often a file named “main.js,” we need to adjust this value to match the structure of our Electron project. So, in this situation, the value of scripts needs to be changed to:

"scripts": {
"start": "electron ."
},

With this adjustment, we can later run our application using the npm start command, and it will start the Electron application with the main.js file as its main entry point.

  • keywords: Keywords are basically used to categorize the project or package that could help others to find the project easily.
  • author: The name or identity of the project author. It could be an individual’s name or an organization responsible for the project.
  • license: The license used for the project. It specifies how others can use, modify, and distribute the project.

As a summary, the values of these parameters are not static but can be adjusted according to our development needs.

And here is an example of the modified package.json with the adjusted scripts section:

{
"name": "my-electron-app",
"version": "1.0.0",
"description": "",
"main": "app/main.js",
"scripts": {
"start": "electron ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^29.1.2"
}
}
Figure 6 Custom package.json File

2.1.4. Installing Electron

Now that we have Node.js installed and created the project directory, the next step is to install Electron. As mentioned earlier, Electron is a framework that allows us to build cross-platform desktop applications using web technologies such as HTML, CSS, and JavaScript.

So to install Electron, we can use npm (Node Package Manager) via terminal, which comes bundled with Node.js. From there, we have a few options for installation — which we will focus only two common options, namely:

A. Global installation: If we want Electron to be available system-wide and accessible from any directory, we can execute the following command:

npm install electron -g

This command basically will install Electron globally on our operating system, allowing us to access the Electron CLI (Command Line Interface) from anywhere. The global installation ensures that we can create new Electron projects and run other Electron-related commands without being restricted to a specific project directory.

B. Local installation: however, if we want to ensure that Electron is only available within a specific project directory and is treated as a development dependency, we can use the following installation command:

npm install electron - save-dev

This command installs Electron as a development dependency within our own project. This means Electron will be included in the project’s package.json file under the “devDependencies” section. This approach allows Electron to be excluded automatically when the application is distributed, ensuring that it is only installed when needed for development purposes.

In short, by managing Electron as a development dependency, we can streamline the deployment process and ensure that our application can be easily distributed and run on other machines without the need for separate Electron installations.

In summary, here are the differences among the two installation models:

Table 1 Differences between Global and Local Installation

Regardless of whichever option is chosen based on existing considerations, the important thing in this step is to ensure that Electron has been installed within it.

Note: In this flow, we will be using the — save-dev flag when installing Electron. And if we look closely, installing Electron in this way will add the devDependencies parameter to the package.json file.

Figure 7 Installing Electron with — save-dev Flag

2.2. Creating the Application

2.2.1. Creating Main Application

Once we’ve configured the necessary settings, the next step is to create the main application file for our Electron project. This file acts as the entry point for our Electron application, where we define the behavior and functionality of our application.

So, how do we go about it? Firstly, we need to create a file named “main.js”, as referenced in subsection 2.1.3, and place it in the ‘app’ directory.

Figure 8 Creating an “app” Directory and main.js File

After that, we can open the main.js file with our favorite text editor and start writing simple code to create the application window that will load our index.html file.

Note: this main.js code will typically include the initialization of Electron, creating windows, handling events, and other application logic.

const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});

win.loadFile(path.join(__dirname, '../index.html'));
}

app.whenReady().then(createWindow);
Figure 9 Creating Application Window

Then, what does this code mean?

  • First, we import the necessary modules from Electron, including app and BrowserWindow.
  • Next, we try to define a function createWindow() to create a new browser window for our Electron application.
  • Within the createWindow() function, we create a new instance of BrowserWindow with specified width, height, and web preferences.
  • And finally, we will try to load an HTML file (for example: index.html) into the newly created window using the loadFile() method.

Make sure we have also created the index.html file in our project directory, as it’s referenced in the code above.

So, with the main.js file created and the initial application logic defined (e.g: browser window and loadfile), we have built the foundation for our Electron application. We can now proceed to write additional code to build out the functionality of our application as needed.

2.2.2. Creating and Writing index.html Content

And finally, we arrive at the last step of building this simple hello world project, namely by adding the content in the index.html file. This HTML file will serve as the user interface for our Electron application, defining what the user sees and interacts with.

Note: we will also see if this step highlights how Electron leverages common web technologies like JavaScript and HTML to create desktop apps without requiring developers to learn new programming languages.

So to accomplish this, simply create a new file named index.html and place it in the root directory of our project (not in the app directory). In our case, the directory would be “my-electron-app”.

<!DOCTYPE html>
<html>
<head>
<title>Example of Electron App</title>
<script>
window.onload = function() {
alert("Hello World");
};
</script>
</head>
<body>
<h1>Welcome in our first Electron App!</h1>
<p>This is just a sample paragraph.</p>
</body>
</html>
Figure 10 Sample Content of index.html File
Figure 11 Location of the index.html

In this example, we’ve provided a basic HTML structure with a heading and a paragraph. Additionally, we’ve included a JavaScript function that triggers an alert message when the document is loaded, providing a simple demonstration of how JavaScript can be used to interact with the Electron environment. And yes, we can modify the content of the HTML file to suit with our application’s specific requirements.

Once we have created the content in the “index.html” file, we have completed the setup for our simple Electron hello world application. With Node.js installed, the project directory configured, package.json initialized, Electron installed, and the main application file created, we are now prepared to execute our Electron application and view the hello world message displayed in the Electron window.

2.3. Running and Building the Application

2.3.1. Running the Electron Application

To run our Electron application, we need to ensure that we are still within our project directory (in this case, my-electron-app). After that, we execute the following command:

npm start

This command will launch the Electron application based on the configuration specified in the package.json file. By default, the npm start command will search for a script named “start” in the “scripts” section of the package.json file (reflect on the parameters we adjusted in subsection 2.1.3.) and execute it.

If all preceding steps have been executed correctly, we should now be able to see the Electron application running, with the application window displaying the content specified in the index.html file, as shown in the following images:

Figure 12 Result of Our Electron App
Figure 13 Result of Our Electron App II

2.3.2. Building and Distributing the Application

After having gone through various steps, some of us might be wondering about how this application is distributed and how it reaches the end-users.

Well, when it comes to distributing Electron applications, it’s important for us to ensure that the application is packaged correctly for different operating systems and architectures. This of course to ensure that users can easily install and run the application on their devices, regardless of their platform.

For example, in Windows, Electron applications are commonly packaged as executable installer files (e.g., .exe or .msi). For Linux distributions, Electron applications may be packaged in various formats depending on the package manager used (such as .deb or .rpm packages). Meanwhile, on macOS, they are typically bundled as macOS application bundles — usually in the form of a .dmg file.

Please note that in this paper, we will focus on building applications for macOS. However, the output generated from this build process will not differ significantly when built on Windows or Linux.

So, how to do it? To create this macOS (or any other OS) application bundle, we need to utilize Electron Builder, a tool for packaging and distributing Electron applications. With Electron Builder, we can configure various aspects of the application packaging process, such as the application name, version, icon, and distribution format.

To begin, we first need to install Electron Builder as a development dependency in our project. We can do this by running the following command in the terminal or command prompt:

npm install electron-builder --save-dev

Note: After running the command, the electron-builder parameter will automatically appear in the package.json file, under the “devDependencies” section.

Figure 14 Installing Electron Builder

Once electron-builder is installed, we need to reconfigure the package.json by adding a “package-mac” under the “scripts” section and adding a “build” script. Basically, this script defines the build configuration options for our Electron application. Here is an example of our final package.json file:

{
"name": "my-electron-app",
"version": "1.0.0",
"description": "A simple Hello-World application",
"main": "app/main.js",
"scripts": {
"start": "electron .",
"package-mac": "electron-builder --mac"
},
"keywords": [],
"author": "YoKo Kho from HakTrak Cybersecurity Squad",
"license": "ISC",
"devDependencies": {
"electron": "^29.1.4",
"electron-builder": "^24.13.3"
},
"build": {
"appId": "com.yoko.my-electron-app",
"productName": "My Electron App",
"directories": {
"output": "dist"
}
}
}
Figure 15 Modified package.json

Additional notes regarding the existing parameters are as follows:

  • The “appId” specifies a unique identifier for the application.
  • The “productName” defines the display name of the application.
  • And the “directories” section specifies the output directory where the packaged application will be placed.

After configuring electron-builder, we can initiate the packaging process by running the following command:

npm run package-mac

This command will start the packaging process specifically for macOS based on the configuration specified in the package.json file. After the packaging process completes, we will find the packaged application files in the specified output directory (“dist” in this example).

Figure 16 Build the Application

With the Electron application successfully packaged, we now have an executable file that we can distribute to users. They can install and run the application on their devices, allowing them to enjoy the application.

Well, here we are, almost at the end of this part. We hope that by now, readers have gained a general understanding of Electron-based applications. In the next part, we will start to explore into technical discussions on how to detect Electron applications, followed by an overview of common testing method performed on these Electron applications.

REFERENCES

--

--