Source Code Protection
Your main process code is not shipped as readable JavaScript, and the packaged app refuses to run anything other than your signed code. This is on by default, set up in the template: there is nothing to configure and nothing to run.
What is protected
Protection is about raising the cost of tampering and casual reverse engineering. A determined attacker with the app on their machine can still dump memory or inspect a running process: no client-side app can prevent that. Keep real secrets (API keys, private endpoints) on a server, never in the app.
- The main process ships as V8 bytecode. On a production build, the whole
main process bundle is compiled to V8 bytecode (
out/main/index.jsc); theindex.jsleft beside it is a tiny loader. Your business logic, database queries and tRPC procedures are no longer plain text in theapp.asararchive. - The app only runs its own code. Electron
fuses are flipped in
the Electron binary at packaging time to lock the app down:
onlyLoadAppFromAsar+enableEmbeddedAsarIntegrityValidation— the app loads only from theapp.asararchive, whose hash is checked against the one recorded at build time. A modified or swapped archive is rejected.runAsNode,enableNodeOptionsEnvironmentVariable,enableNodeCliInspectArguments— turned off, so the packaged binary cannot be relaunched as a plain Node.js runtime, fed extraNODE_OPTIONS, or attached to with the Node inspector to read your code or data.enableCookieEncryption— the on-disk cookie store is encrypted with OS-level keys.
The renderer (your React app) is bundled and minified like any web app, but it is not bytecode: browser engines run JavaScript, not V8 bytecode. Keep sensitive logic in the main process, which is where tRPC procedures already live.
How it is wired
You do not need to touch any of this; it is here so you know where it lives.
- Bytecode —
build.bytecodeinelectron.vite.config.ts, on themainconfig. It only runs in production builds (npm run build,npm run dist), never in development, so hot reload and debugging are unaffected. - Fuses and ASAR integrity —
electronFusesinelectron-builder.ts. electron-builder flips the fuses and records theapp.asarhash while packaging (npm run dist).
export default defineConfig({
main: {
build: {
// Compiles the main process to V8 bytecode in production builds
bytecode: { transformArrowFunctions: false }
}
}
// ...
})transformArrowFunctions is disabled because the extra Babel pass it runs
cannot handle a class-field arrow function in one of the bundled dependencies.
It only affects that pre-processing step; the bytecode compilation itself is
unchanged.
macOS signing and local builds
Flipping fuses invalidates the macOS code signature, so the app is signed again afterwards:
- In CI, the release workflow signs and notarizes with your Developer ID certificate, which is what end users need.
- For a local unsigned build (
npm run dist:dir), there is no certificate, so the template setsresetAdHocDarwinSignature: true: electron-builder re-signs the app ad-hoc after flipping the fuses. Without it, an Apple Silicon build would be killed by the OS on launch (Killed: 9). A real signature simply replaces the ad-hoc one in CI.
Testing it
# Build and package locally (unsigned), then open dist/
npm run dist:dirThe app in dist/ runs the bytecode main process with the fuses applied. To
confirm the main process is not plain JavaScript, look at out/main: the logic
is in index.jsc, not index.js.