Skip to content

Commit 6f78b31

Browse files
committed
ci(mac): own the signing keychain instead of letting electron-builder build it
electron-builder passes the certificate's IMPORT password to `security set-key-partition-list -k`, but that flag authenticates against the KEYCHAIN — a different secret. macOS used not to verify it; the runner image carrying macOS 26 does, so the v2.27.0 build failed with security: SecKeychainUnlock: The user name or passphrase you entered is not correct with nothing changed on our side: same certificate (valid to 2031), same secrets (untouched since May), same electron-builder (26.15.3). Only the image moved, 20260728 -> 20260907. Fixed upstream in PR #10101, but that is in 27.0.0-alpha.8 and in no stable release — 26.15.7 and 26.16.1 both lack it. Rather than wait, the workflow now creates the keychain, imports the certificate and authorises codesign with the keychain's own generated password, then hands CSC_KEYCHAIN to electron-builder so it skips its own path. CSC_LINK is deliberately unset: passing it makes electron-builder build its own keychain again. The step states its own removal condition.
1 parent 7f80d08 commit 6f78b31

2 files changed

Lines changed: 60 additions & 25 deletions

File tree

‎.github/workflows/build.yml‎

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,60 @@ jobs:
9090
# Export the resolved path for the next step
9191
echo "APPLE_API_KEY=$HOME/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8" >> "$GITHUB_ENV"
9292
93+
# ─────────────────────────────────────────────────────────────────
94+
# macOS signing keychain — built HERE rather than by electron-builder.
95+
#
96+
# Its own path passes the certificate's IMPORT password to
97+
# `security set-key-partition-list -k`, but that flag authenticates
98+
# against the KEYCHAIN, not against the .p12. The two are different
99+
# secrets, and macOS used not to check — the runner image that ships
100+
# macOS 26 does, so signing began failing with
101+
# security: SecKeychainUnlock: The user name or passphrase you
102+
# entered is not correct
103+
# on 2026-09-12, with nothing changed on our side: same certificate
104+
# (valid to 2031), same secrets (untouched since May), same
105+
# electron-builder (26.15.3). Only the image moved, 20260728 → 20260907.
106+
#
107+
# Upstream fixed it in PR #10101, but that sits in 27.0.0-alpha.8 and is
108+
# in NO stable release — 26.15.7 and 26.16.1 both lack it. So we own the
109+
# keychain instead of waiting: create it, import the cert, and authorise
110+
# codesign with the keychain's OWN password. `CSC_KEYCHAIN` then tells
111+
# electron-builder to use it and skip the broken path entirely.
112+
#
113+
# The keychain password is generated per run and never leaves the runner.
114+
# Delete this step once the fix lands in a stable electron-builder and
115+
# the dependency is bumped — not before.
116+
# ─────────────────────────────────────────────────────────────────
117+
- name: Prepare macOS signing keychain
118+
if: matrix.os == 'macos-latest'
119+
env:
120+
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
121+
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
122+
run: |
123+
set -euo pipefail
124+
KEYCHAIN="$RUNNER_TEMP/tigertag-signing.keychain-db"
125+
KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
126+
127+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
128+
security set-keychain-settings -lut 21600 "$KEYCHAIN"
129+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
130+
131+
echo "$MACOS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/cert.p12"
132+
security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" \
133+
-P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
134+
rm -f "$RUNNER_TEMP/cert.p12"
135+
136+
# The keychain's OWN password — this is the line electron-builder gets wrong.
137+
security set-key-partition-list -S apple-tool:,apple:,codesign: \
138+
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" > /dev/null
139+
140+
# Put it in the search list so codesign can find the identity.
141+
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
142+
143+
echo "CSC_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV"
144+
echo "Identities available for signing:"
145+
security find-identity -v -p codesigning "$KEYCHAIN" | sed 's/[A-F0-9]\{40\}/<hash>/'
146+
93147
# ─────────────────────────────────────────────────────────────────
94148
# macOS / Linux — build & publish in one step (no post-build signing).
95149
# Windows is handled in the next 4 steps so we can SIGN the .exe with
@@ -102,8 +156,10 @@ jobs:
102156
# GitHub Releases publish (electron-builder reads GH_TOKEN automatically)
103157
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
104158

105-
# macOS code signing — electron-builder reads CSC_LINK (base64 .p12) + CSC_KEY_PASSWORD
106-
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
159+
# macOS code signing — the identity lives in the keychain prepared above
160+
# (CSC_KEYCHAIN, exported to GITHUB_ENV). CSC_LINK is deliberately NOT
161+
# set: passing it makes electron-builder build its own keychain again
162+
# and take the path this works around.
107163
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
108164

109165
# macOS notarization via App Store Connect API Key (preferred over Apple ID + password)

0 commit comments

Comments
 (0)