Back to Blog
Research11 min read

Electron Source Code Protection is Broken. Here's What Attackers Actually See.

JSC Decompiler Team

Security Research

The electron-vite documentation tells developers that compiling to V8 bytecode means “the source code is unrecoverable.” That claim is wrong.

We tested every protection layer that Electron developers rely on to hide their source code. Plaintext ASAR, bundling, obfuscation, V8 bytecode compilation, integrity checks, native addons. Six layers total. Most of them fold under basic tooling. What follows is the layer-by-layer results: what an attacker actually sees at each level, and how long it takes to get through.

Layer 1: No protection. Plaintext JavaScript in ASAR.

Electron packages application files into an ASAR archive. ASAR is not encrypted. It is not compressed. It is a flat concatenated archive, similar to tar. One command extracts everything:

npx @electron/asar extract app.asar ./extracted

# That's it. Full source code.
ls ./extracted/
# index.js  main.js  preload.js  renderer/  node_modules/  package.json

Most Electron apps ship this way by default. VS Code does. Atom did. If the developer did not explicitly add a protection step to their build pipeline, every .js file is sitting in the archive in plaintext, with comments, variable names, and formatting intact.

Time to extract: under 30 seconds. Tools required: one npm command. There is no analysis step. You just read the files.

For a deeper walkthrough of ASAR extraction across macOS, Windows, and Linux, see our guide to decompiling Electron apps.

Layer 2: Bundling and minification

Many Electron apps run their code through webpack, esbuild, or Vite before packaging. The output is a single bundled file with shortened variable names and stripped whitespace. It looks like this:

"use strict";var e=require("electron"),t=require("path"),
n=require("fs");const o=e.app.getPath("userData"),
r=t.join(o,"config.json");function i(){try{return
JSON.parse(n.readFileSync(r,"utf8"))}catch(e){return{
theme:"dark",autoUpdate:!0,telemetry:!1}}}
e.ipcMain.handle("get-config",()=>i());

Variable names are gone. The module structure is flattened into one file. But the logic is right there. You can see the app reads a config file from userData, has default settings for theme, auto-update, and telemetry, and exposes a get-config IPC handler. A quick pass through a JavaScript beautifier makes it fully readable.

Some developers accidentally ship sourcemaps alongside their bundles. If you find a .js.map file in the ASAR, you can reconstruct the original source tree with the original filenames and formatting. That is not an attack. That is the developer handing you the source on a plate.

Minification is a build optimization. It was never designed as a security measure, and it does not function as one.

Layer 3: JavaScript obfuscation

Obfuscation tools like javascript-obfuscator and Jscrambler go further than minification. They rename variables to hex strings, flatten control flow into switch-case state machines, encode string literals into arrays resolved at runtime, and inject dead code to confuse analysis.

The output is valid JavaScript that is genuinely hard to read:

var _0x4a2b=["getPath",
"userData","join"];
const _0x1f3c=require("electron");
var _0x2d4e=_0x1f3c["app"][_0x4a2b[0]](_0x4a2b[1]);

Harder to reverse than minified code. But it is still JavaScript, and the counter-tooling exists. Tools like webcrack automatically detect common obfuscation patterns and undo them. String arrays get decoded, control flow gets unflattened, hex-encoded identifiers get resolved. A skilled analyst recovers the logic in hours.

Obfuscation is the first layer that provides real friction. It will stop a casual user from reading your code. It will not stop someone who does this for a living.

Layer 4: V8 bytecode compilation

This is the layer most Electron developers believe is secure. Tools like bytenode and electron-vite's built-in bytecode plugin compile JavaScript source into V8 bytecode and ship .jsc files instead of .js files inside the ASAR archive.

The claim: electron-vite's documentation on source code protection states that with V8 bytecode compilation, “the source code is unrecoverable.” Bytenode's README makes a similar suggestion, positioning bytecode compilation as a way to “protect” your source code.

Here is what that protection actually looks like to someone with a decompiler.

Original source (before compilation):

const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const Store = require("electron-store");

const store = new Store({ encryptionKey: "x9f2k" });

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      contextIsolation: true,
    },
  });

  win.loadFile("index.html");
}

ipcMain.handle("get-license", () => {
  return store.get("licenseKey", null);
});

ipcMain.handle("activate", async (event, key) => {
  const res = await fetch("https://api.example.com/v1/activate", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ key, machine: require("os").hostname() }),
  });
  const data = await res.json();
  if (data.valid) store.set("licenseKey", key);
  return data;
});

app.whenReady().then(createWindow);

Decompiled output (from JSC Decompiler):

// Decompiled from: main.jsc
// Detected: V8 12.4 (Electron 33)

const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const Store = require("electron-store");

const store = new Store({ encryptionKey: "x9f2k" });

function createWindow() {
  const r0 = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      contextIsolation: true,
    },
  });

  r0.loadFile("index.html");
}

ipcMain.handle("get-license", () => {
  return store.get("licenseKey", null);
});

ipcMain.handle("activate", async (r0, r1) => {
  const r2 = await fetch("https://api.example.com/v1/activate", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ key: r1, machine: require("os").hostname() }),
  });
  const r3 = await r2.json();
  if (r3.valid) store.set("licenseKey", r1);
  return r3;
});

app.whenReady().then(createWindow);

The API endpoint, the encryption key, the activation logic, the license validation flow. All visible. Local variable names are replaced with register references (r0, r1, r2), and comments are gone. Everything else survives: string literals, function names, control flow, require() calls, class structure, numeric constants.

V8 bytecode is not encryption. There is no key. There is no cipher. It is a documented intermediate representation. The opcode set is defined in bytecodes.h in the V8 source tree, and the serialization format, while it changes between V8 versions, follows a consistent structure. A purpose-built decompiler walks the instruction stream and reconstructs JavaScript the same way Ghidra reconstructs C from machine code.

JSC Decompiler handles bytecode from Electron 17 through Electron 38. It reads the V8 version from the file header, selects the correct decompilation pipeline, and produces readable JavaScript output. The process takes seconds. For technical details on how V8 bytecode decompilation works under the hood, see our bytenode decompiler guide.

The electron-vite claim is not a small inaccuracy. It is the opposite of what is true. V8 bytecode does not make source code unrecoverable. It makes source code harder to read without the right tool. The right tool exists.

Layer 5: ASAR integrity and code signing

Electron supports an asar.integrity field in package.json that validates the ASAR contents at load time. If someone modifies the archive, the hash check fails and the app refuses to start. On top of that, macOS codesign and Windows Authenticode verify that the application bundle itself has not been tampered with.

These mechanisms protect against modification. They do not protect against reading. An attacker can copy the ASAR out of the application bundle, extract its contents, and decompile the bytecode without triggering any integrity check. The checks only fire when the application loads.

Even the integrity story has holes. In September 2025, Trail of Bits disclosed CVE-2025-55305: a bypass of Electron's code integrity fuses through V8 heap snapshot tampering. By modifying v8_context_snapshot.bin, researchers were able to inject persistent backdoors into Signal, 1Password, and Slack that survived code signing verification. The integrity fuses that were supposed to prevent this did not validate heap snapshots.

ASAR integrity and code signing are useful defenses against supply chain tampering. They are not reverse engineering protections.

Layer 6: Native addons

Native addons are compiled C or C++ code loaded through N-API. They ship as .node files (shared libraries) inside the Electron application. Reversing them requires Ghidra, IDA Pro, or Binary Ninja. With stripped symbols and compiler optimizations, reconstructing the original logic is a real reverse engineering problem. It takes days or weeks, not minutes or hours.

This is the only layer where “unrecoverable” starts to be an honest description. Not absolute, but the bar is genuinely high. The problem is that almost no Electron app puts its business logic in native addons. They are used for performance-sensitive operations, hardware integration, and native OS APIs. The application logic, the part you probably want to protect, is still JavaScript.

What actually protects your source code

If you need to protect sensitive logic in an Electron application, here is what actually works.

The simplest answer: do not ship the code. Move license validation, pricing logic, and proprietary algorithms to a backend API. The client calls the endpoint. The implementation never leaves your infrastructure. This is what most SaaS companies do, and it eliminates the reverse engineering problem entirely.

If you must ship compute-intensive or sensitive logic client-side, compile it from Rust or C++ to WebAssembly. WASM binaries are harder to decompile than V8 bytecode because they use a lower-level instruction set without the rich metadata that V8 carries. Not bulletproof, but a real step up from anything JavaScript-based.

For everything else, stack what you have. Obfuscate your JavaScript first, then compile to bytecode, then enable ASAR integrity. No single layer survives on its own, but an attacker who decompiles the bytecode still has to deal with obfuscated output. That combination buys you more time than any layer alone.

And pair all of it with clear licensing terms. Technical measures buy time. Legal measures define consequences. In most jurisdictions, reverse engineering for security research is protected. Reverse engineering to copy and redistribute commercial software is not.

The bottom line

Of six protection layers commonly used in Electron apps, only native addons put up a real fight. Plaintext ASAR is instant. Minification is minutes. Obfuscation is hours. V8 bytecode, the layer that electron-vite calls “unrecoverable,” falls to a purpose-built decompiler. ASAR integrity stops tampering but not reading.

If you ship an Electron app, make security decisions based on what is actually true about these layers, not what a build tool's documentation claims. And if you need to look inside a compiled Electron app, JSC Decompiler handles V8 bytecode from Electron 17 through Electron 38. For a comparison of available decompilation tools, see our tool comparison.

Try JSC Decompiler Free

Upload a .jsc file and get readable JavaScript back in seconds. No signup required for the free tier.