[GH-ISSUE #3536] [Feature] Resolve BrowserWindow options from same-scope constants and setWindowOpenHandler overrides #1314

Open
opened 2026-10-16 21:04:07 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal (BrowserWindow dynamic, webPreferences variable, setWindowOpenHandler: none found). Related: #1802, #1656, #2932, and #3535 (the same app's esbuild bundle hides Electron entirely).
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Evidence, artifacts, or reconstruction

Current limitation

analyze_javascript_application resolves webPreferences and preload only when they're written inline in new BrowserWindow({...}). Two common, statically resolvable patterns end up as dynamic or missing:

Fixture Window explicit_web_preferences preload_entrypoints
a_literal.js: inline options with nodeIntegration, contextIsolation, preload 1, object-literal 3 1
b_const.js: const windowOptions = { webPreferences: { nodeIntegration: true, contextIsolation: false } }; new electron.BrowserWindow(windowOptions) in the same function 1, options_status: "dynamic" 0 0
d_openhandler.js: w.webContents.setWindowOpenHandler(() => ({ action: "allow", overrideBrowserWindowOptions: { webPreferences: { nodeIntegration: false, preload: require("path").resolve(__dirname, "p2.js") } } })) opener only 1 (opener's own) 0; no preload node

REA reports these honestly: options_status: "dynamic", web_preferences_status: "dynamic", absence_means_default: false. But these are the security-relevant settings (nodeIntegration, contextIsolation, preload), and they're resolvable without execution.

Real-world example: Joplin desktop 3.7.21 (open source, AGPL; GitHub release .deb, sha256 14de2d55…b965):

  • ElectronAppWrapper.js: const windowOptions = { …, webPreferences: { session: this.joplinSession_, nodeIntegration: true, contextIsolation: false, spellcheck: true, enableRemoteModule: true }, … }; … this.win_ = new electron_1.BrowserWindow(windowOptions);. REA reports the window as dynamic with no preferences, so it misses nodeIntegration: true / contextIsolation: false on the main window.
  • The same file sets the secondary-window preload through setWindowOpenHandler(… overrideBrowserWindowOptions: { webPreferences: { nodeIntegration: false, preload: path.resolve(__dirname, './utils/window/secondaryWindowPreload.js') } } …). REA does create electron-preload nodes for it (resolution_status: resolved, property:preload), but links them to no window, and the summary keeps preload_entrypoints: 0 (ElectronBoundaryAnalysis.ts counts only windows[].preload_path).

Use cases

  • Electron security review: report nodeIntegration / contextIsolation / sandbox / preload for every window, including popups configured through setWindowOpenHandler.
  • Reading real apps, which commonly build window options in a variable before the constructor call.

Desired outcome and success criteria

  • new BrowserWindow(x), where x is a same-scope const bound to an object literal (optionally with spreads of other resolvable literals), is resolved like an inline literal. It stays dynamic when x is reassigned or escapes.
  • setWindowOpenHandler returning a literal { action: "allow", overrideBrowserWindowOptions: {...} } produces a window-configuration record (child of the opener) with its webPreferences and preload, and the preload counts in preload_entrypoints or a separate count.
  • Success: b_const.js reports 2 webPreferences, and d_openhandler.js reports the overriding preload linked to the opener window.

Expected interaction with targets

Read existing files or captured data.

Verification available so far

I have a prototype or observed results.

Verification details

rea-agents@6.5.0 (Linux x64 Docker). Fixtures (a_literal.js, b_const.js, d_openhandler.js, each run with --source-path <file>) produced the table above. Joplin 3.7.21 scoped run: --source-path app.asar/{ElectronAppWrapper.js,bridge.js,…}, exit 0, browser_windows: 2, explicit_web_preferences: 0, preload_entrypoints: 0. Fixture files are below.

Fixture files (run each with --source-path <file>)

fx/package.json

{"name":"fx","version":"1.0.0","main":"a_literal.js"}

fx/a_literal.js

const { BrowserWindow } = require("electron");
new BrowserWindow({ webPreferences: { nodeIntegration: true, contextIsolation: false, preload: require("path").join(__dirname, "p1.js") } });

fx/b_const.js

const electron = require("electron");
function make() {
  const windowOptions = { webPreferences: { nodeIntegration: true, contextIsolation: false } };
  return new electron.BrowserWindow(windowOptions);
}
make();

fx/d_openhandler.js

const { BrowserWindow } = require("electron");
const w = new BrowserWindow({ webPreferences: { contextIsolation: true } });
w.webContents.setWindowOpenHandler(() => ({ action: "allow", overrideBrowserWindowOptions: { webPreferences: { nodeIntegration: false, preload: require("path").resolve(__dirname, "p2.js") } } }));

fx/p1.js

// preload 1

fx/p2.js

// preload 2

Alternatives considered

Reading the options manually. That works, but the summary then understates the app's most important security settings.

Possible direction

Reuse the lexical-binding resolution added for #1656 (aliases) to follow a const identifier to its object-literal initializer. Recognise setWindowOpenHandler as a window-configuration site.

References

  • #1656, #1802, #2932 / #2948
  • src/application/javascript/ElectronBoundaryAnalysis.ts (preload_entrypoints counts windows[].preload_path only)
Originally created by @Rinkia on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3536 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal (BrowserWindow dynamic, webPreferences variable, setWindowOpenHandler: none found). Related: #1802, #1656, #2932, and #3535 (the same app's esbuild bundle hides Electron entirely). - [x] I understand the contribution scope must be reviewed before integration; open questions are welcome here. - [x] This request does not include credentials, private samples, or sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Current limitation `analyze_javascript_application` resolves `webPreferences` and `preload` only when they're written inline in `new BrowserWindow({...})`. Two common, statically resolvable patterns end up as `dynamic` or missing: | Fixture | Window | `explicit_web_preferences` | `preload_entrypoints` | |---|---|---|---| | `a_literal.js`: inline options with `nodeIntegration`, `contextIsolation`, `preload` | 1, `object-literal` | 3 | 1 | | `b_const.js`: `const windowOptions = { webPreferences: { nodeIntegration: true, contextIsolation: false } }; new electron.BrowserWindow(windowOptions)` in the same function | 1, `options_status: "dynamic"` | **0** | 0 | | `d_openhandler.js`: `w.webContents.setWindowOpenHandler(() => ({ action: "allow", overrideBrowserWindowOptions: { webPreferences: { nodeIntegration: false, preload: require("path").resolve(__dirname, "p2.js") } } }))` | opener only | 1 (opener's own) | **0**; no preload node | REA reports these honestly: `options_status: "dynamic"`, `web_preferences_status: "dynamic"`, `absence_means_default: false`. But these are the security-relevant settings (`nodeIntegration`, `contextIsolation`, `preload`), and they're resolvable without execution. **Real-world example: Joplin desktop 3.7.21** (open source, AGPL; GitHub release `.deb`, sha256 `14de2d55…b965`): - `ElectronAppWrapper.js`: `const windowOptions = { …, webPreferences: { session: this.joplinSession_, nodeIntegration: true, contextIsolation: false, spellcheck: true, enableRemoteModule: true }, … }; … this.win_ = new electron_1.BrowserWindow(windowOptions);`. REA reports the window as `dynamic` with no preferences, so it misses `nodeIntegration: true` / `contextIsolation: false` on the main window. - The same file sets the secondary-window preload through `setWindowOpenHandler(… overrideBrowserWindowOptions: { webPreferences: { nodeIntegration: false, preload: path.resolve(__dirname, './utils/window/secondaryWindowPreload.js') } } …)`. REA does create `electron-preload` nodes for it (`resolution_status: resolved`, `property:preload`), but links them to no window, and the summary keeps `preload_entrypoints: 0` (`ElectronBoundaryAnalysis.ts` counts only `windows[].preload_path`). ### Use cases - Electron security review: report `nodeIntegration` / `contextIsolation` / `sandbox` / `preload` for every window, including popups configured through `setWindowOpenHandler`. - Reading real apps, which commonly build window options in a variable before the constructor call. ### Desired outcome and success criteria - `new BrowserWindow(x)`, where `x` is a same-scope `const` bound to an object literal (optionally with spreads of other resolvable literals), is resolved like an inline literal. It stays `dynamic` when `x` is reassigned or escapes. - `setWindowOpenHandler` returning a literal `{ action: "allow", overrideBrowserWindowOptions: {...} }` produces a window-configuration record (child of the opener) with its webPreferences and preload, and the preload counts in `preload_entrypoints` or a separate count. - Success: `b_const.js` reports 2 webPreferences, and `d_openhandler.js` reports the overriding preload linked to the opener window. ### Expected interaction with targets Read existing files or captured data. ### Verification available so far I have a prototype or observed results. ### Verification details `rea-agents@6.5.0` (Linux x64 Docker). Fixtures (`a_literal.js`, `b_const.js`, `d_openhandler.js`, each run with `--source-path <file>`) produced the table above. Joplin 3.7.21 scoped run: `--source-path app.asar/{ElectronAppWrapper.js,bridge.js,…}`, exit 0, `browser_windows: 2, explicit_web_preferences: 0, preload_entrypoints: 0`. Fixture files are below. <details><summary>Fixture files (run each with <code>--source-path &lt;file&gt;</code>)</summary> `fx/package.json` ```json {"name":"fx","version":"1.0.0","main":"a_literal.js"} ``` `fx/a_literal.js` ```js const { BrowserWindow } = require("electron"); new BrowserWindow({ webPreferences: { nodeIntegration: true, contextIsolation: false, preload: require("path").join(__dirname, "p1.js") } }); ``` `fx/b_const.js` ```js const electron = require("electron"); function make() { const windowOptions = { webPreferences: { nodeIntegration: true, contextIsolation: false } }; return new electron.BrowserWindow(windowOptions); } make(); ``` `fx/d_openhandler.js` ```js const { BrowserWindow } = require("electron"); const w = new BrowserWindow({ webPreferences: { contextIsolation: true } }); w.webContents.setWindowOpenHandler(() => ({ action: "allow", overrideBrowserWindowOptions: { webPreferences: { nodeIntegration: false, preload: require("path").resolve(__dirname, "p2.js") } } })); ``` `fx/p1.js` ```js // preload 1 ``` `fx/p2.js` ```js // preload 2 ``` </details> ### Alternatives considered Reading the options manually. That works, but the summary then understates the app's most important security settings. ### Possible direction Reuse the lexical-binding resolution added for #1656 (aliases) to follow a `const` identifier to its object-literal initializer. Recognise `setWindowOpenHandler` as a window-configuration site. ### References - #1656, #1802, #2932 / #2948 - `src/application/javascript/ElectronBoundaryAnalysis.ts` (`preload_entrypoints` counts `windows[].preload_path` only)
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 16, 2026):

Split the two independently testable outcomes: #4366 implements same-scope constant BrowserWindow/options resolution through existing lexical and mutation ownership, with exact source Evidence. This parent remains open for setWindowOpenHandler override configuration/parent relationships and optional literal-spread support. Current-main real Joplin 3.7.21 ASAR reproduction (selected ElectronAppWrapper.js + package.json) returned one window with zero explicit preferences; candidate validation is in progress. No target code is executed.

<!-- gh-comment-id:6208597544 --> @N0zoM1z0 commented on GitHub (Oct 16, 2026): Split the two independently testable outcomes: #4366 implements same-scope constant BrowserWindow/options resolution through existing lexical and mutation ownership, with exact source Evidence. This parent remains open for `setWindowOpenHandler` override configuration/parent relationships and optional literal-spread support. Current-main real Joplin 3.7.21 ASAR reproduction (selected `ElectronAppWrapper.js` + `package.json`) returned one window with zero explicit preferences; candidate validation is in progress. No target code is executed.
Author
Owner

@Rinkia commented on GitHub (Oct 18, 2026):

Re-ran the inline fixtures from this issue on rea-agents@6.7.0:

  • b_const.js (same-scope const windowOptions passed to new electron.BrowserWindow(windowOptions)): now browser_windows: 1, explicit_web_preferences: 2. The const-options part (#4366, PR #4400) is fixed in the release.
  • d_openhandler.js (setWindowOpenHandler → overrideBrowserWindowOptions.webPreferences.preload): still browser_windows: 1, explicit_web_preferences: 1, preload_entrypoints: 0, with no preload linked to a window. This remaining part is still open, as expected.
<!-- gh-comment-id:6249959654 --> @Rinkia commented on GitHub (Oct 18, 2026): Re-ran the inline fixtures from this issue on `rea-agents@6.7.0`: - `b_const.js` (same-scope `const windowOptions` passed to `new electron.BrowserWindow(windowOptions)`): now `browser_windows: 1, explicit_web_preferences: 2`. The const-options part (#4366, PR #4400) is fixed in the release. - `d_openhandler.js` (`setWindowOpenHandler` → `overrideBrowserWindowOptions.webPreferences.preload`): still `browser_windows: 1, explicit_web_preferences: 1, preload_entrypoints: 0`, with no preload linked to a window. This remaining part is still open, as expected.
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#1314
No description provided.