Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
export {};
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const chalk = require("chalk");
|
||||
const minimist = require("minimist");
|
||||
const path = require("path");
|
||||
const _1 = require(".");
|
||||
const config_1 = require("./config");
|
||||
const constants_1 = require("./constants");
|
||||
const mode = process.argv[2];
|
||||
const readHelpText = `electron-fuses read --app [path-to-app]`;
|
||||
const writeHelpText = `electron-fuses write --app [path-to-app] <...key=on/off>`;
|
||||
if (mode !== 'read' && mode !== 'write') {
|
||||
console.error('Invalid mode, check the usage below:');
|
||||
console.info(readHelpText);
|
||||
console.info(writeHelpText);
|
||||
process.exit(0);
|
||||
}
|
||||
function stringForState(state) {
|
||||
switch (state) {
|
||||
case constants_1.FuseState.ENABLE:
|
||||
return chalk.green('Enabled');
|
||||
case constants_1.FuseState.DISABLE:
|
||||
return chalk.red('Disabled');
|
||||
case constants_1.FuseState.INHERIT:
|
||||
return chalk.yellow('Inherited');
|
||||
case constants_1.FuseState.REMOVED:
|
||||
return chalk.strikethrough(chalk.red('Removed'));
|
||||
}
|
||||
}
|
||||
if (mode === 'read') {
|
||||
const argv = minimist(process.argv.slice(3), {
|
||||
string: ['app'],
|
||||
boolean: ['help'],
|
||||
});
|
||||
if (argv.help) {
|
||||
console.log(readHelpText);
|
||||
process.exit(0);
|
||||
}
|
||||
if (!argv.app) {
|
||||
console.error('--app argument is required');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Analyzing app:', chalk.cyan(path.basename(argv.app)));
|
||||
(0, _1.getCurrentFuseWire)(argv.app)
|
||||
.then((config) => {
|
||||
const { version, resetAdHocDarwinSignature, strictlyRequireAllFuses } = config, rest = __rest(config, ["version", "resetAdHocDarwinSignature", "strictlyRequireAllFuses"]);
|
||||
console.log(`Fuse Version: ${chalk.cyan(`v${version}`)}`);
|
||||
switch (config.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
for (const key of Object.keys(rest)) {
|
||||
console.log(` ${chalk.yellow(config_1.FuseV1Options[key])} is ${stringForState(rest[key])}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
else {
|
||||
const argv = minimist(process.argv.slice(3), {
|
||||
string: ['app'],
|
||||
boolean: ['help'],
|
||||
});
|
||||
if (argv.help) {
|
||||
console.log(writeHelpText);
|
||||
process.exit(0);
|
||||
}
|
||||
if (!argv.app) {
|
||||
console.error('--app argument is required');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Analyzing app:', chalk.cyan(path.basename(argv.app)));
|
||||
(0, _1.getCurrentFuseWire)(argv.app)
|
||||
.then((config) => {
|
||||
const { version, resetAdHocDarwinSignature } = config, rest = __rest(config, ["version", "resetAdHocDarwinSignature"]);
|
||||
console.log(`Fuse Version: ${chalk.cyan(`v${version}`)}`);
|
||||
const keyPairs = argv._ || [];
|
||||
for (const keyPair of keyPairs) {
|
||||
const [key, state] = keyPair.split('=');
|
||||
if (!key || !state) {
|
||||
console.error('Invalid fuse:', keyPair);
|
||||
console.error('Must be in the format FuseName=on/off');
|
||||
process.exit(1);
|
||||
}
|
||||
if (state !== 'on' && state !== 'off') {
|
||||
console.error('Invalid fuse state:', chalk.yellow(keyPair));
|
||||
console.error(`Fuses can only be set to the "${chalk.green('on')}" or "${chalk.red('off')}" state`);
|
||||
process.exit(1);
|
||||
}
|
||||
switch (config.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
const validFuseNames = Object.keys(config_1.FuseV1Options).filter((k) => !/^[0-9]+$/.test(k));
|
||||
if (!validFuseNames.includes(key)) {
|
||||
console.error('Invalid fuse name', chalk.yellow(key));
|
||||
console.error('Expected name to be one of', chalk.yellow(JSON.stringify(validFuseNames)));
|
||||
process.exit(1);
|
||||
}
|
||||
const currentState = config[config_1.FuseV1Options[key]];
|
||||
const newState = state === 'on' ? constants_1.FuseState.ENABLE : constants_1.FuseState.DISABLE;
|
||||
if (currentState === newState) {
|
||||
console.log(` ${chalk.yellow(key)} is already ${stringForState(currentState)} and will not be changed`);
|
||||
}
|
||||
else {
|
||||
console.log(` ${chalk.yellow(key)} is ${stringForState(currentState)} and will become ${stringForState(newState)}`);
|
||||
}
|
||||
config[config_1.FuseV1Options[key]] = newState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log('Writing to app:', chalk.cyan(path.basename(argv.app)));
|
||||
function adaptConfig(config) {
|
||||
const { version, resetAdHocDarwinSignature } = config, rest = __rest(config, ["version", "resetAdHocDarwinSignature"]);
|
||||
const fuseConfig = {
|
||||
version,
|
||||
resetAdHocDarwinSignature,
|
||||
};
|
||||
for (const key of Object.keys(rest)) {
|
||||
fuseConfig[key] = rest[key] === constants_1.FuseState.ENABLE;
|
||||
}
|
||||
return fuseConfig;
|
||||
}
|
||||
return (0, _1.flipFuses)(argv.app, adaptConfig(config));
|
||||
})
|
||||
.then(() => {
|
||||
console.log(chalk.green('Fuses written to disk'));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=bin.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+34
@@ -0,0 +1,34 @@
|
||||
export declare enum FuseVersion {
|
||||
V1 = "1"
|
||||
}
|
||||
/**
|
||||
* Maps config keys to their index in the fuse wire
|
||||
*/
|
||||
export declare enum FuseV1Options {
|
||||
RunAsNode = 0,
|
||||
EnableCookieEncryption = 1,
|
||||
EnableNodeOptionsEnvironmentVariable = 2,
|
||||
EnableNodeCliInspectArguments = 3,
|
||||
EnableEmbeddedAsarIntegrityValidation = 4,
|
||||
OnlyLoadAppFromAsar = 5,
|
||||
LoadBrowserProcessSpecificV8Snapshot = 6,
|
||||
GrantFileProtocolExtraPrivileges = 7
|
||||
}
|
||||
export declare type FuseV1Config<T = boolean> = {
|
||||
version: FuseVersion.V1;
|
||||
resetAdHocDarwinSignature?: boolean;
|
||||
/**
|
||||
* 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 declare type FuseConfig<T = boolean> = FuseV1Config<T>;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FuseV1Options = exports.FuseVersion = void 0;
|
||||
var FuseVersion;
|
||||
(function (FuseVersion) {
|
||||
FuseVersion["V1"] = "1";
|
||||
})(FuseVersion = exports.FuseVersion || (exports.FuseVersion = {}));
|
||||
/**
|
||||
* Maps config keys to their index in the fuse wire
|
||||
*/
|
||||
var FuseV1Options;
|
||||
(function (FuseV1Options) {
|
||||
FuseV1Options[FuseV1Options["RunAsNode"] = 0] = "RunAsNode";
|
||||
FuseV1Options[FuseV1Options["EnableCookieEncryption"] = 1] = "EnableCookieEncryption";
|
||||
FuseV1Options[FuseV1Options["EnableNodeOptionsEnvironmentVariable"] = 2] = "EnableNodeOptionsEnvironmentVariable";
|
||||
FuseV1Options[FuseV1Options["EnableNodeCliInspectArguments"] = 3] = "EnableNodeCliInspectArguments";
|
||||
FuseV1Options[FuseV1Options["EnableEmbeddedAsarIntegrityValidation"] = 4] = "EnableEmbeddedAsarIntegrityValidation";
|
||||
FuseV1Options[FuseV1Options["OnlyLoadAppFromAsar"] = 5] = "OnlyLoadAppFromAsar";
|
||||
FuseV1Options[FuseV1Options["LoadBrowserProcessSpecificV8Snapshot"] = 6] = "LoadBrowserProcessSpecificV8Snapshot";
|
||||
FuseV1Options[FuseV1Options["GrantFileProtocolExtraPrivileges"] = 7] = "GrantFileProtocolExtraPrivileges";
|
||||
})(FuseV1Options = exports.FuseV1Options || (exports.FuseV1Options = {}));
|
||||
//# sourceMappingURL=config.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;AAAA,IAAY,WAEX;AAFD,WAAY,WAAW;IACrB,uBAAQ,CAAA;AACV,CAAC,EAFW,WAAW,GAAX,mBAAW,KAAX,mBAAW,QAEtB;AAED;;GAEG;AACH,IAAY,aASX;AATD,WAAY,aAAa;IACvB,2DAAa,CAAA;IACb,qFAA0B,CAAA;IAC1B,iHAAwC,CAAA;IACxC,mGAAiC,CAAA;IACjC,mHAAyC,CAAA;IACzC,+EAAuB,CAAA;IACvB,iHAAwC,CAAA;IACxC,yGAAoC,CAAA;AACtC,CAAC,EATW,aAAa,GAAb,qBAAa,KAAb,qBAAa,QASxB"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export declare const SENTINEL = "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX";
|
||||
export declare enum FuseState {
|
||||
DISABLE = 48,
|
||||
ENABLE = 49,
|
||||
REMOVED = 114,
|
||||
INHERIT = 144
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FuseState = exports.SENTINEL = void 0;
|
||||
exports.SENTINEL = 'dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX';
|
||||
var FuseState;
|
||||
(function (FuseState) {
|
||||
FuseState[FuseState["DISABLE"] = 48] = "DISABLE";
|
||||
FuseState[FuseState["ENABLE"] = 49] = "ENABLE";
|
||||
FuseState[FuseState["REMOVED"] = 114] = "REMOVED";
|
||||
FuseState[FuseState["INHERIT"] = 144] = "INHERIT";
|
||||
})(FuseState = exports.FuseState || (exports.FuseState = {}));
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,QAAQ,GAAG,kCAAkC,CAAC;AAE3D,IAAY,SAKX;AALD,WAAY,SAAS;IACnB,gDAAc,CAAA;IACd,8CAAa,CAAA;IACb,iDAAc,CAAA;IACd,iDAAc,CAAA;AAChB,CAAC,EALW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAKpB"}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { FuseConfig } from './config';
|
||||
import { FuseState } from './constants';
|
||||
export * from './config';
|
||||
export declare const getCurrentFuseWire: (pathToElectron: string) => Promise<FuseConfig<FuseState>>;
|
||||
export declare const flipFuses: (pathToElectron: string, fuseConfig: FuseConfig) => Promise<number>;
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.flipFuses = exports.getCurrentFuseWire = void 0;
|
||||
const cp = require("child_process");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const config_1 = require("./config");
|
||||
const constants_1 = require("./constants");
|
||||
__exportStar(require("./config"), exports);
|
||||
const state = (b) => b === undefined ? constants_1.FuseState.INHERIT : b ? constants_1.FuseState.ENABLE : constants_1.FuseState.DISABLE;
|
||||
const buildFuseV1Wire = (config, wireLength) => {
|
||||
const { version } = config, nonVersionConfig = __rest(config, ["version"]);
|
||||
const badFuseOption = Object.keys(nonVersionConfig).find((fuseOption) => parseInt(fuseOption, 10) >= wireLength);
|
||||
if (badFuseOption !== undefined) {
|
||||
throw new Error(`Trying to configure ${config_1.FuseV1Options[badFuseOption]} but the fuse wire in this version of Electron is not long enough`);
|
||||
}
|
||||
return [
|
||||
state(config[config_1.FuseV1Options.RunAsNode]),
|
||||
state(config[config_1.FuseV1Options.EnableCookieEncryption]),
|
||||
state(config[config_1.FuseV1Options.EnableNodeOptionsEnvironmentVariable]),
|
||||
state(config[config_1.FuseV1Options.EnableNodeCliInspectArguments]),
|
||||
state(config[config_1.FuseV1Options.EnableEmbeddedAsarIntegrityValidation]),
|
||||
state(config[config_1.FuseV1Options.OnlyLoadAppFromAsar]),
|
||||
state(config[config_1.FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]),
|
||||
state(config[config_1.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(constants_1.SENTINEL);
|
||||
const lastSentinel = electron.lastIndexOf(constants_1.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 + constants_1.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 === constants_1.FuseState.REMOVED && newState !== constants_1.FuseState.INHERIT) {
|
||||
console.warn(`Overriding fuse "${fuseNamer(i)}" that has been marked as removed, setting this fuse is a noop`);
|
||||
}
|
||||
if (newState === constants_1.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;
|
||||
};
|
||||
const getCurrentFuseWire = async (pathToElectron) => {
|
||||
const fuseFilePath = pathToFuseFile(pathToElectron);
|
||||
const electron = await fs.readFile(fuseFilePath);
|
||||
const fuseWirePosition = electron.indexOf(constants_1.SENTINEL) + constants_1.SENTINEL.length;
|
||||
if (fuseWirePosition - constants_1.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 config_1.FuseVersion.V1:
|
||||
fuseConfig[i] = currentState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fuseConfig;
|
||||
};
|
||||
exports.getCurrentFuseWire = getCurrentFuseWire;
|
||||
const flipFuses = async (pathToElectron, fuseConfig) => {
|
||||
let numSentinels;
|
||||
switch (fuseConfig.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
numSentinels = await setFuseWire(pathToElectron, fuseConfig.version, fuseConfig.strictlyRequireAllFuses || false, buildFuseV1Wire.bind(null, fuseConfig), (i) => config_1.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;
|
||||
};
|
||||
exports.flipFuses = flipFuses;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user