From d7bef01f0d98503edb3a56566c02a1ca42edef12 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 18 Sep 2026 00:20:23 -0700 Subject: [PATCH] feat: add FuseV1Options.EnableDeviceBoundSessions Adds the option for the deviceBoundSessions fuse, which enables Device Bound Session Credentials in Electron. Binaries whose fuse wire is shorter ignore it unless it is explicitly configured. --- README.md | 1 + src/config.ts | 1 + src/index.ts | 1 + test/helpers.ts | 21 ++++++++++++++++++++- test/index.spec.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4179d46..4368b10 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ await flipFuses( [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: true, // Loads V8 Snapshot from `browser_v8_context_snapshot.bin` for the browser process [FuseV1Options.GrantFileProtocolExtraPrivileges]: true, // Grants the file protocol extra privileges [FuseV1Options.WasmTrapHandlers]: true, // Enables V8 signal handlers to trap Out of Bounds memory access from WebAssembly + [FuseV1Options.EnableDeviceBoundSessions]: true, // Enables Device Bound Session Credentials (DBSC), which bind sessions to hardware-backed keys }, ); ``` diff --git a/src/config.ts b/src/config.ts index 75d6d37..9e895cc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,6 +15,7 @@ export enum FuseV1Options { LoadBrowserProcessSpecificV8Snapshot = 6, GrantFileProtocolExtraPrivileges = 7, WasmTrapHandlers = 8, + EnableDeviceBoundSessions = 9, } export type FuseV1Config = { diff --git a/src/index.ts b/src/index.ts index 35fbd27..3d391ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,6 +38,7 @@ const buildFuseV1Wire = (config: FuseV1Config, wireLength: number) => { state(config[FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]), state(config[FuseV1Options.GrantFileProtocolExtraPrivileges]), state(config[FuseV1Options.WasmTrapHandlers]), + state(config[FuseV1Options.EnableDeviceBoundSessions]), ]; }; diff --git a/test/helpers.ts b/test/helpers.ts index 0a30139..423117a 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -5,7 +5,7 @@ import os from 'node:os'; import path from 'node:path'; import { type FuseConfig, FuseV1Options } from '../src/index.js'; -import { FuseState } from '../src/constants.js'; +import { FuseState, SENTINEL } from '../src/constants.js'; export const supportedPlatforms = [ ['darwin', 'x64'], @@ -46,6 +46,25 @@ export async function getElectronLocally(version: string, platform: string, arch } } +/** + * Writes a stand-in for an Electron binary with a fuse wire of the given length, every fuse disabled. This makes it + * possible to test fuses that no released version of Electron has yet. + */ +export async function getFakeElectronWithFuseWire(wireLength: number) { + const tmpDir = await getTmpDir(); + const electronPath = path.resolve(tmpDir, 'electron'); + await fs.writeFile( + electronPath, + Buffer.concat([ + Buffer.from('not really electron'), + Buffer.from(SENTINEL), + Buffer.from([1, wireLength]), + Buffer.alloc(wireLength, FuseState.DISABLE), + ]), + ); + return electronPath; +} + export function readableFuseWire(config: FuseConfig) { const cloned: any = { ...config }; for (const key of Object.keys(cloned).filter((k) => k !== 'version')) { diff --git a/test/index.spec.ts b/test/index.spec.ts index 5476afb..729ad04 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -7,6 +7,7 @@ import { FuseState } from '../src/constants.js'; import { flipFuses, FuseV1Options, FuseVersion, getCurrentFuseWire } from '../src/index.js'; import { getElectronLocally, + getFakeElectronWithFuseWire, getTmpDir, readableFuseWire, supportedPlatforms, @@ -126,10 +127,50 @@ describe('flipFuses()', () => { }); }); + describe('fuses newer than released versions of Electron', () => { + it('should flip EnableDeviceBoundSessions when the fuse wire has room for it', async () => { + const electronPath = await getFakeElectronWithFuseWire(10); + await expect( + flipFuses(electronPath, { + version: FuseVersion.V1, + [FuseV1Options.EnableDeviceBoundSessions]: true, + }), + ).resolves.toEqual(1); + const wire = await getCurrentFuseWire(electronPath); + expect(wire[FuseV1Options.EnableDeviceBoundSessions]).toEqual(FuseState.ENABLE); + expect(wire[FuseV1Options.WasmTrapHandlers]).toEqual(FuseState.DISABLE); + }); + + it('should refuse to flip EnableDeviceBoundSessions when the fuse wire is too short for it', async () => { + const electronPath = await getFakeElectronWithFuseWire(9); + await expect( + flipFuses(electronPath, { + version: FuseVersion.V1, + [FuseV1Options.EnableDeviceBoundSessions]: true, + }), + ).rejects.toThrow( + 'Trying to configure EnableDeviceBoundSessions but the fuse wire in this version of Electron is not long enough', + ); + }); + + it('should leave EnableDeviceBoundSessions alone when it is not configured', async () => { + const electronPath = await getFakeElectronWithFuseWire(10); + await flipFuses(electronPath, { + version: FuseVersion.V1, + [FuseV1Options.WasmTrapHandlers]: true, + }); + const wire = await getCurrentFuseWire(electronPath); + expect(wire[FuseV1Options.WasmTrapHandlers]).toEqual(FuseState.ENABLE); + expect(wire[FuseV1Options.EnableDeviceBoundSessions]).toEqual(FuseState.DISABLE); + }); + }); + // This test may have to be updated as we add new fuses, update the Electron version and add a new config for the fuse wire it('should succeed when all fuse configurations are provided', async () => { const electronPath = await getElectronLocally('41.0.0-beta.4', 'darwin', 'x64'); await expect( + // @ts-expect-error EnableDeviceBoundSessions is not in a released Electron yet, so this config cannot set it. + // Once it is, update the version above and add it here. flipFuses(electronPath, { version: FuseVersion.V1, strictlyRequireAllFuses: true,