How to Decompile Electron Apps: Extracting and Analyzing JSC Files
JSC Decompiler Team
Engineering
Electron apps bundle JavaScript into desktop applications. Some ship the source as plaintext. Others compile it to V8 bytecode before packaging — producing .jsc files that the V8 engine can run directly but humans cannot read. If you are auditing an Electron app, conducting a supply chain review, or verifying a production build, you need to get into those compiled modules.
This guide walks through the full process: locating the ASAR archive, extracting its contents, identifying compiled modules, determining the correct Electron and V8 version, and decompiling the bytecode back to readable JavaScript.
How Electron Packages JavaScript
Electron applications are built on Chromium and Node.js. The application logic is JavaScript (or TypeScript compiled to JavaScript), and it runs in two contexts: the main process (Node.js, full system access) and renderer processes (Chromium, web page rendering). Some apps also use preload scripts that bridge the two.
The ASAR Archive Format
Electron packages application files into an ASAR (Atom Shell Archive) file. This is a simple concatenated archive format — not compressed, not encrypted. It bundles all application resources into a single file that Electron reads at runtime. The ASAR file is the first thing you need to find and extract.
Plaintext vs. Compiled JavaScript
By default, Electron apps ship plaintext JavaScript. You can open the ASAR, read the .js files, and see the source directly. But many developers compile their JavaScript to V8 bytecode before packaging, using tools like bytenode or Electron's built-in V8 code cache APIs. The result: .jsc files that replace the original .js files in the archive.
Where .jsc Files Live
Inside the extracted ASAR, compiled modules can appear in several locations depending on the app's architecture:
- Main process: The entry point and core application logic. Usually in the root or a
src/directory. These files have full Node.js API access — file system, network, child processes, everything. - Renderer process: The UI layer. Often in
renderer/ordist/directories. May or may not have Node.js access depending on app configuration. - Preload scripts: Bridge scripts that run in the renderer context but with limited Node.js access. These are security-critical because they define the API surface between the privileged main process and the sandboxed renderer.
Why Developers Compile
Two reasons come up consistently. First, IP protection: preventing easy reading of proprietary application logic. Second, startup performance: V8 can load and run pre-compiled bytecode faster than parsing JavaScript from source. In practice, the IP protection motivation is more common. The startup time improvement is real but modest for most applications.
Important: Compiling to V8 bytecode is not encryption. The bytecode contains all the same logic, strings, and structure as the original source. It is a different representation, not a protected one. Decompilation recovers the original program structure.
Extracting .jsc Files from Electron Apps
The extraction process is the same across all three platforms. Find the ASAR, extract it, and identify the compiled modules.
Step 1: Locate the ASAR Archive
The ASAR file lives in a predictable location on each platform:
# macOS
/Applications/AppName.app/Contents/Resources/app.asar
# Windows
C:\Program Files\AppName\resources\app.asar
# or
C:\Users\<user>\AppData\Local\AppName\resources\app.asar
# Linux
/opt/AppName/resources/app.asar
# or
/usr/lib/AppName/resources/app.asarSome apps use app.asar.unpacked alongside the main ASAR for native modules that cannot be packed into the archive. Check both locations. A few apps name their archive differently (e.g., core.asar or electron.asar), but app.asar is the standard.
Step 2: Extract the Archive
Use the @electron/asar package to extract:
# Extract the entire archive
npx @electron/asar extract app.asar ./extracted
# List contents without extracting (useful for large archives)
npx @electron/asar list app.asar
# Extract on macOS — full path example
npx @electron/asar extract \
"/Applications/SomeApp.app/Contents/Resources/app.asar" \
./extractedThe older asar package (without the @electron/ scope) also works but is deprecated. Use @electron/asar for current Electron versions.
Step 3: Identify Compiled vs. Plaintext Modules
After extraction, scan the directory for .jsc files:
# Find all compiled bytecode files
find ./extracted -name "*.jsc" -type f
# Count compiled vs plaintext
echo "Compiled (.jsc): $(find ./extracted -name '*.jsc' | wc -l)"
echo "Plaintext (.js): $(find ./extracted -name '*.js' | wc -l)"
# Check if main entry point is compiled
cat ./extracted/package.json | grep '"main"'
# If main points to "index.jsc" instead of "index.js", the entry point is compiledMany apps compile only the proprietary modules and leave third-party dependencies (in node_modules/) as plaintext. Some apps compile everything. The ratio tells you how thorough the developer was about protecting their source.
Handling Custom Packaging
Some Electron apps deviate from the standard packaging:
- No ASAR: The app ships an unpacked
resources/app/directory instead. The .jsc files are directly accessible without extraction. - Multiple ASARs: Some apps split their code across multiple archives. Check for additional
.asarfiles in the resources directory. - Webpack/Rollup bundles: The app may bundle all code into a single large file before compiling. You will get one large .jsc file instead of many small ones.
- Custom encryption: Rarely, apps add their own encryption layer on top of V8 bytecode. You will need to reverse the encryption before decompilation. This is uncommon — most apps rely on bytecode compilation alone.
Determining the Electron Version
V8 bytecode is version-specific. Bytecode compiled with one V8 version cannot be loaded by another. To decompile the .jsc files, you need to know which V8 version produced them. The chain goes: Electron version → Chromium version → V8 version.
Check package.json Inside the ASAR
The extracted ASAR usually contains a package.json that lists the Electron version as a dependency or devDependency:
# Check the extracted package.json
cat ./extracted/package.json
# Look for version fields like:
# "devDependencies": { "electron": "^28.0.0" }
# or
# "electronVersion": "28.0.0"
# Some apps embed this in a separate file
cat ./extracted/version
cat ./extracted/.electron-versionMap Electron to V8 Version
Each Electron release ships a specific Chromium version, which includes a specific V8 version. Here are some common mappings:
Electron 17 → Chromium 98 → V8 9.8
Electron 20 → Chromium 104 → V8 10.4
Electron 22 → Chromium 108 → V8 10.8
Electron 25 → Chromium 114 → V8 11.4
Electron 28 → Chromium 120 → V8 12.0
Electron 30 → Chromium 124 → V8 12.4
Electron 33 → Chromium 130 → V8 13.0
Electron 35 → Chromium 134 → V8 13.4
Electron 38 → Chromium 138 → V8 14.0The full mapping is maintained in the electron-releases npm package and on the Electron releases page on GitHub.
When Version Info Is Stripped
Some apps strip version metadata from package.json before distribution. If you cannot find the Electron version in the extracted files, you have a few alternatives:
- Check the Electron binary: On macOS, run
strings "AppName.app/Contents/Frameworks/Electron Framework.framework/Electron Framework" | grep "Chrome/"to find the embedded Chromium version string. - Check the bytecode header: The .jsc file header contains the V8 version hash. JSC Decompiler reads this header automatically and detects the correct version without any manual input.
- Run the app and check DevTools: Launch the app and open DevTools (Ctrl+Shift+I or Cmd+Option+I). Run
process.versionsin the Console to see exact version numbers for Electron, Chromium, Node.js, and V8.
Shortcut: JSC Decompiler auto-detects the V8 version from the bytecode file header. If you are using JSC Decompiler for decompilation, you can skip the version identification step entirely. Upload the .jsc file and the correct decompiler will be selected automatically.
Decompiling the .jsc Files
With the .jsc files extracted and (optionally) the Electron version identified, you can decompile them. JSC Decompiler offers three methods depending on your workflow.
Single File via Web Interface
For quick analysis of one or two files: open jscdecompiler.com, upload the .jsc file, and read the decompiled JavaScript output in the browser. The V8 version is detected from the file header. No configuration needed.
Batch via ZIP Upload
For an entire Electron app with dozens or hundreds of compiled modules: ZIP the extracted .jsc files and upload the archive. JSC Decompiler processes every .jsc file in the ZIP and returns decompiled JavaScript for each one. This is the fastest way to get full visibility into a compiled Electron application.
# Create a ZIP of all .jsc files preserving directory structure
cd ./extracted
find . -name "*.jsc" -print | zip ../compiled_modules.zip -@
# Upload the ZIP to the web interface for batch processingAPI for Automated Pipelines
For integration into CI/CD, security scanning pipelines, or automated audit workflows, use the REST API:
# Decompile a single .jsc file
curl -X POST https://api.jscdecompiler.com/v1/decompile \
-H "Authorization: Bearer $API_KEY" \
-F "file=@extracted/main.jsc" \
-o decompiled/main.js
# Batch decompile all .jsc files from an extracted Electron app
for f in $(find extracted -name "*.jsc"); do
outdir="decompiled/$(dirname $f | sed 's|^extracted/||')"
outfile="$outdir/$(basename $f .jsc).js"
mkdir -p "$outdir"
curl -s -X POST https://api.jscdecompiler.com/v1/decompile \
-H "Authorization: Bearer $API_KEY" \
-F "file=@$f" \
-o "$outfile"
done
echo "Decompiled $(find decompiled -name '*.js' | wc -l) files"Understanding the Output
The decompiled output is JavaScript with reconstructed control flow, function names, and string literals. A few notes on what to expect:
- Variable names may be simplified (e.g.,
a,b,c) if the original source was minified before compilation. The control flow and string constants are still intact. - Comments are not preserved — V8 strips them during compilation.
- Module structure (exports, require calls) is preserved. You can trace which modules depend on which.
- String literals — including URLs, API keys, IPC channel names, and file paths — are recovered as-is from the V8 constant pool.
What to Look For in Decompiled Electron Code
The purpose of decompilation determines what you focus on. Here are the patterns that matter most for security analysis of Electron applications.
IPC Handlers (Main ↔ Renderer Communication)
Electron's IPC (Inter-Process Communication) layer is the primary attack surface. The main process defines handlers that the renderer can invoke. Look for:
// Main process IPC handlers — these define the attack surface
const { ipcMain } = require("electron");
// Dangerous: passes unchecked input to the file system
ipcMain.handle("read-file", (event, filePath) => {
return require("fs").readFileSync(filePath, "utf-8");
});
// Dangerous: arbitrary command execution from the renderer
ipcMain.handle("run-command", (event, cmd, args) => {
return require("child_process")
.spawnSync(cmd, args).stdout.toString();
});
// Safe: validates and constrains the input
ipcMain.handle("get-config", (event, key) => {
const allowed = ["theme", "language", "fontSize"];
if (!allowed.includes(key)) return null;
return config[key];
});Overly permissive IPC handlers are the most common vulnerability class in Electron apps. If a handler passes user-controlled input to child_process, fs, or net without validation, you have found a high-severity bug.
Node Integration and Context Isolation Settings
Search the decompiled main process code for BrowserWindow configuration:
// Security-relevant BrowserWindow options
new BrowserWindow({
webPreferences: {
nodeIntegration: true, // BAD: renderer gets full Node.js
contextIsolation: false, // BAD: no preload/page isolation
sandbox: false, // BAD: Chromium sandbox disabled
webSecurity: false, // BAD: same-origin policy off
allowRunningInsecureContent: true, // BAD: HTTP content on HTTPS pages
}
});Any of these misconfigurations significantly expand the attack surface. Modern Electron defaults are secure ( nodeIntegration: false, contextIsolation: true ), but many apps override the defaults. You will only know if you can read the code.
Preload Script Analysis
Preload scripts define the API that web content can access. They are the security boundary between the sandboxed renderer and the privileged main process. Look for:
- Exposed APIs: What functions does
contextBridge.exposeInMainWorldmake available? Are any of them too powerful? - Input validation: Does the preload script validate arguments before forwarding them via IPC? Or does it pass everything through unchecked?
- Leaked Node.js globals: Does the preload script accidentally expose
require,process, orBufferto the renderer?
Native Module Bindings
Some Electron apps use native Node.js modules (compiled C/C++ addons). In the decompiled source, look for require calls that load .node files. These are shared libraries with direct system access. Note which native modules are used and what capabilities they provide — this is where privilege escalation or sandbox escape vulnerabilities often live.
Data Storage and Network Requests
Check how the app stores sensitive data and where it sends network traffic:
- Local storage: Does the app store credentials, tokens, or API keys in plaintext files, SQLite databases, or Electron's
safeStorageAPI? Look for uses ofelectron-store,keytar, or direct file writes to the app data directory. - Network endpoints: What URLs does the app communicate with? Are there any unexpected domains? Is TLS enforced, or does the app accept insecure connections?
- Telemetry and analytics: What data does the app collect and send home? User behavior, system information, crash reports? Check the payload structure to understand what the vendor is actually collecting.
Real-World Use Cases
Pre-Procurement Vendor Security Review
Before deploying a third-party Electron application across your organization, your security team needs to know what it does. If the vendor ships compiled bytecode, you have two choices: trust the vendor, or decompile and verify. Decompilation lets you check Node integration settings, review IPC handlers, identify network endpoints, and verify that the app does not have capabilities beyond what the vendor claims.
This is particularly important for apps that request broad system permissions. An Electron app with Node integration enabled has the same access as any native application. You should know exactly what it does with that access before it runs on employee workstations.
Supply Chain Audit of Electron Dependencies
Electron apps bundle their entire dependency tree. If a compiled app includes hundreds of npm packages, you need to verify what is in that bundle. Decompile the .jsc files and check for:
- Known-vulnerable dependencies (cross-reference with npm audit advisories)
- Unexpected packages that were not in the declared dependency list
- Modified versions of legitimate packages with injected code
- Hardcoded credentials or API keys that should have been environment variables
Verifying Production Builds Match Source
If you are a developer shipping an Electron app with compiled modules, decompilation serves as a verification step. Decompile the production .jsc files and compare the output against your source to confirm that the build pipeline produced the expected result. This catches issues like accidental inclusion of debug code, test credentials, or development endpoints in production builds.
Bug Bounty Hunting
Many companies ship Electron-based desktop applications and run bug bounty programs. Compiled bytecode is not a barrier if you can decompile it. The typical workflow:
- Extract and decompile the application
- Map all IPC handlers and their input validation (or lack thereof)
- Identify Node integration and context isolation settings
- Look for XSS-to-RCE chains through insecure preload scripts
- Check for deep link handlers (custom protocol handlers) that accept external input
- Test for prototype pollution in IPC message serialization
Electron apps are a rich target for bug bounty research because the attack surface is large (web + Node.js + IPC + native modules) and many apps have not been thoroughly audited. Decompilation gives you the same view of the code that the developers have.
Version coverage: JSC Decompiler supports Electron 17 through Electron 38, covering V8 versions 9.8 through 14.0. This range includes every actively maintained Electron release. Upload your .jsc files and the correct V8 version is detected automatically from the bytecode header.
Summary
Decompiling an Electron app is a four-step process: locate the ASAR, extract it, identify the .jsc files, and decompile them. The ASAR extraction is straightforward. The version detection can be done manually or left to JSC Decompiler's auto-detection. The decompilation itself takes seconds per file.
What you do with the decompiled source depends on your purpose. Security auditors focus on IPC handlers and Node integration settings. Supply chain reviewers check bundled dependencies. Bug bounty hunters map the attack surface. In every case, the starting point is the same: turn the bytecode back into JavaScript you can read.
If you have an Electron app with compiled modules and need to see the source, upload the .jsc files to JSC Decompiler. The free tier handles the latest Electron versions. For full version coverage and batch processing, the Enterprise plan includes API access and supports every Electron release from version 17 onward.
Try JSC Decompiler Free
Upload a .jsc file and get readable JavaScript back in seconds. No signup required for the free tier.