Agent Skills: macos-notarization-workflow

Automate Apple notarization with xcrun notarytool for macOS application distribution

UncategorizedID: a5c-ai/babysitter/macos-notarization-workflow

Install this agent skill to your local

pnpm dlx add-skill https://github.com/a5c-ai/babysitter/tree/HEAD/plugins/babysitter/skills/babysit/process/specializations/desktop-development/skills/macos-notarization-workflow

Skill Files

Browse the full folder contents for macos-notarization-workflow.

Download Skill

Loading file tree…

plugins/babysitter/skills/babysit/process/specializations/desktop-development/skills/macos-notarization-workflow/SKILL.md

Skill Metadata

Name
macos-notarization-workflow
Description
Automate Apple notarization with xcrun notarytool for macOS application distribution

macos-notarization-workflow

Automate Apple notarization workflow using xcrun notarytool for macOS applications. This skill handles the complete notarization process including submission, status checking, and stapling.

Capabilities

  • Submit apps for notarization via notarytool
  • Monitor notarization status
  • Staple notarization ticket to app
  • Handle notarization errors
  • Generate CI/CD notarization scripts
  • Configure App Store Connect API keys
  • Validate apps before submission
  • Generate notarization reports

Input Schema

{
  "type": "object",
  "properties": {
    "projectPath": {
      "type": "string",
      "description": "Path to the project"
    },
    "appPath": {
      "type": "string",
      "description": "Path to the signed app bundle or DMG"
    },
    "authMethod": {
      "enum": ["app-store-connect-api", "apple-id", "keychain"],
      "default": "app-store-connect-api"
    },
    "credentials": {
      "type": "object",
      "properties": {
        "keyId": { "type": "string" },
        "issuerId": { "type": "string" },
        "keyPath": { "type": "string" },
        "appleId": { "type": "string" },
        "teamId": { "type": "string" }
      }
    },
    "waitForCompletion": {
      "type": "boolean",
      "default": true
    },
    "staple": {
      "type": "boolean",
      "default": true
    }
  },
  "required": ["projectPath", "appPath"]
}

Output Schema

{
  "type": "object",
  "properties": {
    "success": { "type": "boolean" },
    "submissionId": { "type": "string" },
    "status": { "enum": ["Accepted", "Invalid", "In Progress", "Rejected"] },
    "logUrl": { "type": "string" },
    "errors": { "type": "array" },
    "stapled": { "type": "boolean" }
  },
  "required": ["success"]
}

Notarization Workflow

1. Prerequisites

# Ensure Xcode command line tools are installed
xcode-select --install

# Verify code signing
codesign --verify --deep --strict MyApp.app
codesign -vvv --deep --strict MyApp.app

# Check hardened runtime
codesign -dvvv MyApp.app | grep runtime
# Should show: flags=0x10000(runtime)

2. Store Credentials (Recommended)

# Store App Store Connect API key in keychain
xcrun notarytool store-credentials "MyProfile" \
    --key ~/private_keys/AuthKey_XXXXXXXXXX.p8 \
    --key-id XXXXXXXXXX \
    --issuer xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# Or store Apple ID credentials
xcrun notarytool store-credentials "MyAppleIDProfile" \
    --apple-id your.email@example.com \
    --team-id XXXXXXXXXX \
    --password @keychain:AC_PASSWORD

3. Submit for Notarization

# Using stored credentials
xcrun notarytool submit MyApp.app \
    --keychain-profile "MyProfile" \
    --wait

# Using API key directly
xcrun notarytool submit MyApp.app \
    --key ~/private_keys/AuthKey_XXXXXXXXXX.p8 \
    --key-id XXXXXXXXXX \
    --issuer xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
    --wait

# Using Apple ID
xcrun notarytool submit MyApp.app \
    --apple-id your.email@example.com \
    --team-id XXXXXXXXXX \
    --password @keychain:AC_PASSWORD \
    --wait

4. Check Status

# Check specific submission
xcrun notarytool info <submission-id> \
    --keychain-profile "MyProfile"

# Get submission log
xcrun notarytool log <submission-id> \
    --keychain-profile "MyProfile" \
    developer_log.json

# List recent submissions
xcrun notarytool history \
    --keychain-profile "MyProfile"

5. Staple Ticket

# Staple to app bundle
xcrun stapler staple MyApp.app

# Staple to DMG
xcrun stapler staple MyApp.dmg

# Staple to pkg
xcrun stapler staple MyApp.pkg

# Validate stapling
xcrun stapler validate MyApp.app

Complete Script

#!/bin/bash
# notarize.sh

set -e

APP_PATH="${1}"
KEYCHAIN_PROFILE="${2:-MyProfile}"

echo "=== Validating app bundle ==="
codesign --verify --deep --strict "$APP_PATH"

echo "=== Submitting for notarization ==="
SUBMISSION_OUTPUT=$(xcrun notarytool submit "$APP_PATH" \
    --keychain-profile "$KEYCHAIN_PROFILE" \
    --wait \
    --output-format json)

SUBMISSION_ID=$(echo "$SUBMISSION_OUTPUT" | jq -r '.id')
STATUS=$(echo "$SUBMISSION_OUTPUT" | jq -r '.status')

echo "Submission ID: $SUBMISSION_ID"
echo "Status: $STATUS"

if [ "$STATUS" != "Accepted" ]; then
    echo "=== Notarization failed, fetching log ==="
    xcrun notarytool log "$SUBMISSION_ID" \
        --keychain-profile "$KEYCHAIN_PROFILE" \
        notarization_log.json

    cat notarization_log.json
    exit 1
fi

echo "=== Stapling ticket ==="
xcrun stapler staple "$APP_PATH"

echo "=== Validating staple ==="
xcrun stapler validate "$APP_PATH"

echo "=== Notarization complete ==="

GitHub Actions Integration

name: Build and Notarize

on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: macos-latest

    steps:
    - uses: actions/checkout@v4

    - name: Import signing certificate
      env:
        CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
        CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
      run: |
        CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
        KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
        KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

        echo -n "$CERTIFICATE_BASE64" | base64 --decode > $CERTIFICATE_PATH

        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 $CERTIFICATE_PATH -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
        security list-keychain -d user -s $KEYCHAIN_PATH

    - name: Build app
      run: |
        xcodebuild -project MyApp.xcodeproj \
            -scheme MyApp \
            -configuration Release \
            -archivePath build/MyApp.xcarchive \
            archive

        xcodebuild -exportArchive \
            -archivePath build/MyApp.xcarchive \
            -exportOptionsPlist ExportOptions.plist \
            -exportPath build/

    - name: Store notarization credentials
      env:
        API_KEY: ${{ secrets.NOTARIZATION_API_KEY }}
        API_KEY_ID: ${{ secrets.NOTARIZATION_API_KEY_ID }}
        API_ISSUER: ${{ secrets.NOTARIZATION_API_ISSUER }}
      run: |
        mkdir -p ~/private_keys
        echo -n "$API_KEY" > ~/private_keys/AuthKey.p8

        xcrun notarytool store-credentials "CI_PROFILE" \
            --key ~/private_keys/AuthKey.p8 \
            --key-id "$API_KEY_ID" \
            --issuer "$API_ISSUER"

    - name: Notarize app
      run: |
        xcrun notarytool submit build/MyApp.app \
            --keychain-profile "CI_PROFILE" \
            --wait

        xcrun stapler staple build/MyApp.app

    - name: Create DMG
      run: |
        create-dmg build/MyApp.app build/
        xcrun notarytool submit build/*.dmg \
            --keychain-profile "CI_PROFILE" \
            --wait
        xcrun stapler staple build/*.dmg

    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: MyApp
        path: build/*.dmg

Common Issues

Issue: Hardened runtime not enabled

Error: The signature does not include a secure timestamp.

Fix: Sign with hardened runtime and timestamp:

codesign --force --options runtime --timestamp --sign "Developer ID" MyApp.app

Issue: Missing entitlements

Error: The executable does not have the hardened runtime enabled.

Fix: Include entitlements in signing:

codesign --force --options runtime --timestamp \
    --entitlements MyApp.entitlements \
    --sign "Developer ID Application: Company" MyApp.app

Issue: Unsigned nested code

Error: The signature of the binary is invalid.

Fix: Sign all nested components:

find MyApp.app -name "*.dylib" -o -name "*.framework" | \
    xargs -I {} codesign --force --options runtime --timestamp --sign "Developer ID" {}

Best Practices

  1. Use App Store Connect API: More reliable than Apple ID
  2. Store credentials securely: Use keychain profiles
  3. Validate before submitting: codesign --verify
  4. Always staple: Makes offline verification possible
  5. Archive submission logs: For debugging
  6. Test on fresh Mac: Verify Gatekeeper acceptance

Related Skills

  • macos-entitlements-generator - Entitlements configuration
  • macos-codesign-workflow - Code signing
  • code-signing-setup process - Full signing workflow

Related Agents

  • swiftui-macos-expert - macOS development
  • code-signing-specialist - Signing expertise