Skip to main content

macOS Notarization

Sign and notarize macOS binaries for Gatekeeper compliance

Anodizer supports macOS code signing and notarization in two modes: cross-platform (via rcodesign, works on any OS) and native (via codesign + xcrun notarytool, macOS only).

Cross-platform mode (rcodesign)

Works on Linux, macOS, and Windows. Uses a P12 certificate for signing and an App Store Connect API key for notarization.

Minimal config

notarize:
  macos:
    - skip: false
      sign:
        certificate: "{{ Env.P12_CERTIFICATE }}"
        password: "{{ Env.P12_PASSWORD }}"
      notarize:
        issuer_id: "{{ Env.NOTARIZE_ISSUER_ID }}"
        key: "{{ Env.NOTARIZE_KEY }}"
        key_id: "{{ Env.NOTARIZE_KEY_ID }}"

Cross-platform config fields

FieldTypeDefaultDescription
idslistproject nameBuild IDs to filter
skipstring/booltrueSkip this config. Canonical field — notarization is off until you set skip: false. The upstream-style enabled: is accepted as an inverting back-compat alias (enabled: trueskip: false).

Sign config (sign)

FieldTypeDefaultDescription
certificatestringnonePath to .p12 certificate or base64 contents (template)
passwordstringnonePassword for the .p12 certificate (template)
entitlementsstringnonePath to entitlements XML file (template)
timestamp_urlstringhttp://timestamp.apple.com/ts01RFC-3161 timestamp service URL passed to rcodesign sign --timestamp-url. Override when running behind a corporate proxy or when Apple's service is unreachable.

Notarize config (notarize, optional — omit for sign-only)

FieldTypeDefaultDescription
issuer_idstringnoneApp Store Connect API key issuer UUID (template)
keystringnonePath to .p8 key file or base64 contents (template)
key_idstringnoneAPI key ID (template)
timeoutstring10mTimeout for notarization polling
waitboolnoneWhether to wait for notarization to complete

Native mode (codesign + xcrun)

macOS only. Uses Keychain identities for signing and xcrun notarytool for notarization.

Minimal config

notarize:
  macos_native:
    - skip: false
      use: dmg
      sign:
        identity: "Developer ID Application: My Org"
      notarize:
        profile_name: my-notarytool-profile

Native config fields

FieldTypeDefaultDescription
idslistproject nameBuild IDs to filter
skipstring/booltrueSkip this config. Canonical field — set skip: false to enable. The upstream-style enabled: is accepted as an inverting back-compat alias.
usestringdmgArtifact type to sign: "dmg" or "pkg"

Sign config (sign)

FieldTypeDefaultDescription
identitystringnoneKeychain identity (template)
keychainstringnonePath to Keychain file (template)
optionslistnoneOptions for codesign (e.g., ["runtime"]). DMG only
entitlementsstringnonePath to entitlements XML (template). DMG only

Notarize config (notarize, optional — omit for sign-only)

FieldTypeDefaultDescription
profile_namestringnonenotarytool stored credentials profile name (template)
waitboolnoneWhether to wait for notarization to complete
timeoutstring10mTimeout for xcrun notarytool submit --timeout (template)

Behavior

  • Notarization must be explicitly enabled (skip: false, or the enabled: true back-compat alias) — it is off by default
  • After signing, SHA-256 checksums are refreshed for all affected darwin binary artifacts
  • Notarization status is differentiated: accepted, invalid, rejected, and timeout
  • Timeouts are treated as non-fatal (the submission may still be processing)
  • Sensitive values (P12 password, API key file path) are redacted from log output
  • In native mode, when wait is enabled, the notarization ticket is automatically stapled to the artifact — both DMG (use: dmg) and PKG (use: pkg) are stapled
  • Skippable with --skip notarize

Both modes together

You can use cross-platform signing for CI builds and native signing for local builds:

notarize:
  macos:
    # cross-platform path runs in CI (skip when NOT in CI)
    - skip: "{{ Env.CI == \"\" }}"
      sign:
        certificate: "{{ Env.P12_CERTIFICATE }}"
        password: "{{ Env.P12_PASSWORD }}"
  macos_native:
    # native path runs locally (skip when IN CI)
    - skip: "{{ Env.CI != \"\" }}"
      sign:
        identity: "Developer ID Application: My Org"