diff --git a/config/scripts/electron-runtime-floor.test.ts b/config/scripts/electron-runtime-floor.test.ts new file mode 100644 index 00000000000..77a1d4741e0 --- /dev/null +++ b/config/scripts/electron-runtime-floor.test.ts @@ -0,0 +1,57 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' + +/** + * Why a floor and not just a pin: Electron 43.5.0/43.6.0 set and unset `GDK_GL` + * around `gtk_init()` while FontConfig warmed up on a pool thread, and below + * glibc 2.41 that frees `environ` under a concurrent `getenv()` — a launch-time + * use-after-free on every Ubuntu we support (stablyai/orca#20081). 43.7.0 stops + * freeing the published `environ`. A downgrade past it re-ships that crash, and + * nothing else in the tree would notice. + */ +const MINIMUM_ELECTRON_VERSION = '43.7.0' + +function parseVersion(specifier: string): [number, number, number] { + const match = /(\d+)\.(\d+)\.(\d+)/.exec(specifier) + if (!match) { + throw new Error(`unparseable Electron version: ${specifier}`) + } + return [Number(match[1]), Number(match[2]), Number(match[3])] +} + +function meetsRuntimeFloor(specifier: string): boolean { + const version = parseVersion(specifier) + const floor = parseVersion(MINIMUM_ELECTRON_VERSION) + for (const [index, part] of version.entries()) { + if (part !== floor[index]) { + return part > floor[index] + } + } + return true +} + +describe('electron runtime floor', () => { + it.each([ + ['42.9.0', false], + ['43.6.0', false], + ['43.7.0', true], + ['43.7.1', true], + ['43.8.0', true], + ['44.0.0', true] + ])('reads %s as meeting the floor: %s', (specifier, expected) => { + expect(meetsRuntimeFloor(specifier)).toBe(expected) + }) + + it('pins Electron at or above the glibc environ-race fix', () => { + const packageJson = JSON.parse( + readFileSync(join(__dirname, '../../package.json'), 'utf-8') + ) as { devDependencies: Record } + const specifier = packageJson.devDependencies.electron + + expect( + meetsRuntimeFloor(specifier), + `electron ${specifier} is below the ${MINIMUM_ELECTRON_VERSION} runtime floor` + ).toBe(true) + }) +}) diff --git a/docs/reference/linux-glibc-compatibility.md b/docs/reference/linux-glibc-compatibility.md index 20e9b38acb5..64e72a67b83 100644 --- a/docs/reference/linux-glibc-compatibility.md +++ b/docs/reference/linux-glibc-compatibility.md @@ -131,3 +131,33 @@ a hole in the matrix. No strong `GLIBC_` node may exceed `2.31`, and no `GLIBCXX_`/`CXXABI_` node may exceed `3.4.28`/`1.3.12` — what stock Ubuntu 20.04 ships. + +## Runtime floor: the `environ` race below glibc 2.41 (Electron ≥ 43.7.0) + +Separate from the build floor above, one glibc runtime bug constrains which +Electron we may ship. Before glibc 2.41, `setenv`/`unsetenv` reallocate the +`environ` array and **free** the old one, so a concurrent `getenv()` on another +thread reads freed memory. Ubuntu 20.04–24.04 (2.31–2.39) are all below that +line, so every Linux target we support is exposed. + +Electron 43.5.0 made that latent race reachable on every launch: it started +setting `GDK_GL=disable` around `gtk_init()` and unsetting it right after, while +in the same change moving FontConfig warm-up onto a thread-pool thread that runs +concurrently and calls `getenv()` constantly +([electron#53070](https://github.com/electron/electron/pull/53070)). The result +is a browser-process use-after-free about a second into startup — no window, no +GPU child involved, and the corruption surfaces wherever the next allocation +lands, which is why reports name unrelated frames (`gtk_widget_realize`, +libxcb-dri3, FontConfig/expat). Orca 1.4.199/1.4.200 shipped that runtime and +died on launch on Ubuntu + NVIDIA/X11 +([#20081](https://github.com/stablyai/orca/issues/20081)). + +Electron 43.7.0 fixes it by overriding `setenv`/`unsetenv`/`putenv`/`clearenv` +so a published `environ` is never freed, deferring to glibc on 2.41+ +([electron#53491](https://github.com/electron/electron/pull/53491), backported +to 42/43/44/45). **Do not downgrade Electron below 43.7.0, or move to another +line, without confirming that backport is in the target release** — +`config/scripts/electron-runtime-floor.test.ts` fails the suite if the pin drops +below the floor. Orca itself writes `process.env` during early startup +(`patchPackagedProcessPath`, `configureOrcaUserDataPathEnv`, +`hydrate-shell-path`), so it is a first-class trigger, not just a bystander. diff --git a/package.json b/package.json index 4f03d793aa6..6bf087b987f 100644 --- a/package.json +++ b/package.json @@ -240,7 +240,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dompurify": "3.4.14", - "electron": "43.6.0", + "electron": "43.7.0", "electron-builder": "^26.15.3", "electron-builder-squirrel-windows": "^26.15.3", "electron-vite": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7add63b397b..a7ea90a44bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,10 +127,10 @@ importers: version: 0.3.251(@anthropic-ai/sdk@0.122.0(zod@4.5.4))(@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@4.5.4))(zod@4.5.4) '@electron-toolkit/preload': specifier: ^3.0.2 - version: 3.0.2(electron@43.6.0(supports-color@7.2.0)) + version: 3.0.2(electron@43.7.0(supports-color@7.2.0)) '@electron-toolkit/utils': specifier: ^4.0.0 - version: 4.0.0(electron@43.6.0(supports-color@7.2.0)) + version: 4.0.0(electron@43.7.0(supports-color@7.2.0)) '@floating-ui/dom': specifier: 1.7.6 version: 1.7.6 @@ -224,7 +224,7 @@ importers: version: 2.1.14(@playwright/test@1.59.1)(zod@4.5.4) '@tailwindcss/vite': specifier: ^4.2.4 - version: 4.2.4(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)) + version: 4.2.4(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)) '@tanstack/react-virtual': specifier: ^3.14.10 version: 3.14.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -314,7 +314,7 @@ importers: version: 8.18.1 '@vitejs/plugin-react': specifier: ^5.2.0 - version: 5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)) + version: 5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)) '@xterm/addon-fit': specifier: 0.12.0-beta.300 version: 0.12.0-beta.300(@xterm/xterm@6.1.0-beta.303(patch_hash=1f36ce689bc50c703ae09aeda0e064f107e18e4a5ecba19e746fa5edc4b02ef4)) @@ -349,8 +349,8 @@ importers: specifier: 3.4.14 version: 3.4.14 electron: - specifier: 43.6.0 - version: 43.6.0(supports-color@7.2.0) + specifier: 43.7.0 + version: 43.7.0(supports-color@7.2.0) electron-builder: specifier: ^26.15.3 version: 26.15.3(electron-builder-squirrel-windows@26.15.3) @@ -359,7 +359,7 @@ importers: version: 26.15.3(dmg-builder@26.15.3) electron-vite: specifier: ^5.0.0 - version: 5.0.0(@swc/core@1.15.46)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)) + version: 5.0.0(@swc/core@1.15.46)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)) emoji-picker-react: specifier: ^4.19.1 version: 4.19.1(react@19.2.8) @@ -497,10 +497,10 @@ importers: version: 11.0.5 vite: specifier: npm:rolldown-vite@7.3.1 - version: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4) + version: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4) vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.8)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)) + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.8)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)) vscode-oniguruma: specifier: ^2.0.1 version: 2.0.1 @@ -4330,8 +4330,8 @@ packages: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@43.6.0: - resolution: {integrity: sha512-DqVKYV+FXheMSLTxcMQ+NCo78BDgpnToSyIzXctlUtbP3lRGEuoo1P+C2n/90rJ7TvHgzP0bpP9fbbXxp4noIg==} + electron@43.7.0: + resolution: {integrity: sha512-m98FUehwnoo6q2D9Mr+n602Natb2CRFeKD81GhTdJlKh/Iyud0R1X8hChjpaMBLSsIX9r2ZEFnz0sdIALiO9ZQ==} engines: {node: '>= 22.12.0'} hasBin: true @@ -7332,17 +7332,17 @@ snapshots: '@electron-internal/extract-zip@1.0.4': {} - '@electron-toolkit/preload@3.0.2(electron@43.6.0(supports-color@7.2.0))': + '@electron-toolkit/preload@3.0.2(electron@43.7.0(supports-color@7.2.0))': dependencies: - electron: 43.6.0(supports-color@7.2.0) + electron: 43.7.0(supports-color@7.2.0) '@electron-toolkit/tsconfig@2.0.0(@types/node@25.9.5)': dependencies: '@types/node': 25.9.5 - '@electron-toolkit/utils@4.0.0(electron@43.6.0(supports-color@7.2.0))': + '@electron-toolkit/utils@4.0.0(electron@43.7.0(supports-color@7.2.0))': dependencies: - electron: 43.6.0(supports-color@7.2.0) + electron: 43.7.0(supports-color@7.2.0) '@electron/asar@3.4.1': dependencies: @@ -7739,7 +7739,7 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.0.8 express: 5.2.1(supports-color@7.2.0) - express-rate-limit: 8.5.2(express@5.2.1) + express-rate-limit: 8.5.2(express@5.2.1(supports-color@7.2.0)) hono: 4.13.0 jose: 6.2.3 json-schema-typed: 8.0.2 @@ -7761,7 +7761,7 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.0.8 express: 5.2.1(supports-color@7.2.0) - express-rate-limit: 8.5.2(express@5.2.1) + express-rate-limit: 8.5.2(express@5.2.1(supports-color@7.2.0)) hono: 4.13.0 jose: 6.2.3 json-schema-typed: 8.0.2 @@ -9151,12 +9151,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - '@tailwindcss/vite@4.2.4(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))': + '@tailwindcss/vite@4.2.4(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4))': dependencies: '@tailwindcss/node': 4.2.4 '@tailwindcss/oxide': 4.2.4 tailwindcss: 4.2.4 - vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4) '@tanstack/react-virtual@3.14.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: @@ -9767,7 +9767,7 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-react@5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))': + '@vitejs/plugin-react@5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.7) @@ -9775,7 +9775,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4) transitivePeerDependencies: - supports-color @@ -9788,14 +9788,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.11(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))': + '@vitest/mocker@4.1.11(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.3(@types/node@25.9.5)(typescript@7.0.2) - vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4) '@vitest/pretty-format@4.1.11': dependencies: @@ -10662,7 +10662,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@5.0.0(@swc/core@1.15.46)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)): + electron-vite@5.0.0(@swc/core@1.15.46)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)): dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) @@ -10670,7 +10670,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4) optionalDependencies: '@swc/core': 1.15.46 transitivePeerDependencies: @@ -10688,7 +10688,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron@43.6.0(supports-color@7.2.0): + electron@43.7.0(supports-color@7.2.0): dependencies: '@electron-internal/extract-zip': 1.0.4 '@electron/get': 5.0.0(supports-color@7.2.0) @@ -10862,7 +10862,7 @@ snapshots: exponential-backoff@3.1.3: {} - express-rate-limit@8.5.2(express@5.2.1): + express-rate-limit@8.5.2(express@5.2.1(supports-color@7.2.0)): dependencies: express: 5.2.1(supports-color@7.2.0) ip-address: 10.4.0 @@ -13077,7 +13077,7 @@ snapshots: robust-predicates@3.0.3: {} - rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4): + rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4): dependencies: '@oxc-project/runtime': 0.101.0 fdir: 6.5.0(picomatch@4.0.4) @@ -13088,6 +13088,7 @@ snapshots: tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.9.5 + esbuild: 0.25.12 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.8.4 @@ -13717,10 +13718,10 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.8)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)): + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.11.8)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)) + '@vitest/mocker': 4.1.11(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -13737,7 +13738,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.8.4) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 30fdf0f16b9..4111f3708a0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,6 +10,7 @@ minimumReleaseAge: 4320 minimumReleaseAgeExclude: - pdfjs-dist@6.3.289 - zod@4.5.4 + - electron@43.7.0 shamefullyHoist: true # Orca always launches the user's own resolved Claude CLI via