Skip to content

feat: sign and notarize macOS DMG releases - #18

Merged
DevVig merged 1 commit into
mainfrom
feat/signed-notarized-dmg
Jul 18, 2026
Merged

feat: sign and notarize macOS DMG releases#18
DevVig merged 1 commit into
mainfrom
feat/signed-notarized-dmg

Conversation

@DevVig

@DevVig DevVig commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Release CI imports Developer ID, builds signed app + dmg with Tauri, and notarizes via App Store Connect API key
  • Arch-specific DMG assets (microbridge-ui-<tag>-<target>.dmg) attach to GitHub Releases alongside .app tarballs
  • install-from-release.sh prefers the notarized DMG on macOS; docs cover required secrets

Secrets (already set on DevVig/microbridge)

APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY, KEYCHAIN_PASSWORD, APPLE_API_KEY, APPLE_API_ISSUER, APPLE_API_KEY_P8, APPLE_TEAM_ID

Test plan

  • Merge and tag a release (v0.0.x) or dry-run on this branch via a temporary tag
  • Confirm release assets include signed DMGs for aarch64 + x86_64
  • Download DMG on a clean Mac: open without Gatekeeper block; spctl --assess succeeds
  • ./scripts/install-from-release.sh <tag> installs from DMG path
  • Unsigned fallback still works if signing secrets are removed

Made with Cursor

Summary by CodeRabbit

  • New Features

    • macOS releases can now provide signed and notarized DMG downloads for Apple Silicon and Intel.
    • The macOS installer prefers DMG packages and falls back to legacy archives when needed.
    • Installed apps are automatically configured and launched through the macOS system.
  • Documentation

    • Added guidance for macOS signing, notarization, and required release credentials.
    • Updated installation instructions to describe signed DMGs and latest-release behavior.

Wire Developer ID + App Store Connect API notarization into the
release workflow, ship arch-specific DMGs, and prefer them in the
release installer.
Copilot AI review requested due to automatic review settings July 18, 2026 02:38
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

macOS release automation now optionally signs and notarizes UI DMGs, while Tauri bundle settings provide hardened runtime entitlements. Release documentation and the installer now support DMG-first distribution with tarball fallback.

Changes

macOS release and installation

Layer / File(s) Summary
Bundle hardening and signing configuration
apps/microbridge-ui/src-tauri/Entitlements.plist, apps/microbridge-ui/src-tauri/tauri.conf.json, docs/macos-signing.md
Adds macOS entitlements, enables hardened runtime, references the entitlements file, and documents Developer ID signing and App Store Connect notarization credentials.
Signed build and release packaging
.github/workflows/release.yml
Conditionally imports signing credentials, prepares notarization key material, builds signed DMGs or unsigned apps, validates DMGs, publishes both DMG and tar.gz assets, and updates release notes.
DMG-first UI installation
scripts/install-from-release.sh, INSTALL.md
Adds release-managed app installation and LaunchAgent setup, prefers mounted DMGs, falls back to tar.gz assets, and documents the updated macOS installation behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: copilot

Poem

A bunny hops through signed DMG skies,
With notarized bundles packed just right.
It mounts, installs, and starts the show,
Or finds the tarball fallback below.
Hardened runtime, ears held high—
Release carrots now fly! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: macOS DMG releases are now signed and notarized.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@DevVig
DevVig merged commit 21a8b2f into main Jul 18, 2026
7 of 8 checks passed
@DevVig
DevVig deleted the feat/signed-notarized-dmg branch July 18, 2026 02:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 159-160: Update the release validation commands in the workflow to
fail the job when signing or notarization verification fails: replace the
diagnostic-only spctl and codesign invocations and remove their `|| true`
suppression, using strict deep code-signature verification and stapler
validation for the generated DMG or app artifacts.

In `@INSTALL.md`:
- Around line 86-88: Update the documented macOS DMG filename in INSTALL.md to
use the complete <target> suffix, such as aarch64-apple-darwin, instead of only
<arch>; preserve the existing tag placeholder and filename structure.

In `@scripts/install-from-release.sh`:
- Line 106: Update the release marker assignment in the install script so MARKER
points to a separate state directory outside Microbridge.app, rather than
placing .microbridge-release under DEST. Ensure the directory exists before the
marker is written, while preserving the existing marker read/write behavior.
- Line 117: Remove the xattr command that clears com.apple.quarantine from
install_app_bundle(), including the DMG and tarball installation paths, so the
installed app bundle retains quarantine and macOS can enforce Gatekeeper on
first launch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a0b1ef2d-f005-412c-b740-00357562a050

📥 Commits

Reviewing files that changed from the base of the PR and between 7a17958 and bb8fe42.

📒 Files selected for processing (6)
  • .github/workflows/release.yml
  • INSTALL.md
  • apps/microbridge-ui/src-tauri/Entitlements.plist
  • apps/microbridge-ui/src-tauri/tauri.conf.json
  • docs/macos-signing.md
  • scripts/install-from-release.sh

Comment on lines +159 to +160
spctl --assess --type open --context context:primary-signature "$DMG_OUT" || true
codesign -dv --verbose=2 "$APP" || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '130,190p' .github/workflows/release.yml

Repository: DevVig/microbridge

Length of output: 2591


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/release.yml')
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if 145 <= i <= 170:
        print(f"{i:4}: {line}")
PY

Repository: DevVig/microbridge

Length of output: 1483


🏁 Script executed:

rg -n "spctl|stapler|codesign" .github/workflows/release.yml .github/workflows -g '*.yml' -g '*.yaml'

Repository: DevVig/microbridge

Length of output: 1534


🏁 Script executed:

sed -n '1,220p' .github/workflows/release.yml

Repository: DevVig/microbridge

Length of output: 8789


🌐 Web query:

Apple codesign -dv does not verify integrity spctl assess notarization stapler validate documentation

💡 Result:

The tools codesign, spctl, and stapler serve distinct roles in verifying the integrity and notarization status of macOS software [1][2][3]. It is inaccurate to state that codesign does not verify integrity; rather, it verifies code signature integrity, while other tools verify notarization status [4][3]. 1. codesign: This tool verifies the integrity of the code signature itself [4][3]. It confirms that the binary is signed, the signature is cryptographically sound, and that all sealed components (the executable code and resources) remain unaltered [4]. Running codesign -vvv --deep --strict /path/to/app verifies that the app's signature is structurally and cryptographically valid [1]. 2. spctl: This tool assesses whether the software is authorized by system policies, including Gatekeeper [2][5]. To check if an app is notarized, you use spctl --assess --verbose /path/to/app [1][2]. A successful result indicates that the app is notarized (e.g., source=Notarized Developer ID) [6]. It essentially asks the system policy daemon (syspolicyd) to evaluate the app against current security requirements, which includes checking for a notarization ticket [2]. 3. stapler: This tool is used to attach (staple) a notarization ticket to an app bundle, allowing the app to be verified offline [7][8]. The command stapler validate /path/to/app confirms whether a notarization ticket is successfully stapled to the bundle [9][10]. If an app is notarized but the ticket is not stapled, stapler validate will report that no ticket is found, even if the app is notarized online [3][10]. In summary, codesign confirms the app's internal integrity (that it hasn't been modified since it was signed), while spctl and stapler are used to verify the app's notarization status with Apple [1][2][3]. If you need to confirm both, you must use both categories of tools [2][10].

Citations:


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
text = Path('.github/workflows/release.yml').read_text().splitlines()
for i, line in enumerate(text, 1):
    if 'spctl' in line or 'stapler' in line or 'codesign' in line:
        start = max(1, i-3)
        end = min(len(text), i+3)
        print(f'--- around line {i} ---')
        for j in range(start, end+1):
            print(f'{j:4}: {text[j-1]}')
PY

Repository: DevVig/microbridge

Length of output: 3119


🏁 Script executed:

python3 - <<'PY'
import subprocess, textwrap, json, os, sys
# Probe local tool availability only; do not modify files.
for cmd in [
    ["bash","-lc","command -v gh || true"],
    ["bash","-lc","command -v jq || true"],
]:
    r = subprocess.run(cmd, capture_output=True, text=True)
    print("CMD:", " ".join(cmd))
    print(r.stdout.strip() or "<no stdout>")
PY

Repository: DevVig/microbridge

Length of output: 252


Make release validation fail closed. .github/workflows/release.yml:159-160
spctl --assess and codesign -dv here are only diagnostics, and || true lets the release continue even when the DMG is untrusted or unstapled. Replace this with a real verification step that fails the job, e.g. codesign --verify --deep --strict and stapler validate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 159 - 160, Update the release
validation commands in the workflow to fail the job when signing or notarization
verification fails: replace the diagnostic-only spctl and codesign invocations
and remove their `|| true` suppression, using strict deep code-signature
verification and stapler validation for the generated DMG or app artifacts.

Comment thread INSTALL.md
Comment on lines +86 to +88
arch-specific menu bar app). On macOS, releases also include a
**Developer ID–signed and notarized** DMG
(`microbridge-ui-<tag>-<arch>.dmg`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use <target> in the documented DMG filename.

The published suffix is the complete target triple, such as aarch64-apple-darwin, rather than only the architecture.

-(`microbridge-ui-<tag>-<arch>.dmg`).
+(`microbridge-ui-<tag>-<target>.dmg`, for example
+`microbridge-ui-<tag>-aarch64-apple-darwin.dmg`).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
arch-specific menu bar app). On macOS, releases also include a
**Developer ID–signed and notarized** DMG
(`microbridge-ui-<tag>-<arch>.dmg`).
arch-specific menu bar app). On macOS, releases also include a
**Developer ID–signed and notarized** DMG
(`microbridge-ui-<tag>-<target>.dmg`, for example
`microbridge-ui-<tag>-aarch64-apple-darwin.dmg`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@INSTALL.md` around lines 86 - 88, Update the documented macOS DMG filename in
INSTALL.md to use the complete <target> suffix, such as aarch64-apple-darwin,
instead of only <arch>; preserve the existing tag placeholder and filename
structure.

UI_PLIST="$HOME/Library/LaunchAgents/${UI_LABEL}.plist"
cat >"$UI_PLIST" <<EOF
DEST="$HOME/Applications/Microbridge.app"
MARKER="$DEST/.microbridge-release"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant installer script section with line numbers.
sed -n '80,140p' scripts/install-from-release.sh | cat -n

# Show the full file length so I can judge whether a wider slice is needed.
wc -l scripts/install-from-release.sh

Repository: DevVig/microbridge

Length of output: 2587


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find other macOS installer/signing-related logic.
rg -n --hidden --glob '!**/.git/**' 'codesign|spctl|quarantine|\.microbridge-release|release-managed|MARKER=' .

# Inspect the full installer script around the marker and install flow.
sed -n '1,220p' scripts/install-from-release.sh | cat -n

Repository: DevVig/microbridge

Length of output: 8623


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the release workflow sections that sign the app and build the DMG.
sed -n '120,180p' .github/workflows/release.yml | cat -n

Repository: DevVig/microbridge

Length of output: 3138


Move the release marker out of Microbridge.app. The release workflow signs the app bundle, and writing .microbridge-release after copying mutates that signed bundle and can break Gatekeeper/code-signature validation. Store the marker in a separate state directory instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-from-release.sh` at line 106, Update the release marker
assignment in the install script so MARKER points to a separate state directory
outside Microbridge.app, rather than placing .microbridge-release under DEST.
Ensure the directory exists before the marker is written, while preserving the
existing marker read/write behavior.

rm -rf "$DEST"
mkdir -p "$HOME/Applications"
cp -R "$APP_SRC" "$DEST"
xattr -dr com.apple.quarantine "$DEST" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the installer script around the referenced lines.
git ls-files scripts/install-from-release.sh
wc -l scripts/install-from-release.sh
cat -n scripts/install-from-release.sh | sed -n '90,180p'

Repository: DevVig/microbridge

Length of output: 4260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect surrounding logic and any other quarantine / assessment handling in the repo.
rg -n "xattr -dr com.apple.quarantine|spctl --assess|hdiutil attach|curl -fsSL -o" scripts/install-from-release.sh

Repository: DevVig/microbridge

Length of output: 591


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether release packaging signs/notarizes the macOS artifacts.
rg -n "codesign|notarytool|altool|spctl|hdiutil create|create-dmg|productbuild|pkgbuild|xcrun" .github scripts src .

Repository: DevVig/microbridge

Length of output: 889


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect any release or packaging workflow that may explain whether the DMG is signed/notarized.
fd -a -H -t f .github scripts

Repository: DevVig/microbridge

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the release workflow around DMG creation/assessment.
cat -n .github/workflows/release.yml | sed -n '70,170p'

Repository: DevVig/microbridge

Length of output: 5746


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the installer path that strips quarantine and whether any later step re-adds it.
cat -n scripts/install-from-release.sh | sed -n '145,185p'

Repository: DevVig/microbridge

Length of output: 2075


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if the installer itself documents expected Gatekeeper behavior.
rg -n "quarantine|Gatekeeper|notar|signed DMG|spctl|security" scripts/install-from-release.sh README* .github/workflows/release.yml

Repository: DevVig/microbridge

Length of output: 1877


Keep quarantine on the installed app bundle

install_app_bundle() clears com.apple.quarantine for both the DMG and tarball paths, which suppresses Gatekeeper for the downloaded app. Leave the xattr intact and let macOS enforce trust on first launch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-from-release.sh` at line 117, Remove the xattr command that
clears com.apple.quarantine from install_app_bundle(), including the DMG and
tarball installation paths, so the installed app bundle retains quarantine and
macOS can enforce Gatekeeper on first launch.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the release pipeline to produce Developer ID–signed and notarized macOS DMG assets for the Tauri UI app, and updates installer/docs so macOS users prefer the DMG path when installing from GitHub Releases.

Changes:

  • Add macOS signing + notarization steps in the release workflow and attach .dmg assets alongside existing .tar.gz archives.
  • Update install-from-release.sh to prefer downloading/installing from the DMG on macOS, with archive fallback.
  • Document the DMG install path and required CI secrets (new docs/macos-signing.md), and enable hardened runtime + entitlements in Tauri config.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/install-from-release.sh Prefer installing UI from DMG on macOS; refactors UI install into a helper function.
INSTALL.md Documents notarized DMG assets and install flow on macOS.
docs/macos-signing.md New documentation for CI signing/notarization secrets and process.
apps/microbridge-ui/src-tauri/tauri.conf.json Enables hardened runtime and points to entitlements for macOS bundles.
apps/microbridge-ui/src-tauri/Entitlements.plist Adds macOS entitlements used during signing/hardened runtime.
.github/workflows/release.yml Imports Developer ID cert, configures ASC API key, builds DMG, and publishes DMG assets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +118
DEST="$HOME/Applications/Microbridge.app"
MARKER="$DEST/.microbridge-release"
install_app_bundle() {
local APP_SRC="$1"
if [[ -d "$DEST" && ! -f "$MARKER" && "${MICROBRIDGE_FORCE_APP:-}" != "1" ]]; then
echo " warning: $DEST exists and is not release-managed — leave it"
echo " set MICROBRIDGE_FORCE_APP=1 to replace"
return 0
fi
rm -rf "$DEST"
mkdir -p "$HOME/Applications"
cp -R "$APP_SRC" "$DEST"
xattr -dr com.apple.quarantine "$DEST" 2>/dev/null || true
echo "owned-by-release" >"$MARKER"
Comment on lines +108 to +119
test -n "${APPLE_API_KEY:-}"
test -n "${APPLE_API_ISSUER:-}"
test -n "${APPLE_API_KEY_P8:-}"
KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY}.p8"
printf '%s\n' "$APPLE_API_KEY_P8" > "$KEY_PATH"
chmod 600 "$KEY_PATH"
{
echo "APPLE_API_KEY=$APPLE_API_KEY"
echo "APPLE_API_ISSUER=$APPLE_API_ISSUER"
echo "APPLE_API_KEY_PATH=$KEY_PATH"
echo "APPLE_TEAM_ID=${APPLE_TEAM_ID:-3NQG568C4Q}"
} >> "$GITHUB_ENV"
Comment on lines +158 to +160
echo "Packed signed DMG: $DMG_OUT"
spctl --assess --type open --context context:primary-signature "$DMG_OUT" || true
codesign -dv --verbose=2 "$APP" || true
Comment on lines +5 to +9
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
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.

2 participants