mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
* feat(macos): run the private app runtime on the OpenClaw Bun fork * fix(macos): let the bundled runtime load unsigned plugin addons * test(macos): remove obsolete worker pruning closure tests * test(macos): drop stale packaging test imports * test(macos): migrate CI scope cases to runtime paths
475 lines
17 KiB
Bash
Executable file
475 lines
17 KiB
Bash
Executable file
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Helper imports must not write bytecode caches outside the signing roots.
|
|
export PYTHONDONTWRITEBYTECODE=1
|
|
|
|
APP_BUNDLE="dist/OpenClaw.app"
|
|
SIGNING_VARIANT="${OPENCLAW_MAC_SIGNING_VARIANT:-standard}"
|
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/mac-signing-identity.sh"
|
|
ELEVATION_TEAM_ID="FWJYW4S8P8"
|
|
TIMESTAMP_MODE="${CODESIGN_TIMESTAMP:-auto}"
|
|
CODESIGN_TIMESTAMP_RETRY_ATTEMPTS="${CODESIGN_TIMESTAMP_RETRY_ATTEMPTS:-8}"
|
|
CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS="${CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS:-5}"
|
|
DISABLE_LIBRARY_VALIDATION="${DISABLE_LIBRARY_VALIDATION:-0}"
|
|
SKIP_TEAM_ID_CHECK="${SKIP_TEAM_ID_CHECK:-0}"
|
|
ENT_TMP_DIR=""
|
|
|
|
cleanup() {
|
|
if [[ -n "$ENT_TMP_DIR" ]]; then
|
|
rm -rf "$ENT_TMP_DIR"
|
|
fi
|
|
}
|
|
|
|
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
cat <<'HELP'
|
|
Usage: scripts/codesign-mac-app.sh [app-bundle]
|
|
|
|
Env:
|
|
SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"
|
|
OPENCLAW_MAC_SIGNING_VARIANT=standard|elevation-host
|
|
ALLOW_ADHOC_SIGNING=1
|
|
CODESIGN_TIMESTAMP=auto|on|off
|
|
CODESIGN_TIMESTAMP_RETRY_ATTEMPTS=8
|
|
CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS=5
|
|
DISABLE_LIBRARY_VALIDATION=1 # dev-only Sparkle Team ID workaround
|
|
SKIP_TEAM_ID_CHECK=1 # bypass Team ID audit
|
|
HELP
|
|
exit 0
|
|
fi
|
|
|
|
case "$SIGNING_VARIANT" in
|
|
standard|elevation-host) ;;
|
|
*)
|
|
echo "ERROR: Unknown OPENCLAW_MAC_SIGNING_VARIANT value: $SIGNING_VARIANT (use standard|elevation-host)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [[ "$SIGNING_VARIANT" == "elevation-host" && "$DISABLE_LIBRARY_VALIDATION" == "1" ]]; then
|
|
echo "ERROR: Elevation host signing forbids DISABLE_LIBRARY_VALIDATION=1." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$SIGNING_VARIANT" == "elevation-host" && "$SKIP_TEAM_ID_CHECK" == "1" ]]; then
|
|
echo "ERROR: Elevation host signing forbids SKIP_TEAM_ID_CHECK=1." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${1:-}" == "--" ]]; then
|
|
shift
|
|
fi
|
|
if [[ "$#" -gt 0 ]]; then
|
|
case "$1" in
|
|
-*) echo "ERROR: Unknown codesign option: $1" >&2; exit 1 ;;
|
|
*) APP_BUNDLE="$1"; shift ;;
|
|
esac
|
|
fi
|
|
if [[ "$#" -gt 0 ]]; then
|
|
echo "ERROR: Unexpected codesign argument: $1" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Match scanner paths and expose symlink roots before any validation or signing.
|
|
while [[ "$APP_BUNDLE" == */ && "$APP_BUNDLE" != "/" ]]; do
|
|
APP_BUNDLE="${APP_BUNDLE%/}"
|
|
done
|
|
if [ ! -d "$APP_BUNDLE" ]; then
|
|
echo "App bundle not found: $APP_BUNDLE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# A non-following inventory cannot audit a symlink used as the bundle root.
|
|
if [[ -L "$APP_BUNDLE" ]]; then
|
|
echo "ERROR: App bundle must not be a symlink: $APP_BUNDLE" >&2
|
|
exit 1
|
|
fi
|
|
# Freeze the physical policy root now; resolving it after a swap could authorize the replacement.
|
|
APP_MUTATION_ROOT="$(cd -P -- "$APP_BUNDLE" && pwd -P)"
|
|
|
|
IDENTITY="$(resolve_mac_signing_identity)"
|
|
|
|
echo "Using signing identity: $IDENTITY"
|
|
if [[ "$IDENTITY" == "-" ]]; then
|
|
cat <<'WARN' >&2
|
|
|
|
================================================================================
|
|
!!! AD-HOC SIGNING IN USE - PERMISSIONS WILL NOT STICK (macOS RESTRICTION) !!!
|
|
|
|
macOS ties permissions to the code signature, bundle ID, and app path.
|
|
Ad-hoc signing generates a new signature every build, so macOS treats the app
|
|
as a different binary and will forget permissions (prompts may vanish).
|
|
|
|
For correct permission behavior you MUST sign with a real Apple Development or
|
|
Developer ID certificate.
|
|
|
|
If prompts disappear: remove the app entry in System Settings -> Privacy & Security,
|
|
relaunch the app, and re-grant. Some permissions only reappear after a full
|
|
macOS restart.
|
|
================================================================================
|
|
|
|
WARN
|
|
fi
|
|
|
|
timestamp_arg="--timestamp=none"
|
|
case "$TIMESTAMP_MODE" in
|
|
1|on|yes|true)
|
|
timestamp_arg="--timestamp"
|
|
;;
|
|
0|off|no|false)
|
|
timestamp_arg="--timestamp=none"
|
|
;;
|
|
auto)
|
|
identity_name="$IDENTITY"
|
|
if [[ "$IDENTITY" =~ ^[[:xdigit:]]{40}$ ]]; then
|
|
# A hash selector conceals the certificate class required for notarization timestamps.
|
|
identity_name="$(security find-identity -p codesigning -v 2>/dev/null \
|
|
| awk -v hash="$IDENTITY" 'toupper($2) == toupper(hash) { print }')"
|
|
fi
|
|
if [[ "$identity_name" == *"Developer ID Application"* ]]; then
|
|
timestamp_arg="--timestamp"
|
|
fi
|
|
;;
|
|
*)
|
|
echo "ERROR: Unknown CODESIGN_TIMESTAMP value: $TIMESTAMP_MODE (use auto|on|off)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if [[ "$IDENTITY" == "-" ]]; then
|
|
timestamp_arg="--timestamp=none"
|
|
fi
|
|
|
|
if [[ ! "$CODESIGN_TIMESTAMP_RETRY_ATTEMPTS" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "ERROR: CODESIGN_TIMESTAMP_RETRY_ATTEMPTS must be a positive integer" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! "$CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS must be a nonnegative integer" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ENT_TMP_DIR=$(mktemp -d -t openclaw-entitlements.XXXXXX)
|
|
trap cleanup EXIT
|
|
ENT_TMP_DIR="$(cd -P -- "$ENT_TMP_DIR" && pwd -P)"
|
|
ENT_TMP_APP="$ENT_TMP_DIR/app.plist"
|
|
ENT_TMP_JIT="$ENT_TMP_DIR/jit.plist"
|
|
ENT_TMP_BUN="$ENT_TMP_DIR/bun.plist"
|
|
CODESIGN_OUTPUT="$ENT_TMP_DIR/codesign-output"
|
|
NATIVE_INVENTORY="$ENT_TMP_DIR/native-inventory"
|
|
INVENTORY_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/mac-native-inventory.py"
|
|
MUTATION_SCRIPT="${INVENTORY_SCRIPT%/*}/mac-bundle-mutation.py"
|
|
|
|
options_args=()
|
|
if [[ "$IDENTITY" != "-" ]]; then
|
|
options_args=("--options" "runtime")
|
|
fi
|
|
timestamp_args=("$timestamp_arg")
|
|
|
|
if [[ "$SIGNING_VARIANT" == "elevation-host" ]]; then
|
|
cat > "$ENT_TMP_APP" <<'PLIST'
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict/>
|
|
</plist>
|
|
PLIST
|
|
else
|
|
cat > "$ENT_TMP_APP" <<'PLIST'
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>com.apple.security.automation.apple-events</key>
|
|
<true/>
|
|
<key>com.apple.security.device.audio-input</key>
|
|
<true/>
|
|
<key>com.apple.security.device.camera</key>
|
|
<true/>
|
|
<key>com.apple.security.personal-information.location</key>
|
|
<true/>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
fi
|
|
|
|
if [[ "$DISABLE_LIBRARY_VALIDATION" == "1" ]]; then
|
|
/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
|
|
|
|
APP_ENTITLEMENTS="$ENT_TMP_APP"
|
|
|
|
# The Claude SDK's standalone CLI needs JIT memory but retains library validation.
|
|
cat > "$ENT_TMP_JIT" <<'PLIST'
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0"><dict>
|
|
<key>com.apple.security.cs.allow-jit</key><true/>
|
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
|
|
</dict></plist>
|
|
PLIST
|
|
|
|
# Bun executes arbitrary plugin JS and must load its non-Team-signed native addons.
|
|
# Keep this exception on the private runtime; bundled natives are still Team-signed.
|
|
cat > "$ENT_TMP_BUN" <<'PLIST'
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0"><dict>
|
|
<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><true/>
|
|
</dict></plist>
|
|
PLIST
|
|
|
|
run_bundle_mutation() {
|
|
/usr/bin/python3 "$MUTATION_SCRIPT" "$APP_MUTATION_ROOT" "$ENT_TMP_DIR" "$@"
|
|
}
|
|
|
|
codesign_with_timestamp_retry() {
|
|
local attempt=1
|
|
local command_rc
|
|
local delay
|
|
|
|
while true; do
|
|
: >"$CODESIGN_OUTPUT"
|
|
command_rc=0
|
|
run_bundle_mutation codesign "$@" >"$CODESIGN_OUTPUT" 2>&1 || command_rc=$?
|
|
cat "$CODESIGN_OUTPUT" >&2
|
|
if [[ "$command_rc" -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
if [[ "$timestamp_arg" != "--timestamp" ]] ||
|
|
! grep -Eiq 'A timestamp was expected but was not found|timestamp service is not available' "$CODESIGN_OUTPUT"
|
|
then
|
|
return "$command_rc"
|
|
fi
|
|
if [[ "$attempt" -ge "$CODESIGN_TIMESTAMP_RETRY_ATTEMPTS" ]]; then
|
|
echo "codesign timestamp retry limit reached after $attempt attempts" >&2
|
|
return "$command_rc"
|
|
fi
|
|
|
|
delay=$((CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS * attempt))
|
|
((delay <= 30)) || delay=30
|
|
echo "Transient Apple timestamp failure; retrying codesign in ${delay}s (attempt $((attempt + 1))/$CODESIGN_TIMESTAMP_RETRY_ATTEMPTS)" >&2
|
|
sleep "$delay"
|
|
attempt=$((attempt + 1))
|
|
done
|
|
}
|
|
|
|
sign_item() {
|
|
local target="$1"
|
|
local entitlements="$2"
|
|
codesign_with_timestamp_retry --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --entitlements "$entitlements" --sign "$IDENTITY" "$target"
|
|
}
|
|
|
|
sign_plain_item() {
|
|
local target="$1"
|
|
codesign_with_timestamp_retry --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --sign "$IDENTITY" "$target"
|
|
}
|
|
|
|
codesign_metadata_value() {
|
|
local target="$1" key="$2" metadata
|
|
metadata="$(codesign -dv --verbose=4 "$target" 2>&1)" || {
|
|
local rc=$?
|
|
echo "ERROR: Could not read codesign metadata: $target" >&2
|
|
return "$rc"
|
|
}
|
|
# Executable/Identifier can contain filename newlines. The real CodeDirectory
|
|
# follows Format and resets any injected fields; Authority starts with the leaf.
|
|
awk -F= -v key="$key" '
|
|
/^CodeDirectory / { format = previous; value = ""; found = 0 }
|
|
$1 == key && (!found || key != "Authority") {
|
|
value = substr($0, length(key) + 2); found = 1
|
|
}
|
|
{ previous = $0 }
|
|
END {
|
|
if (format !~ /^Format=.*Mach-O /) exit 1
|
|
print value
|
|
}
|
|
' <<<"$metadata" || {
|
|
echo "ERROR: Native Mach-O signature missing: $target. Rebuild the payload as codesign-compatible native code (thin/fat32); generic signatures are not accepted." >&2
|
|
return 1
|
|
}
|
|
}
|
|
|
|
team_id_for() {
|
|
codesign_metadata_value "$1" TeamIdentifier
|
|
}
|
|
|
|
verify_native_signatures() {
|
|
local expected
|
|
expected="$(team_id_for "$APP_BUNDLE")"
|
|
# A Team ID opt-out never permits generic signatures on native candidates.
|
|
if [[ "$SKIP_TEAM_ID_CHECK" == "1" ]]; then
|
|
echo "Note: skipping Team ID comparison (SKIP_TEAM_ID_CHECK=1); native format checks remain enabled."
|
|
elif [[ -z "$expected" ]]; then
|
|
echo "ERROR: TeamIdentifier missing on app bundle." >&2
|
|
return 1
|
|
fi
|
|
|
|
local mismatches=() kind f team
|
|
while IFS= read -r -d '' kind && IFS= read -r -d '' f; do
|
|
[[ "$kind" == "executable" || "$kind" == "library" ]] || continue
|
|
team="$(team_id_for "$f")"
|
|
if [[ "$SKIP_TEAM_ID_CHECK" != "1" && ( -z "$team" || "$team" != "$expected" ) ]]; then
|
|
mismatches+=("$f (TeamIdentifier=${team:-missing})")
|
|
fi
|
|
done < "$NATIVE_INVENTORY"
|
|
|
|
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
|
|
}
|
|
|
|
assert_no_elevation_cua_driver() {
|
|
[[ "$SIGNING_VARIANT" == "elevation-host" ]] || return 0
|
|
local cua_driver="$APP_BUNDLE/Contents/Resources/cua-driver"
|
|
if [[ -e "$cua_driver" || -L "$cua_driver" ]]; then
|
|
echo "ERROR: Elevation host must not contain bundled CUA driver: $cua_driver" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Sign-time twin of verify_elevation_app in mac-elevation-host.sh, which asserts the same identity
|
|
# invariants but requires an already notarized and stapled bundle. Dropping this check defers every
|
|
# elevation identity failure until after an Apple notarization submission has been spent.
|
|
verify_elevation_signature() {
|
|
[[ "$SIGNING_VARIANT" == "elevation-host" ]] || return 0
|
|
assert_no_elevation_cua_driver
|
|
|
|
local actual_team
|
|
actual_team="$(team_id_for "$APP_BUNDLE")"
|
|
if [[ "$actual_team" != "$ELEVATION_TEAM_ID" ]]; then
|
|
echo "ERROR: Elevation host requires TeamIdentifier=$ELEVATION_TEAM_ID, got '${actual_team:-not set}'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
local authority
|
|
authority="$(codesign_metadata_value "$APP_BUNDLE" Authority)"
|
|
if [[ "$authority" != "$ELEVATION_IDENTITY" ]]; then
|
|
echo "ERROR: Elevation host requires '$ELEVATION_IDENTITY', got '${authority:-not set}'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
assert_no_apple_events_entitlement() {
|
|
local signed_path="$1"
|
|
local entitlements
|
|
entitlements="$(codesign -d --entitlements :- "$signed_path")"
|
|
if /usr/bin/grep -q "com.apple.security.automation.apple-events" <<<"$entitlements"; then
|
|
echo "ERROR: Elevation host code retains Apple Events entitlement: $signed_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
assert_no_apple_events_entitlement "$APP_BUNDLE"
|
|
local kind signed_path
|
|
while IFS= read -r -d '' kind && IFS= read -r -d '' signed_path; do
|
|
[[ "$kind" == "executable" || "$kind" == "library" ]] || continue
|
|
assert_no_apple_events_entitlement "$signed_path"
|
|
done < "$NATIVE_INVENTORY"
|
|
}
|
|
|
|
# Complete the scan before any signing; process substitution hides scanner failures.
|
|
assert_no_elevation_cua_driver
|
|
/usr/bin/python3 "$INVENTORY_SCRIPT" "$APP_BUNDLE" > "$NATIVE_INVENTORY"
|
|
# Shared inodes escape path confinement; special files can block recursive xattr.
|
|
find "$APP_BUNDLE" \( -type f -links +1 -o ! -type f ! -type d ! -type l \) -print0 > "$ENT_TMP_DIR/unsafe-inputs"
|
|
if [[ -s "$ENT_TMP_DIR/unsafe-inputs" ]]; then
|
|
echo "ERROR: Signing requires a private app copy without hardlinked or special files; copy or rebuild the bundle first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Clear attributes only after input validation, inside the same write boundary as signing.
|
|
run_bundle_mutation xattr -cr "$APP_BUNDLE" 2>/dev/null || true
|
|
|
|
# Sign bundled helper binaries before signing the app bundle.
|
|
MAC_CONTROL_CLI="$APP_BUNDLE/Contents/MacOS/openclaw-mac"
|
|
if [ -f "$MAC_CONTROL_CLI" ]; then
|
|
echo "Signing macOS control CLI"; sign_plain_item "$MAC_CONTROL_CLI"
|
|
fi
|
|
|
|
MLX_TTS_HELPER="$APP_BUNDLE/Contents/MacOS/openclaw-mlx-tts"
|
|
if [ -f "$MLX_TTS_HELPER" ]; then
|
|
echo "Signing MLX TTS helper"; sign_plain_item "$MLX_TTS_HELPER"
|
|
fi
|
|
|
|
CUA_DRIVER="$APP_BUNDLE/Contents/Resources/cua-driver"
|
|
if [ -f "$CUA_DRIVER" ]; then
|
|
echo "Signing embedded CUA driver"; sign_plain_item "$CUA_DRIVER"
|
|
fi
|
|
|
|
while IFS= read -r -d '' helper_kind && IFS= read -r -d '' helper_file; do
|
|
[[ "$helper_kind" == "executable" ]] || continue
|
|
[[ "$helper_file" == "$APP_BUNDLE/Contents/Resources/cloudflared/"* ]] || continue
|
|
sign_plain_item "$helper_file"
|
|
codesign --verify --strict "$helper_file"
|
|
done < "$NATIVE_INVENTORY"
|
|
|
|
# Seal all native payloads before the enclosing app; npm packages can carry
|
|
# standalone executables and addons below arbitrarily nested dependency roots.
|
|
RUNTIME_ROOT="$APP_BUNDLE/Contents/Resources/runtime"
|
|
while IFS= read -r -d '' runtime_kind && IFS= read -r -d '' runtime_file; do
|
|
[[ "$runtime_kind" == "executable" || "$runtime_kind" == "library" ]] || continue
|
|
[[ "$runtime_file" == "$RUNTIME_ROOT/"* ]] || continue
|
|
runtime_relative="${runtime_file#"$RUNTIME_ROOT"/}"
|
|
# Only the private Bun runtime hosts arbitrary installed plugin native addons.
|
|
if [[ "$runtime_kind" == "executable" && "$runtime_relative" == bin/bun ]]; then
|
|
sign_item "$runtime_file" "$ENT_TMP_BUN"
|
|
elif [[ "$runtime_kind" == "executable" && (
|
|
"$runtime_relative" == */node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64/claude ||
|
|
"$runtime_relative" == */node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64/claude
|
|
) ]]; then
|
|
sign_item "$runtime_file" "$ENT_TMP_JIT"
|
|
else
|
|
sign_plain_item "$runtime_file"
|
|
fi
|
|
codesign --verify --strict "$runtime_file"
|
|
done < "$NATIVE_INVENTORY"
|
|
|
|
# Sign Sparkle deeply if present
|
|
SPARKLE="$APP_BUNDLE/Contents/Frameworks/Sparkle.framework"
|
|
if [ -d "$SPARKLE" ]; then
|
|
echo "Signing Sparkle framework and helpers"
|
|
while IFS= read -r -d '' kind && IFS= read -r -d '' f; do
|
|
[[ "$kind" == "executable" || "$kind" == "library" ]] || continue
|
|
[[ "$f" == "$SPARKLE/"* ]] || continue
|
|
# Known Sparkle entry points are signed explicitly below. codesign treats
|
|
# bundle main-executable paths as the whole bundle, so don't seal them early.
|
|
case "${f#"$SPARKLE/Versions/B/"}" in
|
|
Sparkle|Autoupdate|Updater.app/Contents/MacOS/Updater|\
|
|
XPCServices/Downloader.xpc/Contents/MacOS/Downloader|\
|
|
XPCServices/Installer.xpc/Contents/MacOS/Installer) continue ;;
|
|
esac
|
|
sign_plain_item "$f"
|
|
done < "$NATIVE_INVENTORY"
|
|
sign_plain_item "$SPARKLE/Versions/B/Autoupdate"
|
|
sign_plain_item "$SPARKLE/Versions/B/Updater.app"
|
|
sign_plain_item "$SPARKLE/Versions/B/XPCServices/Downloader.xpc"
|
|
sign_plain_item "$SPARKLE/Versions/B/XPCServices/Installer.xpc"
|
|
sign_plain_item "$SPARKLE"
|
|
fi
|
|
|
|
# Sign any other embedded frameworks/dylibs
|
|
if [ -d "$APP_BUNDLE/Contents/Frameworks" ]; then
|
|
find "$APP_BUNDLE/Contents/Frameworks" -depth \( -name "*.framework" -o -name "*.dylib" \) ! -path "*Sparkle.framework*" -print0 > "$ENT_TMP_DIR/frameworks"
|
|
while IFS= read -r -d '' f; do
|
|
echo "Signing framework: $f"; sign_plain_item "$f"
|
|
done < "$ENT_TMP_DIR/frameworks"
|
|
fi
|
|
|
|
# The bundle seal also signs its main executable. Signing that path separately
|
|
# would seal every resource twice, with the first seal preceding nested code.
|
|
sign_item "$APP_BUNDLE" "$APP_ENTITLEMENTS"
|
|
|
|
# Signing can create files. Rebuild after the final seal; the two read-only
|
|
# audits share only this completed post-sign inventory, never the pre-sign scan.
|
|
/usr/bin/python3 "$INVENTORY_SCRIPT" "$APP_BUNDLE" > "$NATIVE_INVENTORY"
|
|
verify_native_signatures
|
|
verify_elevation_signature
|
|
|
|
echo "Codesign complete for $APP_BUNDLE"
|