rea-reverse

ASAR unpacker and Electron IPC analyzer

Open an Electron app.asar to browse and download its files, see the main, preload and renderer entry points, and list the IPC channels and contextBridge APIs the app exposes between its processes.

Your archive never leaves your browser. The archive is read locally in a Web Worker that has no network access. Nothing is uploaded. How to verify this yourself

Drop an app.asar here

or

Usually in resources/app.asar of an Electron app (Contents/Resources on macOS). Up to the size your browser can open.

Limits of IPC detection

  • Calls are found by scanning the JavaScript tokens, not by running it. Computed property names (ipcRenderer[name](…)) and aliased calls (const send = ipcRenderer.send; send(…), or a destructured ipcRenderer under another name) are not recognized.
  • Only the first 4 MiB of each JS file and 128 MiB in total are scanned; app files are scanned before node_modules.

Most Electron apps ship their JavaScript, HTML and assets in a single file called app.asar, found in the app’s resources folder. ASAR is a simple archive format: a JSON header that describes a directory tree, followed by the file contents stored back to back, uncompressed. Inside are the three parts that define an Electron app’s security model: the main process code, which has full Node.js access; the preload scripts, which decide what the web page may reach; and the renderer pages. They talk to each other over IPC channels.

This tool opens an app.asar in your browser, lists every file, lets you download or preview any packed file, and builds a table of the IPC channels that the code registers and calls. The archive is never uploaded.

What it does

Input. An ASAR archive, usually resources/app.asar. Only the 16-byte prefix and the JSON header are read up front; file contents are read on demand, so a large archive costs little more than its header. Limits: a header of up to 32 MiB, up to 1,000,000 entries, directory nesting up to 128 levels, and a two-minute time limit.

Archive. You get:

  • File tree: every file, directory and symbolic link as a collapsible, searchable tree with sizes. The JSON export adds each file’s offset, the executable flag and the per-file integrity record (algorithm and hash) when the archive has one.
  • Statistics: counts of files, directories, links and unpacked files, total size, a breakdown by file extension, and the 20 largest files.
  • Download and preview: any file that is stored inside the archive can be downloaded on its own or shown as text, without extracting the rest. Links are followed.
  • app.asar.unpacked entries: files marked as unpacked are listed, but their bytes are not in the .asar file; Electron keeps them in the app.asar.unpacked/ folder next to it. Native modules are typically stored there.

Electron analysis. The report adds:

  • package.json: name, version and main. The main entry is resolved the way Node does it, with index.js as the default.
  • Preload scripts: string values assigned to a preload property, as in webPreferences: { preload: path.join(__dirname, 'preload.js') }, resolved to files in the archive, with the file and line where each one is configured.
  • Renderer entries: <script src> tags in the archive’s HTML files, resolved to archive paths. Remote URLs are ignored.
  • IPC channel table: every call to ipcMain.handle, ipcMain.on, ipcMain.once, ipcRenderer.invoke, ipcRenderer.send, ipcRenderer.sendSync, ipcRenderer.on and webContents.send (including forms such as win.webContents.send). Each row shows the channel, the API, the direction (renderer to main, or main to renderer), the process, and the file:line. When the channel is not a plain string literal, the row is marked dynamic and shows the expression instead.
  • contextBridge exposures: each contextBridge.exposeInMainWorld(name, { ... }) with the exposed name and the keys of the API object. This is the exact surface the web page can call.
  • Native modules: .node files (Node add-ons compiled to Mach-O, ELF or PE), with their size and whether they are unpacked.
  • Source maps: .map files in the archive and the number of scanned JavaScript files that end with a sourceMappingURL comment.
  • Warnings: entries with suspicious names (.., slashes, absolute paths) or links that point outside the archive, malformed entries, files that extend past the end of the archive, and JavaScript files that were only partly scanned.

How it works

The archive starts with two Chromium “pickle” records: the first gives the size of the header, the second holds the header as a JSON string. The page checks that these sizes agree, parses the JSON, and walks its files tree. A packed file is located at the start of the data area plus its offset, which ASAR stores as a decimal string. The format handling was checked against @electron/asar 4.3.1, and the prefix checks follow REA, the open-source project by morluto (MIT).

For the IPC table, every .js, .cjs and .mjs file in the archive is scanned with a lightweight JavaScript tokenizer, application files first and node_modules last. The tokenizer skips comments, string contents and regular expressions, so text such as "ipcMain.handle('x')" inside a string is not reported. It then matches member calls such as ipcRenderer.invoke( and reads the first argument. A channel counts as literal only when that argument is a single string or a template without substitutions.

To decide which process a file belongs to, the page starts from the main entry, the resolved preload scripts and the renderer scripts, and follows relative require and import paths. A file that is not reached this way gets its process from the API it uses: ipcMain and webContents exist only in the main process, and a file that calls contextBridge is treated as a preload. Otherwise the process is shown as unknown.

How your archive stays private

The archive is not uploaded:

  • Parsing, scanning and each file download run in a Web Worker on your device. The worker reads only the header and the slices it needs through the browser’s file APIs.
  • Analytics events are coarse: success or an error code, a coarse range for the number of files found (such as 11-100), a file-size bucket and a duration bucket. They never contain the file name, file paths, channel names or any other content. The full list is on the privacy page.
  • The tool page shows no ads.

How to verify this yourself

  1. Open your browser’s developer tools and switch to the Network tab before you choose a file. Analyze an archive and download a file from it. The only requests are page assets and the worker script /_astro/asar-worker-*.js; none carries your archive.
  2. Or load the page, go offline (turn off Wi-Fi, or tick Offline in the Network tab) and analyze a file. It still works.
  3. The worker runs under the Content Security Policy default-src 'none'; script-src 'self'; connect-src 'none', so the browser does not let it make any network request.

Limitations

  • Tokenizer, not a full parser. Calls are recognized by the literal names ipcMain, ipcRenderer and webContents followed by a method. Aliases are not followed: const { ipcRenderer: r } = require('electron'); r.send(...) or const r = ipcRenderer is missed, and so is a computed call such as ipc[name](...) or ipcMain['handle'](...). Minified bundles that rename these objects lose most rows.
  • Only the listed APIs. Methods such as ipcRenderer.once, ipcRenderer.postMessage, ipcRenderer.sendToHost, ipcMain.removeHandler and MessagePort-based channels are not in the table.
  • Dynamic channels stay dynamic. A channel built from a variable is shown with its expression, not resolved to a value.
  • Scan budget. Only the first 4 MiB of each JavaScript file are scanned, and at most 128 MiB in total. Files beyond the budget, usually in node_modules, are listed in a warning.
  • What is not scanned. Unpacked JavaScript (its bytes are outside the archive), inline <script> blocks in HTML, TypeScript sources and package.json or HTML files larger than 1 MiB.
  • Integrity is shown, not verified. The JSON export carries each file’s integrity hash from the header, but the page does not recompute it, and it does not check Electron’s ASAR integrity fuse or the hash stored in the app bundle.
  • Process attribution is heuristic. Bare package imports are not followed, so code reached only through a package may show as unknown.
  • Native code is out of scope. .node files are listed; analyze them with a native tool such as Ghidra.

FAQ

Can I extract the whole archive?

The page downloads files one at a time. For a full extraction, use npx @electron/asar extract app.asar out/ on your own machine.

Why can’t I download a file marked unpacked?

Its bytes are not inside the .asar file. Look in the app.asar.unpacked/ folder next to it.

Why is a channel shown as dynamic?

The first argument is not a single string literal, for example `user:${id}` or a constant defined elsewhere. Search the file at the given line to see what it resolves to.

The IPC table is empty, but the app clearly uses IPC.

The code is probably minified with renamed objects, or the IPC calls live in an unpacked or oversized file. Check the warnings and the scan statistics.

Does it find security problems?

It maps the attack surface: exposed APIs, channels and their handlers. Whether a handler validates its input is for you to read in the code.

Compliance

Analyze apps you built, apps you are authorized to assess, or open-source apps. Respect the licenses of the software you inspect, including any terms that restrict reverse engineering, and the laws that apply to you. The tool gives no advice on bypassing licensing or other protections. See the disclaimer.