Problem
When users download Blink from GitHub Releases and open it for the first time, macOS Gatekeeper blocks the app and shows a dialog asking to move it to Trash. This happens because:
- The app is not code signed —
CODE_SIGN_IDENTITY="" and CODE_SIGNING_REQUIRED=NO are set in both CI and the build script
- The app is not notarized — Apple has not verified it for malicious content
- The DMG is not signed or stapled — the distribution artifact itself is untrusted
Currently, the build-dmg.sh release notes include a workaround: "Right-click → Open → Click Open", but this is a poor user experience and doesn't work reliably on macOS Sequoia+ which has tightened Gatekeeper further.
Current State
| Aspect |
Value |
| Bundle ID |
com.rachitwatts.blink |
| Code Signing |
Disabled (CODE_SIGN_IDENTITY="") |
| Notarization |
Not implemented |
| Entitlements |
None (no .entitlements file) |
| Distribution |
Unsigned DMG via GitHub Releases |
| Build script |
scripts/build-dmg.sh |
| CI workflow |
.github/workflows/ci.yml |
| Project config |
project.yml (XcodeGen) |
Solution
Implement Apple code signing + notarization in the build pipeline. This requires:
Prerequisites
- Apple Developer Program membership ($99/year) — required for Developer ID certificates
- Developer ID Application certificate — for signing the app (not Mac App Store distribution)
- Developer ID Installer certificate (optional) — for signing the DMG/pkg
- App-specific password — for
notarytool authentication (generated at appleid.apple.com)
Implementation Steps
1. Create an entitlements file
Create Blink/Blink.entitlements:
<?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>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<false/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
Note: Blink uses AppleScript (NSAppleScript) for window management, so the automation.apple-events entitlement is needed. Sandboxing is disabled since the app needs system-level accessibility for window tiling. Adjust entitlements based on actual runtime requirements.
2. Update project.yml with signing settings
settings:
CODE_SIGN_IDENTITY: "Developer ID Application"
CODE_SIGN_STYLE: Manual
DEVELOPMENT_TEAM: <TEAM_ID>
CODE_SIGN_ENTITLEMENTS: Blink/Blink.entitlements
OTHER_CODE_SIGN_FLAGS: "--options runtime" # Hardened Runtime (required for notarization)
The --options runtime flag enables Hardened Runtime, which is mandatory for notarization.
3. Update scripts/build-dmg.sh to sign and notarize
After building the app, add these steps:
# 1. Sign the app with hardened runtime
codesign --force --deep --options runtime \
--sign "Developer ID Application: <NAME> (<TEAM_ID>)" \
--entitlements Blink/Blink.entitlements \
"build/Build/Products/Release/Blink.app"
# 2. Verify signing
codesign --verify --deep --strict "build/Build/Products/Release/Blink.app"
spctl --assess --type execute "build/Build/Products/Release/Blink.app"
# 3. Create the DMG (existing logic)
# ... existing DMG creation ...
# 4. Sign the DMG
codesign --force --sign "Developer ID Application: <NAME> (<TEAM_ID>)" \
"build/Blink.dmg"
# 5. Notarize the DMG
xcrun notarytool submit "build/Blink.dmg" \
--apple-id "$APPLE_ID" \
--team-id "$TEAM_ID" \
--password "$APP_SPECIFIC_PASSWORD" \
--wait
# 6. Staple the notarization ticket to the DMG
xcrun stapler staple "build/Blink.dmg"
# 7. Verify notarization
xcrun stapler validate "build/Blink.dmg"
spctl --assess --type open --context context:primary-signature "build/Blink.dmg"
4. Add GitHub Secrets
Add these secrets to the repository for CI:
| Secret |
Description |
APPLE_CERTIFICATE_BASE64 |
Developer ID Application certificate (.p12) base64-encoded |
APPLE_CERTIFICATE_PASSWORD |
Password for the .p12 file |
APPLE_TEAM_ID |
Apple Developer Team ID |
APPLE_ID |
Apple ID email for notarytool |
APP_SPECIFIC_PASSWORD |
App-specific password for notarytool |
KEYCHAIN_PASSWORD |
Temporary keychain password (can be random) |
5. Update CI workflow (.github/workflows/ci.yml)
Add certificate import and notarization steps:
- name: Import signing certificate
env:
CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create a temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import certificate
echo "$CERTIFICATE_BASE64" | base64 --decode > $RUNNER_TEMP/certificate.p12
security import $RUNNER_TEMP/certificate.p12 \
-P "$CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 \
-k "$KEYCHAIN_PATH"
# Set keychain search list
security list-keychain -d user -s "$KEYCHAIN_PATH"
- name: Build, sign, notarize, and package
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
run: |
bash scripts/build-dmg.sh --release
- name: Cleanup keychain
if: always()
run: security delete-keychain $RUNNER_TEMP/signing.keychain-db
6. Remove Gatekeeper workaround from release notes
Remove the "Right-click → Open" instruction from build-dmg.sh release notes since users will no longer need it.
Files to Modify
Testing
- Build locally with signing:
codesign -dvv build/Build/Products/Release/Blink.app
- Verify Gatekeeper:
spctl --assess --type execute Blink.app
- Download DMG from GitHub Release on a fresh Mac — should open without any Gatekeeper warning
- Check notarization status:
xcrun stapler validate Blink.dmg
References
Problem
When users download Blink from GitHub Releases and open it for the first time, macOS Gatekeeper blocks the app and shows a dialog asking to move it to Trash. This happens because:
CODE_SIGN_IDENTITY=""andCODE_SIGNING_REQUIRED=NOare set in both CI and the build scriptCurrently, the
build-dmg.shrelease notes include a workaround: "Right-click → Open → Click Open", but this is a poor user experience and doesn't work reliably on macOS Sequoia+ which has tightened Gatekeeper further.Current State
com.rachitwatts.blinkCODE_SIGN_IDENTITY="").entitlementsfile)scripts/build-dmg.sh.github/workflows/ci.ymlproject.yml(XcodeGen)Solution
Implement Apple code signing + notarization in the build pipeline. This requires:
Prerequisites
notarytoolauthentication (generated at appleid.apple.com)Implementation Steps
1. Create an entitlements file
Create
Blink/Blink.entitlements:2. Update
project.ymlwith signing settingsThe
--options runtimeflag enables Hardened Runtime, which is mandatory for notarization.3. Update
scripts/build-dmg.shto sign and notarizeAfter building the app, add these steps:
4. Add GitHub Secrets
Add these secrets to the repository for CI:
APPLE_CERTIFICATE_BASE64APPLE_CERTIFICATE_PASSWORDAPPLE_TEAM_IDAPPLE_IDAPP_SPECIFIC_PASSWORDKEYCHAIN_PASSWORD5. Update CI workflow (
.github/workflows/ci.yml)Add certificate import and notarization steps:
6. Remove Gatekeeper workaround from release notes
Remove the "Right-click → Open" instruction from
build-dmg.shrelease notes since users will no longer need it.Files to Modify
Blink/Blink.entitlements— new fileproject.yml— add signing settingsscripts/build-dmg.sh— add signing, notarization, stapling.github/workflows/ci.yml— add certificate import + notarization stepsTesting
codesign -dvv build/Build/Products/Release/Blink.appspctl --assess --type execute Blink.appxcrun stapler validate Blink.dmgReferences
notarytooldocumentation