Skip to content

Disable Node runtime fuses in packaged builds - #6364

Open
nbarrett-logitech wants to merge 1 commit into
masterfrom
chore/harden-electron-fuses
Open

nbarrett-logitech wants to merge 1 commit into
masterfrom
chore/harden-electron-fuses

Conversation

@nbarrett-logitech

Copy link
Copy Markdown
Contributor

Description

Packaged builds ship Electron's default fuses, and three of them leave the signed app binary usable as a general-purpose Node runtime: it honors ELECTRON_RUN_AS_NODE, NODE_OPTIONS and the --inspect family of flags. Nothing in the app needs any of them at runtime, and Electron's security guidance is to turn them off for production. This PR turns them off at package time on macOS and Windows.

It uses electron-builder 26's built-in electronFuses option, which flips the fuses after our afterPack hook and before signing, so the signature covers the flipped binary. @electron/fuses already ships as a dependency of electron-builder, so package.json doesn't change. The preview and beta configs inherit it from base.config.js.

File Change
electron-builder/base.config.js Adds electronFuses turning off RunAsNode, EnableNodeOptionsEnvironmentVariable and EnableNodeCliInspectArguments. Unsigned macOS builds (SLOBS_NO_SIGN) re-apply the ad-hoc signature that flipping invalidates, so local arm64 builds still launch.

Behavior changes, packaged builds only. yarn start and the e2e suite run the unpackaged Electron from node_modules and don't change.

  • child_process.fork() throws in the packaged app. Nothing uses it today. Use utilityProcess for a standalone Node process.
  • NODE_OPTIONS and NODE_EXTRA_CA_CERTS are ignored.
  • --inspect no longer attaches a debugger to a packaged main process. SLOBS_PRODUCTION_DEBUG still opens DevTools on every window, because it doesn't go through a fuse.

Why nothing breaks. Everything the app spawns is a native executable: the OBS server, the crash handler, the Windows updater, and the vision and avatar binaries. Every relaunch (app.relaunch(), the updater's --exec) starts the app normally. No production dependency calls child_process.fork(). None of the 174 native binaries in the production tree (the macOS builds of obs-studio-node and its helpers, crash-handler, realm and the rest) reference ELECTRON_RUN_AS_NODE or NODE_OPTIONS.

Left out on purpose: OnlyLoadAppFromAsar and embedded asar integrity validation. Turning those on needs a signed-build check first. afterPack re-signs and edits files in app.asar.unpacked after the asar header is hashed (codesign, install_name_tool on node-libuiohook, copying the OBS frameworks in), and those edits have to survive validation on both platforms. That's worth its own PR.

Verified locally (unsigned packages built from this branch with the real base.config.js):

Fuse Stock Electron 43.8.3 (macOS and Windows) Streamlabs Desktop.app (arm64) Streamlabs OBS.exe (x64)
RunAsNode Enabled Disabled Disabled
EnableNodeOptionsEnvironmentVariable Enabled Disabled Disabled
EnableNodeCliInspectArguments Enabled Disabled Disabled
Every other fuse — unchanged unchanged

On the macOS package, codesign --verify --deep --strict passes. A normal launch brings up the worker, main and child windows, the OBS server and its helpers, and the crash handler. NODE_OPTIONS and --inspect are ignored, the app log has no errors, and SIGTERM shuts it down cleanly. I packaged the Windows target on a Mac only to read the exe's fuses, so it has not been launched on Windows.

QA Instructions

  • Not verified yet: the Windows app starting, and any signed build. I could only produce unsigned builds locally, and the Windows package was built on a Mac. The Windows steps below matter most
  • Build a signed Windows installer from this branch, install it, and run npx @electron/fuses read --app "<install dir>\Streamlabs OBS.exe". Expect RunAsNode, EnableNodeOptionsEnvironmentVariable and EnableNodeCliInspectArguments to read Disabled
  • Launch it, log in, and confirm the scene preview renders and a short recording saves
  • Change the language in Settings > General and confirm the app restarts on its own
  • On a signed, notarized macOS build, repeat the fuse read on Streamlabs Desktop.app, then launch it and confirm the preview renders and the virtual camera starts

Packaged builds shipped Electron's default fuses, which leave the app
binary usable as a general-purpose Node runtime: it honored
ELECTRON_RUN_AS_NODE, NODE_OPTIONS and the --inspect family of flags.
Nothing in the app needs any of these at runtime.

Turn off the RunAsNode, EnableNodeOptionsEnvironmentVariable and
EnableNodeCliInspectArguments fuses through electron-builder's built-in
electronFuses option, which flips them after the afterPack hook and
before signing, on macOS and Windows. Unsigned macOS builds re-apply the
ad-hoc signature that flipping invalidates, so local arm64 builds made
with SLOBS_NO_SIGN still launch.
Copilot AI balanced review requested due to automatic review settings October 30, 2026 16:04
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Security-sensitive packaging fuse changes lack signed Windows startup/relaunch checks and signed, notarized macOS launch/native-helper validation, with reported verification limited to unsigned packages.

No open issues 🎉

What changed in this PR

Hardens packaged Streamlabs Desktop builds on macOS and Windows by disabling Electron features that allow the app binary to act as a Node runtime or expose main-process debugging.

Changes:

  • Disables the RunAsNode, Node environment options, and Node CLI inspection fuses before signing.
  • Restores the ad-hoc signature for unsigned macOS builds so local arm64 packages remain launchable.
File Description
electron-builder/​base.config.js Configures production fuse restrictions and conditional macOS ad-hoc signing.

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Signed Windows and notarized macOS artifacts still require release-environment validation of fuse states, signatures, startup, relaunch, recording, and virtual-camera behavior.

No open issues 🎉

Review effort: Balanced

@bundlemon

bundlemon Bot commented Oct 30, 2026

Copy link
Copy Markdown

BundleMon

Unchanged files (4)
Status Path Size Limits
✅ renderer.(hash).js
10.77MB -
✅ vendors~renderer.(hash).js
4.65MB -
✅ updater.js
115.29KB -
✅ guest-api.js
40.23KB -

No change in files bundle size

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants