[GH-ISSUE #4490] [Bug] Destructuring Electron from a Webpack module-table external drops BrowserWindow and IPC (const {BrowserWindow, ipcMain} = external_electron_) #1777

Open
opened 2026-10-16 21:10:09 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @Rinkia on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4490

Before submitting

  • I searched the open and closed issues for a related report (electron destructuring, webpack external destructure, object pattern electron). Related: #2932 and #2948 (Webpack bootstrap externals, member form; now on main), #3596 (esbuild __require), #3536 (BrowserWindow options), #4355 (Webpack getters through loader aliases). None covers destructuring.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

analyze_javascript_application follows Electron through a Webpack module-table external when members are read directly (external_electron_.BrowserWindow, the #2948 case). It loses Electron when the binding is destructured from the external. Webpack produces exactly that shape when it bundles the common CommonJS idiom const { app, BrowserWindow, ipcMain } = require("electron"): the require becomes __webpack_require__(<id>) of a module.exports = require("electron") module.

Minimal fixtures. Each is a directory with package.json ("main":"main.js") and a webpack-5-style bootstrap whose module 84157 is module.exports = require("electron"):

Fixture main.js body (after the bootstrap) windows webPrefs IPC ops main handlers
A const electron = require("electron"); const { ipcMain } = electron; ipcMain.on("chan-a", …) – – 1 1
D var external_electron_ = __webpack_require__(84157); external_electron_.ipcMain.on("chan-d", …) – – 1 1
B var external_electron_ = __webpack_require__(84157); const {app, ipcMain} = external_electron_; ipcMain.on("chan-b", …) – – 0 0
C same as B, with the registration inside const init = () => { if (!ipcMain || !app) return; ipcMain.on("chan-c", …) }; init(); – – 0 0
F new external_electron_.BrowserWindow({webPreferences:{preload:"./preload.js",nodeIntegration:true}}) 1 2 – –
E const {BrowserWindow} = external_electron_; new BrowserWindow({…same options…}) 0 0 – –

Fixture E hides a nodeIntegration: true window completely. The result has no unknown for the unresolved binding, so a reader sees "no windows".

Results are identical on rea-agents@6.5.0 and on main @ 180ba03bf (built from source).

Real-world case: Bitwarden desktop 2026.10.0. Its main process is a Webpack bundle with a single module.exports = require("electron") module, and electron-store is bundled as const {app, ipcMain, shell} = external_electron_;. REA reports 33 of the bundle's 34 literal-channel ipcMain registrations. The missing one is exactly ipcMain.on('electron-store-get-data', …) from that destructured binding. The rest of the result matches the bundle: 1 BrowserWindow, main_handlers 50, operations 145.

Reproduction

rea analyze-javascript-application …/opt/Bitwarden/resources/app.asar --source-path main.js --source-path preload.js --format json

main.js (5,042,030 bytes, from the ASAR) has var external_electron_ = __webpack_require__(84157); at line 136592, module.exports = require("electron"); at line 131947, and the electron-store registration at line 138952.

Expected behavior

An object pattern whose initializer resolves to the Electron module identity (directly, or through the proven Webpack external from #2948) binds each destructured property to the matching Electron member, as the plain-require case (A) already does. Then B, C and E report their handler and window, and Bitwarden reports 34 of 34. If a pattern can't be resolved (computed keys, rest elements), report an unknown rather than nothing.

Impact and scope

Any Webpack-bundled Electron main process whose own code, or a bundled dependency, uses the destructuring idiom. That is the form Electron's own documentation uses, so windows, webPreferences and IPC handlers can be under-reported, including security-relevant options. Tested: the fixtures on 6.5.0 and main 180ba03bf; Bitwarden on both.

Originally created by @Rinkia on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4490 ### Before submitting - [x] I searched the open and closed issues for a related report (electron destructuring, webpack external destructure, object pattern electron). Related: #2932 and #2948 (Webpack bootstrap externals, member form; now on `main`), #3596 (esbuild `__require`), #3536 (BrowserWindow options), #4355 (Webpack getters through loader aliases). None covers destructuring. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `analyze_javascript_application` follows Electron through a Webpack module-table external when members are read directly (`external_electron_.BrowserWindow`, the #2948 case). It loses Electron when the binding is **destructured** from the external. Webpack produces exactly that shape when it bundles the common CommonJS idiom `const { app, BrowserWindow, ipcMain } = require("electron")`: the `require` becomes `__webpack_require__(<id>)` of a `module.exports = require("electron")` module. Minimal fixtures. Each is a directory with `package.json` (`"main":"main.js"`) and a webpack-5-style bootstrap whose module `84157` is `module.exports = require("electron")`: | Fixture | `main.js` body (after the bootstrap) | windows | webPrefs | IPC ops | main handlers | |---|---|---|---|---|---| | A | `const electron = require("electron"); const { ipcMain } = electron; ipcMain.on("chan-a", …)` | – | – | 1 | 1 | | D | `var external_electron_ = __webpack_require__(84157); external_electron_.ipcMain.on("chan-d", …)` | – | – | 1 | 1 | | **B** | `var external_electron_ = __webpack_require__(84157); const {app, ipcMain} = external_electron_; ipcMain.on("chan-b", …)` | – | – | **0** | **0** | | **C** | same as B, with the registration inside `const init = () => { if (!ipcMain \|\| !app) return; ipcMain.on("chan-c", …) }; init();` | – | – | **0** | **0** | | F | `new external_electron_.BrowserWindow({webPreferences:{preload:"./preload.js",nodeIntegration:true}})` | 1 | 2 | – | – | | **E** | `const {BrowserWindow} = external_electron_; new BrowserWindow({…same options…})` | **0** | **0** | – | – | Fixture E hides a `nodeIntegration: true` window completely. The result has no unknown for the unresolved binding, so a reader sees "no windows". Results are identical on `rea-agents@6.5.0` and on `main` @ `180ba03bf` (built from source). **Real-world case: Bitwarden desktop 2026.10.0.** Its main process is a Webpack bundle with a single `module.exports = require("electron")` module, and `electron-store` is bundled as `const {app, ipcMain, shell} = external_electron_;`. REA reports 33 of the bundle's 34 literal-channel `ipcMain` registrations. The missing one is exactly `ipcMain.on('electron-store-get-data', …)` from that destructured binding. The rest of the result matches the bundle: 1 BrowserWindow, `main_handlers` 50, `operations` 145. ### Reproduction - Fixtures: as in the table above. I can attach them. - Bitwarden: `Bitwarden-2026.10.0-amd64.deb` from https://github.com/bitwarden/clients/releases/tag/desktop-v2026.10.0, sha256 `fcabfc7da3c327e2625b105e951cee4e60703bb8e256f0aac9cc9bc83ec7063f` (matches the GitHub asset digest). Then: ```sh rea analyze-javascript-application …/opt/Bitwarden/resources/app.asar --source-path main.js --source-path preload.js --format json ``` `main.js` (5,042,030 bytes, from the ASAR) has `var external_electron_ = __webpack_require__(84157);` at line 136592, `module.exports = require("electron");` at line 131947, and the `electron-store` registration at line 138952. ### Expected behavior An object pattern whose initializer resolves to the Electron module identity (directly, or through the proven Webpack external from #2948) binds each destructured property to the matching Electron member, as the plain-`require` case (A) already does. Then B, C and E report their handler and window, and Bitwarden reports 34 of 34. If a pattern can't be resolved (computed keys, rest elements), report an unknown rather than nothing. ### Impact and scope Any Webpack-bundled Electron main process whose own code, or a bundled dependency, uses the destructuring idiom. That is the form Electron's own documentation uses, so windows, `webPreferences` and IPC handlers can be under-reported, including security-relevant options. Tested: the fixtures on 6.5.0 and `main` `180ba03bf`; Bitwarden on both.
Author
Owner

@mohammadasim224 commented on GitHub (Oct 24, 2026):

Draft #6993 fixes the destructured form on current main: const s=n(45840);const{BrowserWindow,ipcMain}=s now resolves windows and identity-based IPC. Computed-key/rest unknowns are left out (plain require destructuring behaves the same). I noticed #4493 also covers this issue but it conflicts with main; happy to close mine if that one is preferred.

<!-- gh-comment-id:6383367172 --> @mohammadasim224 commented on GitHub (Oct 24, 2026): Draft #6993 fixes the destructured form on current `main`: `const s=n(45840);const{BrowserWindow,ipcMain}=s` now resolves windows and identity-based IPC. Computed-key/rest unknowns are left out (plain `require` destructuring behaves the same). I noticed #4493 also covers this issue but it conflicts with `main`; happy to close mine if that one is preferred.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1777
No description provided.