feat(electron): sign and notarize mac release builds (#1171)

* feat(electron): sign and notarize mac release builds when Apple credentials are present

* fix(electron): work around codesign PID parsing for digit-leading app name in @electron/notarize

codesign interprets a bare '5chan.app' argument as process ID 5, so
@electron/notarize 2.5.0's pre-upload signature check fails with 'No such
process'. Backport the './' basename prefix from electron/notarize#245 as
a yarn patch until forge depends on notarize >= 3.x.

* docs(agent-playbooks): record codesign PID parsing surprise for digit-leading app name

* fix(ci): keep mac signing env consistent with certificate availability
This commit is contained in:
Tommaso Casaburi
2026-06-13 15:26:31 +07:00
committed by GitHub
parent 3484963a59
commit 85b782e934
8 changed files with 115 additions and 2 deletions
+26
View File
@@ -156,9 +156,35 @@ jobs:
run: node electron/download-ipfs && sudo chmod +x bin/mac/ipfs
- name: Build React app
run: CI='' NODE_ENV=production yarn build
# Import the Developer ID Application certificate into a throwaway keychain so
# forge can sign and notarize. Skipped (build stays unsigned) if secrets are absent.
- name: Import code signing certificate
env:
APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }}
APPLE_CERT_P12_PASSWORD: ${{ secrets.APPLE_CERT_P12_PASSWORD }}
run: |
if [ -z "$APPLE_CERT_P12_BASE64" ]; then
echo "No signing certificate configured, building unsigned"
exit 0
fi
KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db"
KEYCHAIN_PASSWORD=$(uuidgen)
echo "$APPLE_CERT_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$RUNNER_TEMP/cert.p12" -P "$APPLE_CERT_P12_PASSWORD" -A -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
rm "$RUNNER_TEMP/cert.p12"
- name: Build Electron app for Mac
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
# Pass signing credentials only when the certificate was imported above, so
# forge's signing gate stays consistent with the keychain state.
APPLE_ID: ${{ secrets.APPLE_CERT_P12_BASE64 != '' && secrets.APPLE_ID || '' }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_CERT_P12_BASE64 != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD || '' }}
APPLE_TEAM_ID: ${{ secrets.APPLE_CERT_P12_BASE64 != '' && secrets.APPLE_TEAM_ID || '' }}
run: |
if [ "${{ matrix.arch }}" = "arm64" ]; then
yarn electron:build:mac:arm64
@@ -0,0 +1,21 @@
diff --git a/lib/check-signature.js b/lib/check-signature.js
index 324568af71bcc4372c9f959131ecd24122848c86..12f07a866e2c2b049abc222d2c11145b60f0fd72 100644
--- a/lib/check-signature.js
+++ b/lib/check-signature.js
@@ -41,14 +41,14 @@ const spawn_1 = require("./spawn");
const debug_1 = __importDefault(require("debug"));
const d = (0, debug_1.default)('electron-notarize');
const codesignDisplay = (opts) => __awaiter(void 0, void 0, void 0, function* () {
- const result = yield (0, spawn_1.spawn)('codesign', ['-dv', '-vvvv', '--deep', path.basename(opts.appPath)], {
+ const result = yield (0, spawn_1.spawn)('codesign', ['-dv', '-vvvv', '--deep', `./${path.basename(opts.appPath)}`], {
cwd: path.dirname(opts.appPath),
});
return result;
});
const codesign = (opts) => __awaiter(void 0, void 0, void 0, function* () {
d('attempting to check codesign of app:', opts.appPath);
- const result = yield (0, spawn_1.spawn)('codesign', ['-vvv', '--deep', '--strict', path.basename(opts.appPath)], {
+ const result = yield (0, spawn_1.spawn)('codesign', ['-vvv', '--deep', '--strict', `./${path.basename(opts.appPath)}`], {
cwd: path.dirname(opts.appPath),
});
return result;
+10
View File
@@ -137,3 +137,13 @@ If uncertain, ask the developer before adding an entry.
- **Impact:** Agents can silently inherit invalid or weak model settings, leading to broken subagent runs or degraded implementation quality.
- **Mitigation:** Keep `.cursor` agent configs on Cursor-supported models only, never use `composer-2` in `.claude`, and standardize `.codex/agents/*.toml` on `gpt-5.4` unless a contributor explicitly requests an override.
- **Status:** confirmed
### codesign parses "5chan.app" as process ID 5
- **Date:** 2026-06-12
- **Observed by:** contributor + Claude
- **Context:** Running the first signed + notarized mac Electron build (`yarn electron:build:mac:arm64` with Apple credentials set)
- **What was surprising:** `@electron/notarize` 2.x runs its pre-upload signature check as `codesign -dv 5chan.app` from the bundle's parent directory, and `codesign` accepts a process ID in place of a path — so it parses the digit-leading basename as PID 5 and fails with `5chan.app: No such process` even though the app is signed correctly.
- **Impact:** Notarization aborts after a successful signing pass; the error message looks like a signing failure and invites debugging the certificate/keychain instead of the real cause. Any tool that shells out to `codesign` with a bare relative path can hit this because the app is literally named `5chan`.
- **Mitigation:** Keep the yarn patch `.yarn/patches/@electron-notarize-npm-2.5.0-*.patch` (backport of electron/notarize#245, prefixes the basename with `./`) until electron-forge depends on `@electron/notarize` >= 3.x. When invoking `codesign` manually on the app bundle, always use an absolute or `./`-prefixed path.
- **Status:** confirmed
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Required by Electron/V8 JIT under the hardened runtime -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<!-- Required to load native modules (better-sqlite3) under the hardened runtime -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+20
View File
@@ -1,5 +1,9 @@
import { downloadIpfsClients } from './electron/before-pack.js';
// Sign and notarize the mac app only when Apple credentials are present (CI release
// builds); local builds without the certificate stay unsigned and keep working.
const shouldSignMac = process.platform === 'darwin' && !!process.env.APPLE_ID && !!process.env.APPLE_APP_SPECIFIC_PASSWORD && !!process.env.APPLE_TEAM_ID;
const config = {
packagerConfig: {
name: '5chan',
@@ -43,6 +47,22 @@ const config = {
/node_modules\/\.bin/,
/node_modules\/\.cache/,
],
...(shouldSignMac && {
osxSign: {
// identity is auto-discovered from the keychain (the only Developer ID
// Application identity present, both locally and in the CI temp keychain)
optionsForFile: () => ({
hardenedRuntime: true,
entitlements: './electron/entitlements.plist',
}),
},
osxNotarize: {
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
teamId: process.env.APPLE_TEAM_ID,
},
}),
},
rebuildConfig: {
+2 -1
View File
@@ -242,7 +242,8 @@
"yaml@npm:^1.10.0": "1.10.3",
"yaml@npm:^1.10.2": "1.10.3",
"yaml@npm:^2.8.2": "2.8.3",
"use-sync-external-store": "1.6.0"
"use-sync-external-store": "1.6.0",
"@electron/notarize@npm:^2.1.0": "patch:@electron/notarize@npm%3A2.5.0#~/.yarn/patches/@electron-notarize-npm-2.5.0-b15dc30c99.patch"
},
"main": "electron/main.js",
"lint-staged": {
+10
View File
@@ -918,6 +918,16 @@ If uncertain, ask the developer before adding an entry.
- **Impact:** Agents can silently inherit invalid or weak model settings, leading to broken subagent runs or degraded implementation quality.
- **Mitigation:** Keep `.cursor` agent configs on Cursor-supported models only, never use `composer-2` in `.claude`, and standardize `.codex/agents/*.toml` on `gpt-5.4` unless a contributor explicitly requests an override.
- **Status:** confirmed
### codesign parses "5chan.app" as process ID 5
- **Date:** 2026-06-12
- **Observed by:** contributor + Claude
- **Context:** Running the first signed + notarized mac Electron build (`yarn electron:build:mac:arm64` with Apple credentials set)
- **What was surprising:** `@electron/notarize` 2.x runs its pre-upload signature check as `codesign -dv 5chan.app` from the bundle's parent directory, and `codesign` accepts a process ID in place of a path — so it parses the digit-leading basename as PID 5 and fails with `5chan.app: No such process` even though the app is signed correctly.
- **Impact:** Notarization aborts after a successful signing pass; the error message looks like a signing failure and invites debugging the certificate/keychain instead of the real cause. Any tool that shells out to `codesign` with a bare relative path can hit this because the app is literally named `5chan`.
- **Mitigation:** Keep the yarn patch `.yarn/patches/@electron-notarize-npm-2.5.0-*.patch` (backport of electron/notarize#245, prefixes the basename with `./`) until electron-forge depends on `@electron/notarize` >= 3.x. When invoking `codesign` manually on the app bundle, always use an absolute or `./`-prefixed path.
- **Status:** confirmed
```
---
+12 -1
View File
@@ -2234,7 +2234,7 @@ __metadata:
languageName: node
linkType: hard
"@electron/notarize@npm:^2.1.0":
"@electron/notarize@npm:2.5.0":
version: 2.5.0
resolution: "@electron/notarize@npm:2.5.0"
dependencies:
@@ -2245,6 +2245,17 @@ __metadata:
languageName: node
linkType: hard
"@electron/notarize@patch:@electron/notarize@npm%3A2.5.0#~/.yarn/patches/@electron-notarize-npm-2.5.0-b15dc30c99.patch":
version: 2.5.0
resolution: "@electron/notarize@patch:@electron/notarize@npm%3A2.5.0#~/.yarn/patches/@electron-notarize-npm-2.5.0-b15dc30c99.patch::version=2.5.0&hash=8c8230"
dependencies:
debug: "npm:^4.1.1"
fs-extra: "npm:^9.0.1"
promise-retry: "npm:^2.0.1"
checksum: 10c0/241008603a7a89358de9bf157e7d630f67180a2405536694a1a39027ad86ee860b94000da5dd167f9503c8c1fa975dfaae6b8519054c144319bf6909201008d6
languageName: node
linkType: hard
"@electron/osx-sign@npm:^1.0.5":
version: 1.3.3
resolution: "@electron/osx-sign@npm:1.3.3"