mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: 避免 macOS unsigned 包携带残缺签名 (#2101)
* fix: mitigate broken signatures in unsigned macOS packages * fix: normalize app signatures before DMG packaging
This commit is contained in:
@@ -128,9 +128,12 @@ popd >/dev/null
|
||||
|
||||
cp -R "${ROOT_DIR}/dist/stock_analysis" "${ROOT_DIR}/dist/backend/stock_analysis"
|
||||
|
||||
log "Verifying packaged runtime imports..."
|
||||
packaged_root="${ROOT_DIR}/dist/backend/stock_analysis"
|
||||
|
||||
log "Removing invalid signatures before the packaged backend is executed..."
|
||||
bash "${SCRIPT_DIR}/macos-signature-audit.sh" normalize "${packaged_root}"
|
||||
|
||||
log "Verifying packaged runtime imports..."
|
||||
packaged_entry="${packaged_root}/stock_analysis"
|
||||
if [[ ! -x "${packaged_entry}" ]]; then
|
||||
echo "ERROR: packaged backend entrypoint not found or not executable: ${packaged_entry}."
|
||||
|
||||
@@ -7,6 +7,69 @@ ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
export CSC_IDENTITY_AUTO_DISCOVERY="false"
|
||||
export ELECTRON_BUILDER_CACHE="${ROOT_DIR}/.electron-builder-cache"
|
||||
|
||||
verify_expected_unsigned_app() {
|
||||
local app_path="$1"
|
||||
local signature_details=""
|
||||
local assessment_output=""
|
||||
|
||||
bash "${SCRIPT_DIR}/macos-signature-audit.sh" check "${app_path}"
|
||||
|
||||
if signature_details="$(codesign -d "${app_path}" 2>&1)"; then
|
||||
echo "ERROR: expected an unsigned application bundle, but a signature was found: ${app_path}"
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${signature_details}" != *"code object is not signed at all"* ]]; then
|
||||
echo "ERROR: application bundle has an unreadable or invalid signature: ${app_path}"
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if assessment_output="$(spctl --assess --type execute --verbose=4 "${app_path}" 2>&1)"; then
|
||||
echo "WARNING: Gatekeeper accepted an explicitly unsigned application: ${app_path}"
|
||||
echo "${assessment_output}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "${assessment_output}"
|
||||
if [[ "${assessment_output}" == *"code has no resources but signature indicates they must be present"* ]]; then
|
||||
echo "ERROR: Gatekeeper detected the broken-signature defect reported in issue #2075." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "WARNING: Gatekeeper rejection is expected because this build has no Apple Developer signature."
|
||||
}
|
||||
|
||||
verify_unsigned_dmg() {
|
||||
local dmg_path="$1"
|
||||
local mount_dir=""
|
||||
local mounted_app=""
|
||||
local mounted=false
|
||||
|
||||
mount_dir="$(mktemp -d "${TMPDIR:-/tmp}/dsa-unsigned-dmg.XXXXXX")"
|
||||
cleanup_mount() {
|
||||
if [[ "${mounted}" == "true" ]]; then
|
||||
hdiutil detach "${mount_dir}" >/dev/null || true
|
||||
fi
|
||||
rmdir "${mount_dir}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup_mount EXIT
|
||||
|
||||
hdiutil attach "${dmg_path}" -nobrowse -readonly -mountpoint "${mount_dir}" >/dev/null
|
||||
mounted=true
|
||||
mounted_app="${mount_dir}/Daily Stock Analysis.app"
|
||||
if [[ ! -d "${mounted_app}" ]]; then
|
||||
echo "ERROR: application bundle not found in mounted DMG: ${mounted_app}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_expected_unsigned_app "${mounted_app}"
|
||||
|
||||
hdiutil detach "${mount_dir}" >/dev/null
|
||||
mounted=false
|
||||
rmdir "${mount_dir}"
|
||||
trap - EXIT
|
||||
}
|
||||
|
||||
echo "Building Electron desktop app (macOS)..."
|
||||
|
||||
if [[ ! -d "${ROOT_DIR}/dist/backend/stock_analysis" ]]; then
|
||||
@@ -64,6 +127,10 @@ if compgen -G "dist/mac*" >/dev/null; then
|
||||
echo "Cleaning dist/mac*..."
|
||||
rm -rf dist/mac*
|
||||
fi
|
||||
if compgen -G "dist/*.dmg" >/dev/null; then
|
||||
echo "Cleaning stale dist/*.dmg..."
|
||||
rm -f dist/*.dmg
|
||||
fi
|
||||
|
||||
MAC_ARCH="${DSA_MAC_ARCH:-}"
|
||||
ARCH_ARGS=()
|
||||
@@ -85,6 +152,24 @@ if [[ ${#ARCH_ARGS[@]} -gt 0 ]]; then
|
||||
else
|
||||
npx electron-builder --mac dmg --publish never
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
app_candidates=(dist/mac*/"Daily Stock Analysis.app")
|
||||
dmg_candidates=(dist/*.dmg)
|
||||
shopt -u nullglob
|
||||
|
||||
if [[ "${#app_candidates[@]}" -ne 1 ]]; then
|
||||
echo "ERROR: expected one unpacked macOS app, found ${#app_candidates[@]}."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${#dmg_candidates[@]}" -ne 1 ]]; then
|
||||
echo "ERROR: expected one macOS DMG, found ${#dmg_candidates[@]}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_expected_unsigned_app "${app_candidates[0]}"
|
||||
verify_unsigned_dmg "${dmg_candidates[0]}"
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
echo "Desktop build completed."
|
||||
|
||||
93
scripts/macos-signature-audit.sh
Executable file
93
scripts/macos-signature-audit.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <normalize|check> <artifact-path>" >&2
|
||||
}
|
||||
|
||||
mode="${1:-}"
|
||||
artifact_root="${2:-}"
|
||||
|
||||
if [[ "${mode}" != "normalize" ]] && [[ "${mode}" != "check" ]]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "${artifact_root}" ]] || [[ ! -e "${artifact_root}" ]]; then
|
||||
echo "ERROR: macOS signature audit target does not exist: ${artifact_root:-<empty>}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v codesign >/dev/null 2>&1; then
|
||||
echo "ERROR: codesign is required for macOS signature auditing." >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v file >/dev/null 2>&1; then
|
||||
echo "ERROR: file is required for macOS signature auditing." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
checked_count=0
|
||||
signed_count=0
|
||||
removed_count=0
|
||||
|
||||
remove_broken_signature() {
|
||||
local candidate="$1"
|
||||
local signature_details=""
|
||||
|
||||
echo "WARNING: removing invalid signature from unsigned macOS artifact: ${candidate}"
|
||||
codesign --remove-signature "${candidate}"
|
||||
removed_count=$((removed_count + 1))
|
||||
|
||||
signature_details="$(codesign -d "${candidate}" 2>&1 || true)"
|
||||
if [[ "${signature_details}" != *"code object is not signed at all"* ]]; then
|
||||
echo "ERROR: failed to remove invalid signature: ${candidate}" >&2
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
audit_candidate() {
|
||||
local candidate="$1"
|
||||
local signature_details=""
|
||||
|
||||
checked_count=$((checked_count + 1))
|
||||
if ! signature_details="$(codesign -d "${candidate}" 2>&1)"; then
|
||||
if [[ "${signature_details}" == *"code object is not signed at all"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "${mode}" == "normalize" ]]; then
|
||||
remove_broken_signature "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
echo "ERROR: unreadable or invalid signature in macOS artifact: ${candidate}" >&2
|
||||
echo "${signature_details}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
signed_count=$((signed_count + 1))
|
||||
if codesign --verify --strict --verbose=4 "${candidate}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${mode}" == "normalize" ]]; then
|
||||
remove_broken_signature "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "ERROR: invalid signature in macOS artifact: ${candidate}" >&2
|
||||
codesign --verify --strict --verbose=4 "${candidate}" || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
while IFS= read -r -d '' candidate; do
|
||||
if [[ -d "${candidate}" ]]; then
|
||||
audit_candidate "${candidate}"
|
||||
elif file -b "${candidate}" | grep -q "Mach-O"; then
|
||||
audit_candidate "${candidate}"
|
||||
fi
|
||||
done < <(
|
||||
find "${artifact_root}" -depth \
|
||||
\( -type f -o -type d \( -name "*.app" -o -name "*.framework" -o -name "*.xpc" \) \) \
|
||||
-print0
|
||||
)
|
||||
|
||||
echo "macOS signature audit complete: mode=${mode}, checked=${checked_count}, signed=${signed_count}, removed=${removed_count}"
|
||||
Reference in New Issue
Block a user