Skip to content

fix(release): ad-hoc sign macOS .app before DMG sealing#57

Merged
Ron537 merged 1 commit into
mainfrom
fix/macos-adhoc-sign-before-publish
May 11, 2026
Merged

fix(release): ad-hoc sign macOS .app before DMG sealing#57
Ron537 merged 1 commit into
mainfrom
fix/macos-adhoc-sign-before-publish

Conversation

@Ron537
Copy link
Copy Markdown
Owner

@Ron537 Ron537 commented May 11, 2026

Follow-up to #45 / #56 — addresses the unsigned-bundle issue raised by @raphapizzi.

Problem

Published macOS DMGs through v0.14.1 ship an entirely unsigned .app:

$ codesign -dvvv /Volumes/DPlex\ 0.14.0/DPlex.app
/Volumes/DPlex 0.14.0/DPlex.app: code object is not signed at all

This forces users to manually xattr -dr com.apple.quarantine on first launch and can trigger AMFI / "Team IDs differ" errors on macOS 14+.

Root cause

Two things compound:

  1. identity: null in electron-builder.yml disables electron-builder's signing path entirely. The existing comment explains the original motivation: when electron-builder did partial ad-hoc signing, the bundled Electron Framework.framework ended up with a different Team ID than the main binary, breaking the load on macOS 14+.
  2. The release workflow's Ad-hoc sign macOS app step is dead code. It runs after npm run build:mac:ci -- --publish always, by which point electron-builder has already created and uploaded the DMG containing the unsigned .app. The post-build codesign is applied to a file on the runner that nothing downloads.

Fix

Move the ad-hoc signing into an electron-builder afterPack hook that runs after pack but before DMG sealing:

  • New scripts/mac-adhoc-sign.mjs — runs codesign --force --deep --sign - <.app>. A single recursive call signs every nested Mach-O (main binary, helpers, framework, dylibs) with the same empty/ad-hoc identity, so the "Team IDs differ" mismatch never arises. No-op on Windows/Linux; no-op on macOS when CSC_LINK is set so real Developer ID signing can take over later.
  • electron-builder.yml — wires the hook via afterPack:, and rewrites the identity: null comment to explain why we still disable electron-builder's own broken per-component path.
  • .github/workflows/release.yml — removes the dead post-build Ad-hoc sign macOS app step (its job is now handled correctly by the hook).
  • package.json / CHANGELOG.md — patch bump to v0.14.2 + customer-facing changelog entry.

What this gets us

  • codesign -dvvv reports a valid ad-hoc signature on the published .app
  • ✅ macOS 14+ loads the bundled Electron Framework without "Team IDs differ"
  • ✅ The "DPlex is damaged and can't be opened" Gatekeeper hard-reject path goes away

What this does NOT get us

Still not signed by a Developer ID, so:

  • Users still see the unidentified-developer warning on first launch
  • Users may still need xattr -dr com.apple.quarantine /Applications/DPlex.app

Both are unavoidable until we add a paid Developer ID + notarization.

Local verification

Smoke-tested the hook against a real .app (Calculator stand-in):

=== before ===
/tmp/sign-test/DPlex.app: code object is not signed at all

=== after ===
flags=0x2(adhoc)
/tmp/sign-test/DPlex.app: valid on disk
/tmp/sign-test/DPlex.app: satisfies its Designated Requirement

Pre-release verification

Same recipe as #56 — dry-run the release workflow on this PR, download the macOS artifact, and run:

hdiutil attach DPlex-x64.dmg -nobrowse
codesign -dvvv "/Volumes/DPlex 0.14.2/DPlex.app"
# Expect: flags=0x2(adhoc), "satisfies its Designated Requirement"
hdiutil detach "/Volumes/DPlex 0.14.2"

Tests

  • npm run lint — no new issues
  • npm run typecheck — clean
  • npm run test:unit — 325/325 pass
  • Manual codesign smoke test (above)

Diff

+90 / −26 across 5 files (~30 lines of actual hook logic, the rest is comment, workflow cleanup, version bump, changelog entry).

Published macOS DMGs through v0.14.1 contained an entirely unsigned
.app (`codesign -dvvv` reported "code object is not signed at all"),
so users had to manually clear quarantine and could hit AMFI/'Team IDs
differ' errors on macOS 14+.

Root cause: `electron-builder.yml` set `identity: null` (disabling
electron-builder's signing path entirely), and the release workflow's
`Ad-hoc sign macOS app` step ran AFTER `--publish always` had already
uploaded the unsigned DMG — so the post-build codesign was applied to
a file no one downloads.

Move the ad-hoc signing into an electron-builder `afterPack` hook so
it runs after pack but BEFORE DMG sealing. Using a single recursive
`codesign --deep --sign -` against the fully-packed .app signs the
main binary, helpers, and the bundled Electron Framework with the
same (empty) ad-hoc identity — avoiding the per-component 'Team IDs
differ' issue that originally motivated `identity: null`. When
CSC_LINK is set in CI, the hook is a no-op and the real Developer ID
path takes over.

Verified locally on a Calculator.app stand-in:
  before: 'code object is not signed at all'
  after:  flags=0x2(adhoc), satisfies its Designated Requirement

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Ron537 Ron537 merged commit e892113 into main May 11, 2026
5 checks passed
@Ron537 Ron537 deleted the fix/macos-adhoc-sign-before-publish branch May 11, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant