From d5c2114c649e3f596276412717e473c2639c4006 Mon Sep 17 00:00:00 2001 From: nicholassanti Date: Thu, 8 Oct 2026 11:20:51 +0200 Subject: [PATCH 1/2] Fix macOS bundle signatures without requiring an Apple account --- CONTRIBUTING.md | 7 ++++-- Justfile | 2 +- apps/desktop/packaging/packager.json | 1 + apps/desktop/packaging/release.sh | 5 ++++ apps/desktop/packaging/verify-macos.sh | 28 +++++++++++++++++++++ apps/landing/src/content/docs/en/install.md | 2 +- apps/landing/src/content/docs/it/install.md | 2 +- 7 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 apps/desktop/packaging/verify-macos.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e47dc1..0b1235e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,8 +141,11 @@ One-time repository setup, using the pinned `cargo packager` in the devcontainer For notarized macOS packages, also set `APPLE_SIGNING_IDENTITY`, `APPLE_CERTIFICATE` (base64 P12), `APPLE_CERTIFICATE_PASSWORD`, `APPLE_ID`, `APPLE_PASSWORD` (app-specific password) and -`APPLE_TEAM_ID`. Without them macOS packages are unsigned. Windows installers are unsigned -and can trigger SmartScreen. Intel Macs are not supported: ONNX Runtime ships no build for +`APPLE_TEAM_ID`. Without them macOS bundles receive an ad-hoc signature: no Apple account +is needed, but users must approve the app with **Open Anyway** in **System Settings › Privacy +& Security**. The release verifies the bundle signatures inside both the DMG and updater +archive; updater `.sig` files are a separate signature, not Apple signing. Windows installers +are unsigned and can trigger SmartScreen. Intel Macs are not supported: ONNX Runtime ships no build for them. For a local signed package, export the variables above, set diff --git a/Justfile b/Justfile index 2a9ee6a..e3e2af0 100644 --- a/Justfile +++ b/Justfile @@ -258,7 +258,7 @@ agents-usage *args: # Lint the devcontainer and packaging shell scripts. check-shell: - shellcheck -x .devcontainer/initialize.sh .devcontainer/shell-env.sh .devcontainer/desktop/*.sh apps/desktop/packaging/release.sh + shellcheck -x .devcontainer/initialize.sh .devcontainer/shell-env.sh .devcontainer/desktop/*.sh apps/desktop/packaging/*.sh # Prepare a recording for a PR: first 15 seconds, no audio, at most 8 MB. Use .mp4 or .gif. [positional-arguments] diff --git a/apps/desktop/packaging/packager.json b/apps/desktop/packaging/packager.json index e75d7ac..943d108 100644 --- a/apps/desktop/packaging/packager.json +++ b/apps/desktop/packaging/packager.json @@ -8,6 +8,7 @@ "icons": ["apps/desktop/packaging/icon.png"], "binaries": [{"path": "study", "main": true}], "macos": { + "signingIdentity": "-", "minimumSystemVersion": "12.0", "infoPlistPath": "apps/desktop/packaging/Info.plist", "entitlements": "apps/desktop/packaging/Entitlements.plist" diff --git a/apps/desktop/packaging/release.sh b/apps/desktop/packaging/release.sh index d5f1156..3378a72 100755 --- a/apps/desktop/packaging/release.sh +++ b/apps/desktop/packaging/release.sh @@ -46,6 +46,11 @@ package() { export APPIMAGE_EXTRACT_AND_RUN=1 cargo build --locked --release -p study --target "$target" cargo packager --config "$config" --formats "$(IFS=,; echo "${formats[*]}")" + # Updater signatures do not validate Apple bundle signatures. Check the distributed + # bundles, not the executable (which can run even when Finder rejects the app). + if [[ $format == app ]]; then + bash apps/desktop/packaging/verify-macos.sh "$out"/*.app.tar.gz "$out"/*.dmg + fi local extensions=("$suffix") [[ $format == app ]] && extensions+=(.dmg) for extension in "${extensions[@]}"; do diff --git a/apps/desktop/packaging/verify-macos.sh b/apps/desktop/packaging/verify-macos.sh new file mode 100644 index 0000000..75ed6b2 --- /dev/null +++ b/apps/desktop/packaging/verify-macos.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Verify the bundles users receive, including the updater archive. An ad-hoc signature +# is valid without an Apple account; Gatekeeper still needs the user's Open Anyway approval. +set -euo pipefail +[[ $# == 2 ]] || { echo "usage: $0 APP.tar.gz INSTALLER.dmg" >&2; exit 1; } +work=$(mktemp -d) +mounted=false +cleanup() { + if [[ $mounted == true ]]; then + hdiutil detach "$work/mount" -quiet + fi + rm -rf "$work" +} +trap cleanup EXIT + +verify() { + local apps=("$1"/*.app) + [[ ${#apps[@]} == 1 && -d ${apps[0]} ]] \ + || { echo "expected one app bundle in $1" >&2; exit 1; } + codesign --verify --deep --strict --verbose=2 "${apps[0]}" +} + +mkdir "$work/archive" "$work/mount" +tar -xzf "$1" -C "$work/archive" +verify "$work/archive" +hdiutil attach "$2" -readonly -nobrowse -mountpoint "$work/mount" -quiet +mounted=true +verify "$work/mount" diff --git a/apps/landing/src/content/docs/en/install.md b/apps/landing/src/content/docs/en/install.md index 03644e4..b9cf664 100644 --- a/apps/landing/src/content/docs/en/install.md +++ b/apps/landing/src/content/docs/en/install.md @@ -25,7 +25,7 @@ chmod +x study-linux-x86_64.AppImage The Linux builds are made on Ubuntu 24.04, so they need a distribution of about that age or newer. -On **macOS**, open the `.dmg` and drag Study into Applications. Study needs macOS 12 or later on an Apple Silicon Mac. There is no build for Intel Macs. +On **macOS**, open the `.dmg` and drag Study into Applications. Study needs macOS 12 or later on an Apple Silicon Mac. There is no build for Intel Macs. If macOS blocks Study because the developer cannot be verified, first try opening it, then go to **System Settings › Privacy & Security**, choose **Open Anyway**, and confirm. Only approve a copy you downloaded from this project's releases. On **Windows**, run the `.exe` installer and follow its steps. diff --git a/apps/landing/src/content/docs/it/install.md b/apps/landing/src/content/docs/it/install.md index c03c030..61ba4b9 100644 --- a/apps/landing/src/content/docs/it/install.md +++ b/apps/landing/src/content/docs/it/install.md @@ -25,7 +25,7 @@ chmod +x study-linux-x86_64.AppImage Le versioni per Linux sono compilate su Ubuntu 24.04, quindi serve una distribuzione di quell'epoca o più recente. -Su **macOS** apri il `.dmg` e trascina Study in Applicazioni. Study richiede macOS 12 o successivo su un Mac con Apple Silicon. Non esiste una versione per i Mac Intel. +Su **macOS** apri il `.dmg` e trascina Study in Applicazioni. Study richiede macOS 12 o successivo su un Mac con Apple Silicon. Non esiste una versione per i Mac Intel. Se macOS blocca Study perché non può verificare lo sviluppatore, prova prima ad aprirlo, poi vai in **Impostazioni di Sistema › Privacy e sicurezza**, scegli **Apri comunque** e conferma. Autorizza solo una copia scaricata dalle release di questo progetto. Su **Windows** avvia l'installer `.exe` e segui i passaggi. From 3f9a40398a493c03d16172196df5ce41a97e05c8 Mon Sep 17 00:00:00 2001 From: nicholassanti Date: Thu, 8 Oct 2026 11:30:36 +0200 Subject: [PATCH 2/2] Ignore empty Apple release credentials before packaging --- apps/desktop/packaging/release.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/desktop/packaging/release.sh b/apps/desktop/packaging/release.sh index 3378a72..8b4c2d9 100755 --- a/apps/desktop/packaging/release.sh +++ b/apps/desktop/packaging/release.sh @@ -28,6 +28,16 @@ package() { : "${CARGO_PACKAGER_SIGN_PRIVATE_KEY:?is required for signed packages}" : "${STUDY_UPDATE_PUBLIC_KEY:?is required in release binaries}" : "${STUDY_UPDATE_ENDPOINT:?is required in release binaries}" + if [[ $format == app ]]; then + # GitHub exports missing secrets as empty strings; packager checks their presence. + if [[ -z ${APPLE_CERTIFICATE:-} ]]; then + unset APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD + fi + local credential + for credential in APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID; do + [[ -n ${!credential:-} ]] || unset "$credential" + done + fi local target_dir out version config target_dir=${CARGO_TARGET_DIR:-target} out="$target_dir/packages/$1"