diff --git a/apps/macos/README.md b/apps/macos/README.md new file mode 100644 index 000000000..ae35b772e --- /dev/null +++ b/apps/macos/README.md @@ -0,0 +1,64 @@ +# Clawdbot macOS app (dev + signing) + +## Quick dev run + +```bash +# from repo root +scripts/restart-mac.sh +``` + +Options: + +```bash +scripts/restart-mac.sh --no-sign # fastest dev; ad-hoc signing (TCC permissions do not stick) +scripts/restart-mac.sh --sign # force code signing (requires cert) +``` + +## Packaging flow + +```bash +scripts/package-mac-app.sh +``` + +Creates `dist/Clawdbot.app` and signs it via `scripts/codesign-mac-app.sh`. + +## Signing behavior + +Auto-selects identity (first match): +1) Developer ID Application +2) Apple Distribution +3) Apple Development +4) first available identity + +If none found: +- errors by default +- set `ALLOW_ADHOC_SIGNING=1` or `SIGN_IDENTITY="-"` to ad-hoc sign + +## Team ID audit (Sparkle mismatch guard) + +After signing, we read the app bundle Team ID and compare every Mach-O inside the app. +If any embedded binary has a different Team ID, signing fails. + +Skip the audit: +```bash +SKIP_TEAM_ID_CHECK=1 scripts/package-mac-app.sh +``` + +## Library validation workaround (dev only) + +If Sparkle Team ID mismatch blocks loading (common with Apple Development certs), opt in: + +```bash +DISABLE_LIBRARY_VALIDATION=1 scripts/package-mac-app.sh +``` + +This adds `com.apple.security.cs.disable-library-validation` to app entitlements. +Use for local dev only; keep off for release builds. + +## Useful env flags + +- `SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"` +- `ALLOW_ADHOC_SIGNING=1` (ad-hoc, TCC permissions do not persist) +- `CODESIGN_TIMESTAMP=off` (offline debug) +- `DISABLE_LIBRARY_VALIDATION=1` (dev-only Sparkle workaround) +- `SKIP_TEAM_ID_CHECK=1` (bypass audit) diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md index fb63e9406..01e65da7d 100644 --- a/docs/platforms/mac/dev-setup.md +++ b/docs/platforms/mac/dev-setup.md @@ -32,6 +32,9 @@ To build the macOS app and package it into `dist/Clawdbot.app`, run: If you don't have an Apple Developer ID certificate, the script will automatically use **ad-hoc signing** (`-`). +For dev run modes, signing flags, and Team ID troubleshooting, see the macOS app README: +https://github.com/clawdbot/clawdbot/blob/main/apps/macos/README.md + > **Note**: Ad-hoc signed apps may trigger security prompts. If the app crashes immediately with "Abort trap 6", see the [Troubleshooting](#troubleshooting) section. ## 3. Install the CLI diff --git a/docs/platforms/mac/signing.md b/docs/platforms/mac/signing.md index a1bdfbbac..de549bf57 100644 --- a/docs/platforms/mac/signing.md +++ b/docs/platforms/mac/signing.md @@ -14,6 +14,7 @@ This app is usually built from [`scripts/package-mac-app.sh`](https://github.com - inject build metadata into Info.plist: `ClawdbotBuildTimestamp` (UTC) and `ClawdbotGitCommit` (short hash) so the About pane can show build, git, and debug/release channel. - **Packaging requires Node 22+**: the script runs TS builds and the Control UI build. - reads `SIGN_IDENTITY` from the environment. Add `export SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"` (or your Developer ID Application cert) to your shell rc to always sign with your cert. Ad-hoc signing requires explicit opt-in via `ALLOW_ADHOC_SIGNING=1` or `SIGN_IDENTITY="-"` (not recommended for permission testing). +- runs a Team ID audit after signing and fails if any Mach-O inside the app bundle is signed by a different Team ID. Set `SKIP_TEAM_ID_CHECK=1` to bypass. ## Usage @@ -23,6 +24,7 @@ scripts/package-mac-app.sh # auto-selects identity; errors if none SIGN_IDENTITY="Developer ID Application: Your Name" scripts/package-mac-app.sh # real cert ALLOW_ADHOC_SIGNING=1 scripts/package-mac-app.sh # ad-hoc (permissions will not stick) SIGN_IDENTITY="-" scripts/package-mac-app.sh # explicit ad-hoc (same caveat) +DISABLE_LIBRARY_VALIDATION=1 scripts/package-mac-app.sh # dev-only Sparkle Team ID mismatch workaround ``` ### Ad-hoc Signing Note diff --git a/scripts/codesign-mac-app.sh b/scripts/codesign-mac-app.sh index ed895272e..318fc8d60 100755 --- a/scripts/codesign-mac-app.sh +++ b/scripts/codesign-mac-app.sh @@ -4,11 +4,28 @@ set -euo pipefail APP_BUNDLE="${1:-dist/Clawdbot.app}" IDENTITY="${SIGN_IDENTITY:-}" TIMESTAMP_MODE="${CODESIGN_TIMESTAMP:-auto}" +DISABLE_LIBRARY_VALIDATION="${DISABLE_LIBRARY_VALIDATION:-0}" +SKIP_TEAM_ID_CHECK="${SKIP_TEAM_ID_CHECK:-0}" ENT_TMP_BASE=$(mktemp -t clawdbot-entitlements-base.XXXXXX) ENT_TMP_APP=$(mktemp -t clawdbot-entitlements-app.XXXXXX) ENT_TMP_APP_BASE=$(mktemp -t clawdbot-entitlements-app-base.XXXXXX) ENT_TMP_RUNTIME=$(mktemp -t clawdbot-entitlements-runtime.XXXXXX) +if [[ "${APP_BUNDLE}" == "--help" || "${APP_BUNDLE}" == "-h" ]]; then + cat <<'HELP' +Usage: scripts/codesign-mac-app.sh [app-bundle] + +Env: + SIGN_IDENTITY="Apple Development: Your Name (TEAMID)" + ALLOW_ADHOC_SIGNING=1 + CODESIGN_TIMESTAMP=auto|on|off + DISABLE_LIBRARY_VALIDATION=1 # dev-only Sparkle Team ID workaround + SKIP_TEAM_ID_CHECK=1 # bypass Team ID audit + ENABLE_TIME_SENSITIVE_NOTIFICATIONS=1 +HELP + exit 0 +fi + if [ ! -d "$APP_BUNDLE" ]; then echo "App bundle not found: $APP_BUNDLE" >&2 exit 1 @@ -184,6 +201,14 @@ cat > "$ENT_TMP_APP" <<'PLIST' PLIST +if [[ "$DISABLE_LIBRARY_VALIDATION" == "1" ]]; then + /usr/libexec/PlistBuddy -c "Add :com.apple.security.cs.disable-library-validation bool true" "$ENT_TMP_APP_BASE" >/dev/null 2>&1 || \ + /usr/libexec/PlistBuddy -c "Set :com.apple.security.cs.disable-library-validation true" "$ENT_TMP_APP_BASE" + /usr/libexec/PlistBuddy -c "Add :com.apple.security.cs.disable-library-validation bool true" "$ENT_TMP_APP" >/dev/null 2>&1 || \ + /usr/libexec/PlistBuddy -c "Set :com.apple.security.cs.disable-library-validation true" "$ENT_TMP_APP" + echo "Note: disable-library-validation entitlement enabled (DISABLE_LIBRARY_VALIDATION=1)." +fi + # The time-sensitive entitlement is restricted and requires explicit enablement # (and typically a matching provisioning profile). It is *not* safe to enable # unconditionally for local debug packaging since AMFI will refuse to launch. @@ -209,6 +234,51 @@ sign_plain_item() { codesign --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --sign "$IDENTITY" "$target" } +team_id_for() { + codesign -dv --verbose=4 "$1" 2>&1 | awk -F= '/^TeamIdentifier=/{print $2; exit}' +} + +verify_team_ids() { + if [[ "$SKIP_TEAM_ID_CHECK" == "1" ]]; then + echo "Note: skipping Team ID audit (SKIP_TEAM_ID_CHECK=1)." + return 0 + fi + + local expected + expected="$(team_id_for "$APP_BUNDLE" || true)" + if [[ -z "$expected" ]]; then + echo "WARN: TeamIdentifier missing on app bundle; skipping Team ID audit." + return 0 + fi + + local mismatches=() + while IFS= read -r -d '' f; do + if /usr/bin/file "$f" | /usr/bin/grep -q "Mach-O"; then + local team + team="$(team_id_for "$f" || true)" + if [[ -z "$team" ]]; then + team="not set" + fi + if [[ "$expected" == "not set" ]]; then + if [[ "$team" != "not set" ]]; then + mismatches+=("$f (TeamIdentifier=$team)") + fi + elif [[ "$team" != "$expected" ]]; then + mismatches+=("$f (TeamIdentifier=$team)") + fi + fi + done < <(find "$APP_BUNDLE" -type f -print0) + + if [[ "${#mismatches[@]}" -gt 0 ]]; then + echo "ERROR: Team ID mismatch detected (expected: $expected)" + for entry in "${mismatches[@]}"; do + echo " - $entry" + done + echo "Hint: re-sign embedded frameworks or set DISABLE_LIBRARY_VALIDATION=1 for dev builds." + exit 1 + fi +} + # Sign main binary if [ -f "$APP_BUNDLE/Contents/MacOS/Clawdbot" ]; then echo "Signing main binary"; sign_item "$APP_BUNDLE/Contents/MacOS/Clawdbot" "$APP_ENTITLEMENTS" @@ -218,6 +288,11 @@ fi SPARKLE="$APP_BUNDLE/Contents/Frameworks/Sparkle.framework" if [ -d "$SPARKLE" ]; then echo "Signing Sparkle framework and helpers" + find "$SPARKLE" -type f -print0 | while IFS= read -r -d '' f; do + if /usr/bin/file "$f" | /usr/bin/grep -q "Mach-O"; then + sign_plain_item "$f" + fi + done sign_plain_item "$SPARKLE/Versions/B/Sparkle" sign_plain_item "$SPARKLE/Versions/B/Autoupdate" sign_plain_item "$SPARKLE/Versions/B/Updater.app/Contents/MacOS/Updater" @@ -240,5 +315,7 @@ fi # Finally sign the bundle sign_item "$APP_BUNDLE" "$APP_ENTITLEMENTS" +verify_team_ids + rm -f "$ENT_TMP_BASE" "$ENT_TMP_APP_BASE" "$ENT_TMP_APP" "$ENT_TMP_RUNTIME" echo "Codesign complete for $APP_BUNDLE"