Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add strict mode to hard fail when new fuses added without explicit config #38

Merged
merged 2 commits into from
Mar 25, 2024
Merged
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
);
```
14 changes: 13 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ export enum FuseV1Options {
export type FuseV1Config<T = boolean> = {
version: FuseVersion.V1;
resetAdHocDarwinSignature?: boolean;
} & Partial<Record<FuseV1Options, T>>;
/**
* 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<Record<FuseV1Options, T>> & { strictlyRequireAllFuses?: false | undefined })
| (Record<FuseV1Options, T> & { strictlyRequireAllFuses: true })
);

export type FuseConfig<T = boolean> = FuseV1Config<T>;
16 changes: 15 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) => {
Expand Down Expand Up @@ -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];
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -158,6 +171,7 @@ export const flipFuses = async (
numSentinels = await setFuseWire(
pathToElectron,
fuseConfig.version,
fuseConfig.strictlyRequireAllFuses || false,
buildFuseV1Wire.bind(null, fuseConfig),
(i) => FuseV1Options[i],
);
Expand Down
43 changes: 43 additions & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
});
});