Skip to content

Notarize macOS builds to eliminate Gatekeeper warnings #11

Description

@rachitwatts

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:

  1. The app is not code signed — CODE_SIGN_IDENTITY="" and CODE_SIGNING_REQUIRED=NO are set in both CI and the build script
  2. The app is not notarized — Apple has not verified it for malicious content
  3. 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

  • Blink/Blink.entitlements — new file
  • project.yml — add signing settings
  • scripts/build-dmg.sh — add signing, notarization, stapling
  • .github/workflows/ci.yml — add certificate import + notarization steps
  • GitHub repo settings — add secrets

Testing

  1. Build locally with signing: codesign -dvv build/Build/Products/Release/Blink.app
  2. Verify Gatekeeper: spctl --assess --type execute Blink.app
  3. Download DMG from GitHub Release on a fresh Mac — should open without any Gatekeeper warning
  4. Check notarization status: xcrun stapler validate Blink.dmg

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium priorityenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions