diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fca4451c..88e91e10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.yarn/patches/@electron-notarize-npm-2.5.0-b15dc30c99.patch b/.yarn/patches/@electron-notarize-npm-2.5.0-b15dc30c99.patch new file mode 100644 index 00000000..cdd127db --- /dev/null +++ b/.yarn/patches/@electron-notarize-npm-2.5.0-b15dc30c99.patch @@ -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; diff --git a/docs/agent-playbooks/known-surprises.md b/docs/agent-playbooks/known-surprises.md index 171ef94e..d555ec2e 100644 --- a/docs/agent-playbooks/known-surprises.md +++ b/docs/agent-playbooks/known-surprises.md @@ -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 diff --git a/electron/entitlements.plist b/electron/entitlements.plist new file mode 100644 index 00000000..890ebcf4 --- /dev/null +++ b/electron/entitlements.plist @@ -0,0 +1,14 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + + com.apple.security.cs.disable-library-validation + + + diff --git a/forge.config.js b/forge.config.js index a79d3e6a..341b51d3 100644 --- a/forge.config.js +++ b/forge.config.js @@ -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: { diff --git a/package.json b/package.json index e2fe9a95..f704ec6b 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/public/llms-full.txt b/public/llms-full.txt index 43800638..5898089a 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -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 ``` --- diff --git a/yarn.lock b/yarn.lock index 4b83d4e6..5051ebb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"