diff --git a/README.md b/README.md index 4151407..2852927 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,24 @@ await flipFuses( }, ); ``` + +### New Fuses + +If you want to ensure you provide a config for every fuse, even newly added fuses during Electron upgrades +you can set the `strictlyRequireAllFuses` option to `true`. This will hard fail the build if you are on +a version of `@electron/fuses` that doesn't have configuration options for every fuse in the Electron binary +you are targetting or if you don't provide a configuration for a specific fuse present in the Electron binary +you are targetting. + +```typescript +import { flipFuses, FuseVersion, FuseV1Options } from '@electron/fuses'; + +await flipFuses( + require('electron'), + { + version: FuseVersion.V1, + strictlyRequireAllFuses: true, + [FuseV1Options.RunAsNode]: false, + }, +); +``` diff --git a/src/config.ts b/src/config.ts index 355c8ad..73ba154 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,6 +19,18 @@ export enum FuseV1Options { export type FuseV1Config = { version: FuseVersion.V1; resetAdHocDarwinSignature?: boolean; -} & Partial>; + /** + * Ensures that all fuses in the fuse wire being set have been defined to a set value + * by the provided config. Set this to true to ensure you don't accidentally miss a + * fuse being added in future Electron upgrades. + * + * This option may default to "true" in a future version of @electron/fuses but currently + * defaults to "false" + */ + strictlyRequireAllFuses?: boolean; +} & ( + | (Partial> & { strictlyRequireAllFuses?: false | undefined }) + | (Record & { strictlyRequireAllFuses: true }) +); export type FuseConfig = FuseV1Config; diff --git a/src/index.ts b/src/index.ts index 427d9f1..1762f33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,6 +60,7 @@ const pathToFuseFile = (pathToElectron: string) => { const setFuseWire = async ( pathToElectron: string, fuseVersion: FuseVersion, + strictlyRequireAllFuses: boolean, fuseWireBuilder: (wireLength: number) => FuseState[], fuseNamer: (index: number) => string, ) => { @@ -94,6 +95,11 @@ const setFuseWire = async ( const fuseWireLength = electron[fuseWirePosition + 1]; const wire = fuseWireBuilder(fuseWireLength).slice(0, fuseWireLength); + if (wire.length < fuseWireLength && strictlyRequireAllFuses) { + throw new Error( + `strictlyRequireAllFuses: The fuse wire in the Electron binary has ${fuseWireLength} fuses but you only provided a config for ${wire.length} fuses, you may need to update @electron/fuses or provide additional fuse settings`, + ); + } for (let i = 0; i < wire.length; i++) { const idx = fuseWirePosition + 2 + i; const currentState = electron[idx]; @@ -106,7 +112,14 @@ const setFuseWire = async ( )}" that has been marked as removed, setting this fuse is a noop`, ); } - if (newState === FuseState.INHERIT) continue; + if (newState === FuseState.INHERIT) { + if (strictlyRequireAllFuses) { + throw new Error( + `strictlyRequireAllFuses: Missing explicit configuration for fuse ${fuseNamer(i)}`, + ); + } + continue; + } electron[idx] = newState; } } @@ -158,6 +171,7 @@ export const flipFuses = async ( numSentinels = await setFuseWire( pathToElectron, fuseConfig.version, + fuseConfig.strictlyRequireAllFuses || false, buildFuseV1Wire.bind(null, fuseConfig), (i) => FuseV1Options[i], ); diff --git a/test/index.spec.ts b/test/index.spec.ts index 7179f80..99c2de5 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -104,4 +104,47 @@ describe('flipFuses()', () => { expect(sentinels).toEqual(2); }); } + + describe('strictlyRequireAllFuses', () => { + it('should fail when missing fuse configuration', async () => { + const electronPath = await getElectronLocally('20.0.0', 'darwin', 'x64'); + await expect( + // @ts-expect-error strictlyRequireAllFuses is actually type safe, so we have to _really_ try here + flipFuses(electronPath, { + version: FuseVersion.V1, + strictlyRequireAllFuses: true, + [FuseV1Options.EnableCookieEncryption]: true, + }), + ).rejects.toMatchInlineSnapshot( + `[Error: strictlyRequireAllFuses: Missing explicit configuration for fuse RunAsNode]`, + ); + // Doesn't actually flip any fuses + expect( + (await getCurrentFuseWire(electronPath))[FuseV1Options.EnableCookieEncryption], + ).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('29.0.0', 'darwin', 'x64'); + await expect( + flipFuses(electronPath, { + version: FuseVersion.V1, + strictlyRequireAllFuses: true, + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, + [FuseV1Options.EnableNodeCliInspectArguments]: true, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: true, + [FuseV1Options.GrantFileProtocolExtraPrivileges]: true, + [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: true, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + [FuseV1Options.RunAsNode]: true, + }), + ).resolves.toMatchInlineSnapshot(`1`); + // Actually flips a fuse + expect((await getCurrentFuseWire(electronPath))[FuseV1Options.EnableCookieEncryption]).toEqual( + FuseState.ENABLE, + ); + }); });