mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-19 02:10:15 -05:00
macOS packaging: strip extended attributes before signing and verify the DMG's app strictly (#525) (#527)
hdiutil makehybrid -hfs gave every file and folder in the image a com.apple.FinderInfo, so WordCraft.app failed codesign --verify --deep --strict inside the released DMG and after copying it out with ditto. - Build the DMG with hdiutil create -srcfolder -fs HFS+ -format UDZO (retried: attaching a device is flaky on CI runners). - Clear extended attributes on the bundle before signing. - After signing the DMG, mount it read-only and strictly verify the app inside it and a ditto copy of it (plus spctl when notarizing), so the release job fails if this regresses. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1e4ed377d8
commit
f822dcb23c
2 files changed
+65
-9
No files matched your search
@@ -2,8 +2,14 @@
|
||||
|
||||
What Finder shows when the DMG opens: a 660 × 400 pt background with the app icon and the
|
||||
`Applications` link side by side. `package.sh` copies these files into the image; nothing here is
|
||||
generated at build time, so the DMG still builds with `hdiutil makehybrid` (no mounted device, no
|
||||
Finder scripting on CI, nothing extra installed in the signing job).
|
||||
generated at build time, so the DMG builds with a plain `hdiutil create -srcfolder` (no Finder
|
||||
scripting on CI, nothing extra installed in the signing job).
|
||||
|
||||
Nothing in this step may touch `WordCraft.app`: `codesign --verify --deep --strict` rejects any
|
||||
`com.apple.FinderInfo` or resource fork on the bundle. `hdiutil makehybrid -hfs`, which built the
|
||||
image before, put a FinderInfo on every file in it, so the released app failed that check (#525).
|
||||
`package.sh` clears extended attributes before signing and, after building the DMG, mounts it
|
||||
read-only and checks the app inside it, and a `ditto` copy of it, with `--strict`.
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
|
||||
@@ -111,6 +111,10 @@ sed -e "s/@VERSION@/$VERSION/g" -e "s/@SHORT_VERSION@/$SHORT_VERSION/g" \
|
||||
plutil -lint "$APP/Contents/Info.plist"
|
||||
printf 'APPL????' >"$APP/Contents/PkgInfo"
|
||||
|
||||
# codesign --strict rejects extended attributes such as com.apple.FinderInfo or a resource fork
|
||||
# ("Finder information, or similar detritus not allowed"); a source file copied in can carry them.
|
||||
# Clear them before signing; nothing after this point may write xattrs onto the bundle (#525).
|
||||
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/WordCraft"
|
||||
@@ -135,15 +139,61 @@ 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 "WordCraft" -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"
|
||||
# Retries an hdiutil verb that attaches a device: on CI runners it fails now and then with
|
||||
# "Resource busy". Usage: hdiutil_retry <verb> <args...>; five tries with growing pauses.
|
||||
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 5 times" >&2
|
||||
return 1
|
||||
fi
|
||||
warn "hdiutil $1 failed (attempt $attempt of 5); retrying in $((attempt * 10))s"
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
}
|
||||
# `hdiutil create -srcfolder` copies the staged files as they are. The previous
|
||||
# `makehybrid -hfs` + `convert` gave every file and folder in the image a com.apple.FinderInfo
|
||||
# (Finder location -1,-1), so the signed app failed `codesign --verify --deep --strict` inside the
|
||||
# mounted DMG and after copying it out (#525). The volume name has no version: .DS_Store finds the
|
||||
# background through an alias that includes it.
|
||||
hdiutil_retry create -srcfolder "$STAGE" -volname "WordCraft" -fs HFS+ -format UDZO \
|
||||
-imagekey zlib-level=9 -ov "$DMG"
|
||||
sign "$DMG"
|
||||
codesign --verify --strict --verbose=2 "$DMG"
|
||||
|
||||
# Gate: the app inside the finished image, and a copy installed from it with ditto, must pass the
|
||||
# same strict check as the app before packaging, so a container signature can't hide a damaged app
|
||||
# signature (#525). Returns non-zero (set -e then stops the build) after detaching the image.
|
||||
verify_dmg_app() {
|
||||
local dmg="$1" mount="$WORK/dmg-check" copy="$WORK/dmg-check-copy" rc=0
|
||||
mkdir -p "$mount" "$copy"
|
||||
hdiutil_retry attach -readonly -nobrowse -noautoopen -mountpoint "$mount" "$dmg" >/dev/null
|
||||
echo "==> strict signature check of WordCraft.app inside $(basename "$dmg")"
|
||||
codesign --verify --deep --strict --verbose=2 "$mount/WordCraft.app" || rc=1
|
||||
if [ "$rc" = 0 ]; then
|
||||
ditto "$mount/WordCraft.app" "$copy/WordCraft.app" || rc=1
|
||||
fi
|
||||
if [ "$rc" = 0 ]; then
|
||||
echo "==> strict signature check of a ditto copy of it"
|
||||
codesign --verify --deep --strict --verbose=2 "$copy/WordCraft.app" || rc=1
|
||||
fi
|
||||
if [ "$rc" = 0 ] && [ "$NOTARIZE" = 1 ]; then
|
||||
spctl --assess --type execute -vvv "$copy/WordCraft.app" || rc=1
|
||||
fi
|
||||
if [ "$rc" != 0 ]; then
|
||||
echo "error: WordCraft.app in $(basename "$dmg") fails strict verification; extended attributes:" >&2
|
||||
xattr -lr "$mount/WordCraft.app" >&2 || true
|
||||
fi
|
||||
hdiutil_retry detach "$mount" >/dev/null || hdiutil detach "$mount" -force >/dev/null || rc=1
|
||||
rm -rf "$copy"
|
||||
return "$rc"
|
||||
}
|
||||
verify_dmg_app "$DMG"
|
||||
if [ "$NOTARIZE" = 1 ]; then
|
||||
notarize "$DMG"
|
||||
xcrun stapler staple "$DMG"
|
||||
|
||||
Reference in new issue
Block a user