Best Practices

Electron App Supply Chain Security Posture

Electron apps ship Chromium, Node.js, and your entire npm tree to a user's desktop, running with the privileges of the logged-in user. The supply chain implications are severe enough that they deserve their own category of threat model.

Nayan Dey
Senior Security Engineer
7 min read

Electron powers Slack, Discord, VS Code, Figma Desktop, 1Password, and thousands of other apps that sit on users' home and work machines with significant privilege. When I audit an Electron app, I tell the team the same thing every time: you are shipping a web browser plus Node.js plus a hundred megabytes of npm dependencies to a user's desktop, running with full user-context permissions. The supply chain decisions you make end up in ~/Applications and persist there until the user uninstalls or updates.

This piece is about the supply chain posture specifically — not the general "Electron security checklist" you can find in the official docs. I want to talk about what actually goes wrong in the dependency tree and what a serious team does about it.

Chromium is your biggest dependency, and it ages poorly

Every Electron app bundles a specific Chromium version. Electron 27 (released October 2023) shipped Chromium 118. Electron 25 shipped Chromium 114. If your app is on Electron 22, you are shipping Chromium 108, which is nine months behind and missing a couple hundred CVEs Chrome has patched since.

Users who ship LTS versions often do not realize that Electron's release cadence means shipping security patches requires actual upgrades, not just config changes. Electron's support window is short — roughly the latest three stable versions — and older versions stop getting Chromium security backports.

I mandate a rule in every Electron audit: your shipped Electron version must be within two major releases of the latest stable. If you cannot upgrade that fast, you need to justify why not, and the justification cannot be "it is too much work." The work of upgrading Electron is less than the work of explaining to a customer why your app shipped with a Chromium zero-day for four months.

The autoUpdater attack surface

Electron's autoUpdater (or Squirrel on Mac, or electron-updater from the electron-builder community) downloads update packages from a server you control and applies them. If an attacker compromises your update server or your signing keys, they can distribute malicious code to your entire user base.

The update flow should meet three bars: the update binary must be signed by your code-signing certificate and verified before execution; the manifest (which describes what is available) should be signed separately so an attacker cannot serve a downgrade; and the channel between client and server should be TLS with certificate pinning when possible.

I have audited Electron apps with all three controls in place. I have also audited ones where the update manifest was served over HTTP with no signature because the developer did not realize Squirrel's default signature verification only covered the binary, not the manifest. That is a downgrade-attack vector sitting in the default configuration.

The nodeIntegration ghost

nodeIntegration: true in your renderer's webPreferences grants the renderer full Node.js access. Any XSS in the renderer becomes remote code execution on the user's machine. This has been the source of most headline Electron CVEs — the electron-notifier exploit, the various remote-takeover bugs in older Slack versions, and so on.

The framework default has been nodeIntegration: false since Electron 5 (2019), but legacy codebases still have it on. If your app uses it, schedule the migration. contextIsolation: true is the other half of the story, and as of Electron 12 (2021) it is the default. An audit item in every Electron review I do: grep for nodeIntegration and contextIsolation in every BrowserWindow instantiation.

Preload scripts and the contextBridge

Once you turn off nodeIntegration, you need preload scripts and contextBridge to expose specific APIs to the renderer. This is safer than full Node access, but it is a narrow API contract that developers sometimes widen inappropriately.

The anti-pattern is exposing raw ipcRenderer or require through the bridge. The renderer now has full messaging access to the main process, which usually has full Node access, which means you have reintroduced the original risk by a longer path. Expose narrow, purpose-specific APIs — saveFile(content), getAppVersion() — rather than generic message-passing primitives.

The npm tree that ends up on the user's disk

A typical Electron app's app.asar contains not just the app source but most of its runtime dependencies, bundled or unbundled depending on your packaging tool. This means every npm package in your production tree ends up as code on the user's machine, running with user privileges.

Every transitive dependency is a potential code-execution vector. The ua-parser-js compromise in October 2021 — where a malicious version was published and installed a cryptocurrency miner — hit Electron apps particularly hard because those apps often had the compromised version bundled into releases, meaning users were running the malicious code for as long as it took to rebuild and ship an update.

Your Electron build process should generate an SBOM, flag new transitive dependencies in diffs, and block releases that introduce packages with known advisories. This is table stakes for any desktop app that will sit on thousands of machines.

Code signing and notarization

macOS notarization (required for apps distributed outside the App Store since 2020) and Windows Authenticode signing are both required for a trustworthy Electron app. Skip either and users get scary warnings, or in macOS's case, the app simply will not launch.

Notarization is a check against known malware, not a security guarantee. It does not mean Apple has audited your code. It means Apple's scanner did not find a signature match for known malware. A compromised dependency can easily slip through notarization because it is not yet on the malware list. Do not treat notarization as security review; treat it as a distribution requirement.

The native module surface

Electron apps frequently use native Node modules — better-sqlite3, keytar, node-pty, serialport, and so on. These ship platform-specific compiled binaries. Every native module is a package you are trusting to produce binaries that match the source on GitHub.

The provenance gap here is real. npm's package signing story for native modules is weaker than for pure JS, and prebuilt binaries are often served from package maintainers' personal GitHub release pages. Verify where the binaries come from, pin versions strictly, and if your threat model is high-assurance, build native modules from source in your own CI.

Remote content and the sandbox

Electron's renderer sandbox, enabled via sandbox: true, limits what a renderer can do even if nodeIntegration is on by mistake. It has been available since Electron 5 and is recommended default since roughly Electron 20.

Apps that load remote content — that is, content not bundled with the app — should have sandbox on, contextIsolation on, nodeIntegration off, and navigation restricted to an allowlist of origins. Apps that load only local content still benefit from these settings, because a successful XSS against local content is otherwise a gateway to full system access. Defense in depth applies here too.

Telemetry and the third-party SDK aggregation

Most Electron apps bundle telemetry SDKs — Sentry, Segment, Amplitude, LogRocket, and so on. Each of these is a third-party code path that runs in your app and has some level of network access. Audit what they collect, verify they are not inadvertently capturing sensitive data (clipboard contents, form field values, URL parameters with tokens), and keep the list short.

How Safeguard Helps

Safeguard scans your Electron build — app dependencies, native modules, and Chromium version — and flags CVEs across all three layers in a single dashboard. Griffin AI surfaces configuration risks like nodeIntegration: true or missing contextIsolation from static analysis of your BrowserWindow definitions. SBOM generation gives you a precise inventory of what ships in every app.asar, which is what a customer's security review will actually demand. Policy gates block releases built on Electron versions older than your configured support floor and prevent introduction of native modules from unverified publishers, keeping the desktop supply chain as tight as a web app supply chain deserves to be.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.