From b479798f427c37a981d55235a4d75f5927a5e066 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Jan 2024 15:56:04 +0000 Subject: [PATCH] Burn Node-related Electron fuses as a proactive hardening measure (#1412) Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: Valere --- .github/workflows/build_and_test.yaml | 22 +++++++++++--- .github/workflows/build_macos.yaml | 8 ++--- electron-builder.js | 43 +++++++++++++++++++++++++-- package.json | 3 +- yarn.lock | 13 ++++++-- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 92b906a..1621ca4 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -106,17 +106,22 @@ jobs: - name: macOS Universal os: macos artifact: macos - executable: "/Volumes/Element/Element.app/Contents/MacOS/Element" - prepare_cmd: "hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element" + executable: "/Users/runner/Applications/Element.app/Contents/MacOS/Element" + # We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is + # read-only and thus would not allow us to override the fuses as is required for Playwright. + prepare_cmd: | + hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element && + rsync -a /Volumes/Element/Element.app ~/Applications/ && + hdiutil detach /Volumes/Element - name: "Linux (amd64) (sqlcipher: system)" os: ubuntu artifact: linux-amd64-sqlcipher-system - executable: "element-desktop" + executable: "/opt/Element/element-desktop" prepare_cmd: "sudo apt install ./dist/*.deb" - name: "Linux (amd64) (sqlcipher: static)" os: ubuntu artifact: linux-amd64-sqlcipher-static - executable: "element-desktop" + executable: "/opt/Element/element-desktop" prepare_cmd: "sudo apt install ./dist/*.deb" - name: Windows (x86) os: windows @@ -147,6 +152,15 @@ jobs: run: ${{ matrix.prepare_cmd }} if: matrix.prepare_cmd + # We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires + # it to to be enabled to test Electron apps, so turn it back on. + - name: Set EnableNodeCliInspectArguments fuse enabled + run: $RUN_AS npx @electron/fuses write --app ${{ matrix.executable }} EnableNodeCliInspectArguments=on + shell: bash + env: + # We need sudo on Linux as it is installed in /opt/ + RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }} + - name: Run tests uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1 timeout-minutes: 5 diff --git a/.github/workflows/build_macos.yaml b/.github/workflows/build_macos.yaml index 5330d11..522725d 100644 --- a/.github/workflows/build_macos.yaml +++ b/.github/workflows/build_macos.yaml @@ -88,10 +88,10 @@ jobs: - name: Check app was signed & notarised successfully if: inputs.sign != '' run: | - hdiutil attach dist/*.dmg - codesign -dv --verbose=4 /Volumes/Element*/*.app - spctl -a -vvv -t install /Volumes/Element*/*.app - hdiutil detach /Volumes/Element* + hdiutil attach dist/*.dmg -mountpoint /Volumes/Element + codesign -dv --verbose=4 /Volumes/Element/*.app + spctl -a -vvv -t install /Volumes/Element/*.app + hdiutil detach /Volumes/Element - name: "[Unsigned] Build App" if: inputs.sign == '' diff --git a/electron-builder.js b/electron-builder.js index 1edef2b..7b4fb6e 100644 --- a/electron-builder.js +++ b/electron-builder.js @@ -1,5 +1,8 @@ const os = require("os"); const fs = require("fs"); +const path = require("path"); +const Arch = require("electron-builder").Arch; +const { flipFuses, FuseVersion, FuseV1Options } = require("@electron/fuses"); // Typescript conversion blocked on https://github.com/electron-userland/electron-builder/issues/7775 @@ -21,7 +24,6 @@ const fs = require("fs"); */ const NIGHTLY_APP_ID = "im.riot.nightly"; -const NIGHTLY_APP_NAME = "element-desktop-nightly"; const NIGHTLY_DEB_NAME = "element-nightly"; const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); @@ -33,6 +35,43 @@ const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); const config = { appId: "im.riot.app", asarUnpack: "**/*.node", + afterPack: async (context) => { + if (context.electronPlatformName !== "darwin" || context.arch === Arch.universal) { + // Burn in electron fuses for proactive security hardening. + // On macOS, we only do this for the universal package, as the constituent arm64 and amd64 packages are embedded within. + const ext = { + darwin: ".app", + win32: ".exe", + linux: "", + }[context.electronPlatformName]; + + let executableName = context.packager.appInfo.productFilename; + if (context.electronPlatformName === "linux") { + // Linux uses the package name as the executable name + executableName = context.packager.appInfo.name; + } + + const electronBinaryPath = path.join(context.appOutDir, `${executableName}${ext}`); + console.log(`Flipping fuses for: ${electronBinaryPath}`); + + await flipFuses(electronBinaryPath, { + version: FuseVersion.V1, + resetAdHocDarwinSignature: context.electronPlatformName === "darwin" && context.arch === Arch.universal, + + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + + [FuseV1Options.RunAsNode]: false, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + + // Mac app crashes on arm for us when `LoadBrowserProcessSpecificV8Snapshot` is enabled + [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: false, + // https://github.com/electron/fuses/issues/7 + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: false, + }); + } + }, files: [ "package.json", { @@ -137,7 +176,7 @@ if (process.env.ED_NIGHTLY) { config.appId = NIGHTLY_APP_ID; config.extraMetadata.productName += " Nightly"; - config.extraMetadata.name = NIGHTLY_APP_NAME; + config.extraMetadata.name += "-nightly"; config.extraMetadata.description += " (nightly unstable build)"; config.deb.fpm.push("--name", NIGHTLY_DEB_NAME); diff --git a/package.json b/package.json index 70a41bf..0e2d51c 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,8 @@ "tar": "^6.1.2", "ts-jest": "^29.0.0", "ts-node": "^10.9.1", - "typescript": "5.3.3" + "typescript": "5.3.3", + "@electron/fuses": "^1.7.0" }, "hakDependencies": { "matrix-seshat": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index eefa255..1672f0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1148,6 +1148,15 @@ glob "^7.1.6" minimatch "^3.0.4" +"@electron/fuses@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@electron/fuses/-/fuses-1.7.0.tgz#0800d5404fffe5683705297990fea089d49811a2" + integrity sha512-mfhLoZGQdqrSU/SeOFBs6r+D7g1tYiVs2C/hh7t3NFQ0chcXGoWrrad17rCQL1ImNJuCXs4cu23YBj5CAnj5SA== + dependencies: + chalk "^4.1.1" + fs-extra "^9.0.1" + minimist "^1.2.5" + "@electron/get@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" @@ -3021,7 +3030,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5761,7 +5770,7 @@ minimatch@^5.0.1, minimatch@^5.1.0, minimatch@^5.1.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==