Here is a quick example to show how to enable byte code protection in your Electron project.
Environment
| tech | version |
|---|---|
| electron | 30.0.6 |
| webpack | 5.91.0 |
| @herberttn/bytenode-webpack-plugin | 2.3.1 |
| nodejs | v20.14.0 |
Steps
Webpack Configuration
// import plugin
const { BytenodeWebpackPlugin } = require('@herberttn/bytenode-webpack-plugin');
// enable only in production
const isEnvProduction = process.env.NODE_ENV === 'production';
...
plugins: [
isEnvProduction && new BytenodeWebpackPlugin({ compileForElectron: true }),
],
...
// main,preload,renderer entry. I used webpack-merge, if you didn't, ignore it.
// main
const mainConfig = merge(commonConfig, {
// entry: './src/main/main.ts',
entry: {
main: './src/main/main.ts',
},
target: 'electron-main',
output: {
filename: '[name].js',
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
},
...
})
// preload
const preloadConfig = merge(commonConfig, {
// entry: './src/preload/preload.ts',
entry: {
preload: './src/preload/preload.ts',
},
target: 'electron-preload',
output: {
filename: '[name].js',
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
},
});
// renderer
const rendererConfig = merge(commonConfig, {
entry: {
renderer: './src/renderer/renderer.tsx',
},
target: 'electron-renderer',
output: { devtoolModuleFilenameTemplate: '[absolute-resource-path]' },
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
}),
],
});
Electron entry main.ts/main.js configuration
mainWindow = new BrowserWindow({
...
webPreferences: {
nodeIntegration: true, // set to true to enable jsc support
contextIsolation: false, // set to false to enable jsc support, but it will disable contextBridge, I will show you how to fix it
preload: path.join(__dirname, './preload.js'), // bytenode compiled js file, used to load preload.compiled.jsc
webSecurity: false,
sandbox: false,
},
});
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.
- import { contextBridge } from 'electron';
- import ipcAPI from '_preload/ipc-api';
import loadAddon from "_preload/load_node_addon";
import jb from "_preload/nodejieba";
- contextBridge.exposeInMainWorld('ipcAPI', ipcAPI);
- contextBridge.exposeInMainWorld('myplugin', loadAddon('myplugin'));
- contextBridge.exposeInMainWorld('jb', jb);
+ window.ipcAPI = ipcAPI;
+ window.myplugin = loadAddon('myplugin');
+ 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.