Electron Source Code Protection

Electron Source Code Protection

Here is a quick example to show how to enable byte code protection in your Electron project.

Environment

techversion
electron30.0.6
webpack5.91.0
@herberttn/bytenode-webpack-plugin2.3.1
nodejsv20.14.0

Steps

Webpack Configuration

Reference

 1// import plugin
 2const { BytenodeWebpackPlugin } = require('@herberttn/bytenode-webpack-plugin');
 3// enable only in production
 4const isEnvProduction = process.env.NODE_ENV === 'production';
 5...
 6plugins: [
 7  isEnvProduction && new BytenodeWebpackPlugin({ compileForElectron: true }),
 8],
 9...
10// main,preload,renderer entry. I used webpack-merge, if you didn't, ignore it.
11// main
12const mainConfig = merge(commonConfig, {
13  // entry: './src/main/main.ts',
14  entry: {
15    main: './src/main/main.ts',
16  },
17  target: 'electron-main',
18  output: {
19    filename: '[name].js',
20    devtoolModuleFilenameTemplate: '[absolute-resource-path]',
21  },
22  ...
23})
24// preload
25const preloadConfig = merge(commonConfig, {
26    // entry: './src/preload/preload.ts',
27    entry: {
28      preload: './src/preload/preload.ts',
29    },
30    target: 'electron-preload',
31    output: {
32      filename: '[name].js',
33      devtoolModuleFilenameTemplate: '[absolute-resource-path]',
34    },
35});
36// renderer
37const rendererConfig = merge(commonConfig, {
38  entry: {
39    renderer: './src/renderer/renderer.tsx',
40  },
41  target: 'electron-renderer',
42  output: { devtoolModuleFilenameTemplate: '[absolute-resource-path]' },
43  plugins: [
44    new HtmlWebpackPlugin({
45      template: path.resolve(__dirname, './public/index.html'),
46    }),
47  ],
48});

Electron entry main.ts/main.js configuration

 1  mainWindow = new BrowserWindow({
 2  ...
 3  webPreferences: {
 4    nodeIntegration: true, // set to true to enable jsc support
 5    contextIsolation: false, // set to false to enable jsc support, but it will disable contextBridge, I will show you how to fix it
 6    preload: path.join(__dirname, './preload.js'), // bytenode compiled js file, used to load preload.compiled.jsc
 7    webSecurity: false,
 8    sandbox: false,
 9  },
10});

I know the above configurations don’t meet Electron’s default security requirements, but if they are enabled, basically nothing can be done.

If you wish to use bytecode, you must configure it as above.

preload contextBridge fix

contextIsolation: false causes contextBridge to be unusable, another one of Electron’s interesting decisions.

contextBridge is unavailable, then you can’t be using ipc, but you don’t need it now.

At this point, you can call node’s api directly in the renderer process, such as fs.

However, I still suggest you to pass it through preload.

 1- import { contextBridge } from 'electron';
 2- import ipcAPI from '_preload/ipc-api';
 3import loadAddon from "_preload/load_node_addon";
 4import jb from "_preload/nodejieba";
 5
 6- contextBridge.exposeInMainWorld('ipcAPI', ipcAPI);
 7- contextBridge.exposeInMainWorld('myplugin', loadAddon('myplugin'));
 8- contextBridge.exposeInMainWorld('jb', jb);
 9
10+ window.ipcAPI = ipcAPI;
11+ window.myplugin = loadAddon('myplugin');
12+ window.jb = jb;

Done

With the above three configurations in webpack.js, main.js, and preload.js, you should be able to use bytecode in your project.

Bytecode is still not the final solution because it is easily decompiled and at this stage it just adds some cost to the beginner cracker.

Some people have used the solution of writing native plugins in rust and pairing it with bytecode for obfuscation,

which you can refer to if you have a strong need to keep your code secret. You need to make a trade-off between maintainability and security.