@electron/fuses
Version:
Flip Electron Fuses and customize your packaged build of Electron
126 lines • 6.5 kB
JavaScript
import cp from 'node:child_process';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { FuseV1Options, FuseVersion } from './config.js';
import { FuseState, SENTINEL } from './constants.js';
export * from './config.js';
export { FuseState } from './constants.js';
const state = (b) => b === undefined ? FuseState.INHERIT : b ? FuseState.ENABLE : FuseState.DISABLE;
const buildFuseV1Wire = (config, wireLength) => {
const { version, ...nonVersionConfig } = config;
const badFuseOption = Object.keys(nonVersionConfig).find((fuseOption) => parseInt(fuseOption, 10) >= wireLength);
if (badFuseOption !== undefined) {
throw new Error(`Trying to configure ${FuseV1Options[badFuseOption]} but the fuse wire in this version of Electron is not long enough`);
}
return [
state(config[FuseV1Options.RunAsNode]),
state(config[FuseV1Options.EnableCookieEncryption]),
state(config[FuseV1Options.EnableNodeOptionsEnvironmentVariable]),
state(config[FuseV1Options.EnableNodeCliInspectArguments]),
state(config[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]),
state(config[FuseV1Options.OnlyLoadAppFromAsar]),
state(config[FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]),
state(config[FuseV1Options.GrantFileProtocolExtraPrivileges]),
];
};
const pathToFuseFile = (pathToElectron) => {
if (pathToElectron.endsWith('.app')) {
return path.resolve(pathToElectron, 'Contents', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
}
if (pathToElectron.includes('.app')) {
return path.resolve(pathToElectron, '..', '..', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
}
return pathToElectron;
};
const setFuseWire = async (pathToElectron, fuseVersion, strictlyRequireAllFuses, fuseWireBuilder, fuseNamer) => {
const fuseFilePath = pathToFuseFile(pathToElectron);
const electron = await fs.readFile(fuseFilePath);
const firstSentinel = electron.indexOf(SENTINEL);
const lastSentinel = electron.lastIndexOf(SENTINEL);
// If the last sentinel is different to the first sentinel we are probably in a universal build
// We should flip the fuses in both sentinels to affect both slices of the universal binary
const sentinels = firstSentinel === lastSentinel ? [firstSentinel] : [firstSentinel, lastSentinel];
for (const indexOfSentinel of sentinels) {
if (indexOfSentinel === -1) {
throw new Error('Could not find sentinel in the provided Electron binary, fuses are only supported in Electron 12 and higher');
}
const fuseWirePosition = indexOfSentinel + SENTINEL.length;
const fuseWireVersion = electron[fuseWirePosition];
if (parseInt(fuseVersion, 10) !== fuseWireVersion) {
throw new Error(`Provided fuse wire version "${parseInt(fuseVersion, 10)}" does not match watch was found in the binary "${fuseWireVersion}". You should update your usage of @electron/fuses.`);
}
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];
const newState = wire[i];
if (currentState === FuseState.REMOVED && newState !== FuseState.INHERIT) {
console.warn(`Overriding fuse "${fuseNamer(i)}" that has been marked as removed, setting this fuse is a noop`);
}
if (newState === FuseState.INHERIT) {
if (strictlyRequireAllFuses) {
throw new Error(`strictlyRequireAllFuses: Missing explicit configuration for fuse ${fuseNamer(i)}`);
}
continue;
}
electron[idx] = newState;
}
}
await fs.writeFile(fuseFilePath, electron);
return sentinels.length;
};
export const getCurrentFuseWire = async (pathToElectron) => {
const fuseFilePath = pathToFuseFile(pathToElectron);
const electron = await fs.readFile(fuseFilePath);
const fuseWirePosition = electron.indexOf(SENTINEL) + SENTINEL.length;
if (fuseWirePosition - SENTINEL.length === -1) {
throw new Error('Could not find sentinel in the provided Electron binary, fuses are only supported in Electron 12 and higher');
}
const fuseWireVersion = electron[fuseWirePosition];
const fuseWireLength = electron[fuseWirePosition + 1];
const fuseConfig = {
version: `${fuseWireVersion}`,
};
for (let i = 0; i < fuseWireLength; i++) {
const idx = fuseWirePosition + 2 + i;
const currentState = electron[idx];
switch (fuseConfig.version) {
case FuseVersion.V1:
fuseConfig[i] = currentState;
break;
}
}
return fuseConfig;
};
export const flipFuses = async (pathToElectron, fuseConfig) => {
let numSentinels;
switch (fuseConfig.version) {
case FuseVersion.V1:
numSentinels = await setFuseWire(pathToElectron, fuseConfig.version, fuseConfig.strictlyRequireAllFuses || false, buildFuseV1Wire.bind(null, fuseConfig), (i) => FuseV1Options[i]);
break;
default:
throw new Error(`Unsupported fuse version number: ${fuseConfig.version}`);
}
// Reset the ad-hoc signature on macOS, should only be done for arm64 apps
if (fuseConfig.resetAdHocDarwinSignature && pathToElectron.includes('.app')) {
const pathToApp = `${pathToElectron.split('.app')[0]}.app`;
const result = cp.spawnSync('codesign', [
'--sign',
'-',
'--force',
'--preserve-metadata=entitlements,requirements,flags,runtime',
'--deep',
pathToApp,
]);
if (result.status !== 0) {
console.error(result.stderr.toString());
throw new Error(`Ad-hoc codesign failed with status: ${result.status}`);
}
}
return numSentinels;
};
//# sourceMappingURL=index.js.map