fix: harden macOS signing flow
This commit is contained in:
64
apps/macos/README.md
Normal file
64
apps/macos/README.md
Normal file
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user