MarshallOfSound

#50419: fix: lazily initialize safeStorage async encryptor

Merged
Created: Mar 22, 2026, 3:34:04 PM
Merged: Mar 23, 2026, 12:47:14 PM
3 comments
Target: main

Description of Change

The SafeStorage constructor previously registered a browser observer that called os_crypt_async()->GetInstance() on app-ready. Because ESM named imports (import { x } from 'electron') eagerly evaluate all electron module getters, simply importing electron in an ESM entrypoint would construct SafeStorage and touch the OS keychain on app-ready — even when safeStorage was never used.

This showed up as a macOS CI hang: the esm-spec import-meta fixture triggers a keychain access prompt that blocks the test runner until timeout, manifesting as flaky macos-x64 / test (darwin, 2) failures (~50% rate, ~14 occurrences over the last ~50 commits). Diagnosed via timeout screenshot artifacts showing the "Electron Safe Storage" keychain dialog.

Trigger chain:

  1. spec/fixtures/esm/import-meta/main.mjs does import { app, BrowserWindow } from 'electron'
  2. Node's ESM loader eagerly evaluates all electron lazy getters to build the namespace, including safeStorage
  3. This calls _linkedBinding('electron_browser_safe_storage') → SafeStorage() constructor → AddObserver(this)
  4. App reaches ready → OnFinishLaunching → os_crypt_async()->GetInstance() → KeychainKeyProvider::GetKey() → keychain prompt → hang

Fix: The async encryptor is now requested lazily on the first call to encryptStringAsync, decryptStringAsync, or isAsyncEncryptionAvailable. The constructor is now a no-op.

isAsyncEncryptionAvailable() now returns a Promise<boolean> that resolves once initialization completes, matching what the docs already stated (the previous implementation returned a sync bool, contradicting the docs).

Regression from #49054.

Checklist

  • PR description included
  • npm test passes
  • Relevant documentation updated
  • PR title follows semantic commit guidelines

Release Notes

Notes: Fixed an issue where importing electron via ESM would touch the OS keychain on app-ready even when safeStorage was never used. safeStorage.isAsyncEncryptionAvailable() now returns a Promise as documented.

Backports

42-x-y
Merged
PR Number
#51924
Merged At
Jun 9, 2026, 12:21:50 PM
Released In
v42.4.1
Release Date
Jun 16, 2026, 11:00:20 AM

Semver Impact

Major
Breaking changes
Minor
New features
Patch
Bug fixes
None
Docs, tests, etc.

Semantic Versioning helps users understand the impact of updates:

  • Major (X.y.z): Breaking changes that may require code modifications
  • Minor (x.Y.z): New features that maintain backward compatibility
  • Patch (x.y.Z): Bug fixes that don't change the API
  • None: Changes that don't affect using facing parts of Electron