Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
);
```
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum FuseV1Options {
LoadBrowserProcessSpecificV8Snapshot = 6,
GrantFileProtocolExtraPrivileges = 7,
WasmTrapHandlers = 8,
EnableDeviceBoundSessions = 9,
}

export type FuseV1Config<T = boolean> = {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
];
};

Expand Down
21 changes: 20 additions & 1 deletion test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -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<FuseState>) {
const cloned: any = { ...config };
for (const key of Object.keys(cloned).filter((k) => k !== 'version')) {
Expand Down
41 changes: 41 additions & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading