From 5efbe1323a55c86074f7f4174b23323a00320230 Mon Sep 17 00:00:00 2001 From: Stevenson Chittumuri Date: Mon, 22 Jun 2026 02:32:01 -0400 Subject: [PATCH] fix(macos): make notarization actually pass (sign nested code; fail on reject) The 2.1.0-rc1 release build signed and submitted the .app but Apple returned "status: Invalid", and the script then tried to staple a rejected artifact (exit 65). Two fixes: - Sign every nested Mach-O (dylib/.so) explicitly with the hardened runtime + timestamp before sealing the bundle. `codesign --deep` is unreliable for notarization, which rejects bundles containing any unsigned/old-signature nested code (the usual cause of "Invalid" for PyInstaller apps). - notarize_and_staple now checks for "status: Accepted"; on anything else it prints Apple's detailed notary log (xcrun notarytool log ) and fails the build instead of stapling a rejected artifact. Co-Authored-By: Claude Opus 4.8 --- packaging/build_macos.sh | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/packaging/build_macos.sh b/packaging/build_macos.sh index fc6f9ac..0fad7d8 100755 --- a/packaging/build_macos.sh +++ b/packaging/build_macos.sh @@ -36,25 +36,38 @@ echo " venv: ${VENV}" SIGN_IDENTITY="${MACOS_SIGN_IDENTITY:-}" # Notarize + staple a signed artifact (.app or .dmg) with whichever credentials -# are available; a no-op (with a note) when none are set. +# are available; a no-op (with a note) when none are set. Fails the build (and +# prints Apple's detailed log) if the submission is not Accepted, instead of +# blindly stapling a rejected artifact. notarize_and_staple() { local target="$1" + local creds=() if [[ -n "${MACOS_NOTARY_KEYCHAIN_PROFILE:-}" ]]; then - echo "==> Notarizing ${target} via keychain profile ${MACOS_NOTARY_KEYCHAIN_PROFILE}" - xcrun notarytool submit "${target}" \ - --keychain-profile "${MACOS_NOTARY_KEYCHAIN_PROFILE}" --wait + creds=(--keychain-profile "${MACOS_NOTARY_KEYCHAIN_PROFILE}") elif [[ -n "${MACOS_NOTARY_APPLE_ID:-}" && -n "${MACOS_NOTARY_TEAM_ID:-}" \ && -n "${MACOS_NOTARY_PASSWORD:-}" ]]; then - echo "==> Notarizing ${target} via Apple ID ${MACOS_NOTARY_APPLE_ID}" - xcrun notarytool submit "${target}" \ - --apple-id "${MACOS_NOTARY_APPLE_ID}" \ - --team-id "${MACOS_NOTARY_TEAM_ID}" \ - --password "${MACOS_NOTARY_PASSWORD}" --wait + creds=(--apple-id "${MACOS_NOTARY_APPLE_ID}" --team-id "${MACOS_NOTARY_TEAM_ID}" + --password "${MACOS_NOTARY_PASSWORD}") else echo "==> Signed but NOT notarized (no notary credentials): ${target}" echo " Set MACOS_NOTARY_APPLE_ID/TEAM_ID/PASSWORD or MACOS_NOTARY_KEYCHAIN_PROFILE." return 0 fi + echo "==> Submitting ${target} to Apple's notary service…" + local out submission_id + # `|| true`: notarytool exits non-zero on a rejected submission; we want to + # inspect the status and fetch the log ourselves rather than abort here. + out="$(xcrun notarytool submit "${target}" "${creds[@]}" --wait 2>&1)" || true + echo "${out}" + if ! printf '%s\n' "${out}" | grep -q "status: Accepted"; then + submission_id="$(printf '%s\n' "${out}" | awk -F'[: ]+' '/^[[:space:]]*id:/{print $3; exit}')" + echo "!! Notarization was NOT Accepted for ${target}." + if [[ -n "${submission_id}" ]]; then + echo "==> Apple notary log for ${submission_id}:" + xcrun notarytool log "${submission_id}" "${creds[@]}" || true + fi + return 1 + fi echo "==> Stapling ${target}" xcrun stapler staple "${target}" } @@ -107,7 +120,16 @@ plutil -lint "${APP}/Contents/Info.plist" # Must happen BEFORE the .dmg is built so the dmg ships the signed app. if [[ -n "${SIGN_IDENTITY}" ]]; then echo "==> Codesigning ${APP} with: ${SIGN_IDENTITY}" - codesign --deep --force --options runtime --timestamp \ + # Sign every nested Mach-O (dylib/.so) first, inside-out. `codesign --deep` is + # unreliable for notarization — Apple returns "Invalid" when any nested binary + # is unsigned or carries an old signature — so sign them explicitly with the + # hardened runtime + a secure timestamp before sealing the bundle. + while IFS= read -r -d '' lib; do + codesign --force --options runtime --timestamp --sign "${SIGN_IDENTITY}" "${lib}" \ + || { echo "!! codesign failed for ${lib}"; exit 1; } + done < <(find "${APP}/Contents" -type f \( -name "*.dylib" -o -name "*.so" \)) + # Then the app bundle itself, carrying the entitlements. + codesign --force --options runtime --timestamp \ --entitlements packaging/entitlements.plist \ --sign "${SIGN_IDENTITY}" "${APP}" codesign --verify --deep --strict --verbose=2 "${APP}"