From 0b960d39e32b176e5161d62bb26dcce1d1d229c8 Mon Sep 17 00:00:00 2001 From: xristosmoug <298008744+xristosmoug@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:26:48 +0200 Subject: [PATCH] fix(macos): verify strict signature in release DMG (#863) --- packaging/macos/package.sh | 66 ++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/packaging/macos/package.sh b/packaging/macos/package.sh index b43166f1..c6fd5e09 100755 --- a/packaging/macos/package.sh +++ b/packaging/macos/package.sh @@ -112,6 +112,10 @@ printf 'APPL????' >"$APP/Contents/PkgInfo" mkdir -p "$APP/Contents/Resources/Licenses" copy_docs "$APP/Contents/Resources/Licenses" +# Remove inherited build-time extended attributes BEFORE code signing. +# Never repair, re-sign or strip metadata from the published release after signing. +xattr -cr "$APP" + # Sign inside-out: nested code first, then the bundle itself (no --deep on the final signature). # Today the only nested code is the main executable; frameworks/helpers would be signed here too. sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP/Contents/MacOS/LightCraft" @@ -136,15 +140,63 @@ ln -s /Applications "$STAGE/Applications" mkdir -p "$STAGE/.background" cp "$HERE/dmg/background.tiff" "$STAGE/.background/background.tiff" cp "$HERE/dmg/dmg-layout.DS_Store" "$STAGE/.DS_Store" -rm -f "$DMG" "$WORK/raw.dmg" -# makehybrid + convert builds the image without attaching a device, unlike `create -srcfolder`, -# which is flaky on CI runners ("Resource busy") and hangs in sandboxed sessions. -# The volume name has no version: .DS_Store finds the background through an alias that includes it. -hdiutil makehybrid -hfs -hfs-volume-name "LightCraft" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE" -hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG" -rm -f "$WORK/raw.dmg" +rm -f "$DMG" +# hdiutil makehybrid -hfs injects FinderInfo metadata into the signed app. +# create -srcfolder preserves the signed app and the existing .DS_Store layout. +# hdiutil may transiently return "Resource busy" on CI; retry boundedly. +hdiutil_retry() { + local attempt + for attempt in 1 2 3 4 5; do + if hdiutil "$@"; then + return 0 + fi + if [ "$attempt" = 5 ]; then + echo "error: hdiutil $1 failed after five attempts" >&2 + return 1 + fi + warn "hdiutil $1 attempt $attempt failed; retrying" + sleep $((attempt * 10)) + done +} +hdiutil_retry create -srcfolder "$STAGE" -volname "LightCraft" -fs HFS+ -format UDZO \ + -imagekey zlib-level=9 -ov "$DMG" sign "$DMG" codesign --verify --strict --verbose=2 "$DMG" + +# Verification must use the FINISHED image (and a normal ditto-installed copy). +# Signatures of the DMG container alone do not validate the bundle in the image. +verify_dmg_app() { + local mount="$WORK/dmg-verify-mount" copy="$WORK/dmg-verify-copy" rc=0 + mkdir -p "$mount" "$copy" + if ! hdiutil_retry attach -readonly -nobrowse -noautoopen -mountpoint "$mount" "$DMG" >/dev/null; then + echo "error: unable to mount finished $DMG" >&2 + hdiutil detach "$mount" >/dev/null 2>&1 || true + return 1 + fi + echo "==> strict codesign of LightCraft.app in finished DMG" + codesign --verify --deep --strict --verbose=2 "$mount/LightCraft.app" || rc=1 + if [ "$rc" = 0 ]; then + ditto "$mount/LightCraft.app" "$copy/LightCraft.app" || rc=1 + fi + if [ "$rc" = 0 ]; then + echo "==> strict codesign of LightCraft.app after ditto install-copy" + codesign --verify --deep --strict --verbose=2 "$copy/LightCraft.app" || rc=1 + fi + if [ "$rc" = 0 ] && [ "$NOTARIZE" = 1 ]; then + spctl --assess --type execute -vvv "$copy/LightCraft.app" || rc=1 + fi + if [ "$rc" != 0 ]; then + echo "error: published-image app verification failed; reject $DMG" >&2 + xattr -lr "$mount/LightCraft.app" >&2 || true + fi + if ! hdiutil_retry detach "$mount" >/dev/null; then + echo "error: could not detach verification mount" >&2 + rc=1 + fi + rm -rf "$copy" + return "$rc" +} +verify_dmg_app if [ "$NOTARIZE" = 1 ]; then notarize "$DMG" xcrun stapler staple "$DMG"