第一章:Apple审核拒绝Go Mac App的核心原因全景图
Apple 对 Mac App Store 上架应用的审核极为严格,尤其对使用 Go 语言构建的原生 macOS 应用存在系统性拒审倾向。这并非源于 Go 语言本身缺陷,而是其默认构建行为与 Apple 的沙盒、签名、隐私及运行时规范存在多处深层冲突。
沙盒与权限模型不兼容
Go 默认编译生成静态链接二进制文件,不依赖系统动态库(如 libsystem),但 macOS 要求所有 App 必须启用 App Sandbox,并显式声明 com.apple.security.app-sandbox 权限。若未在 entitlements.plist 中正确配置,即使代码逻辑合法,也会触发 ITMS-90299 错误。示例 entitlements 配置需包含:
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
动态符号解析违反 hardened runtime
Go 运行时通过 dlsym 动态加载部分系统符号(如 getentropy),而启用 Hardened Runtime 后,Apple 禁止此类非白名单动态调用。解决方案是禁用 CGO 并使用 -ldflags="-buildmode=exe -s -w" 编译,或在 main.go 顶部添加 //go:build !cgo 构建约束。
隐私描述缺失触发强制拒审
即使应用未访问敏感 API,只要二进制中存在 NSCameraUsageDescription 等字符串引用(Go 标准库或第三方包可能隐式引入),App Store 就会要求提供对应 Info.plist 描述字段。常见被扫描的关键词包括:camera, microphone, location, contacts。
代码签名与公证链断裂
Go 构建产物需经三步签名验证:
- 使用 Developer ID Application 证书签名可执行文件
- 对
.app包整体签名:codesign --force --deep --sign "Developer ID Application: XXX" MyApp.app - 提交公证服务:
xcrun notarytool submit MyApp.zip --keychain-profile "AC_PASSWORD" --wait
未完成任一环节,均导致 ITMS-90338 或 ITMS-90534 错误。Apple 官方明确指出:“Go-built apps must undergo full notarization — ad-hoc signing is insufficient.”
第二章:Bundle Identifier与签名体系的隐性校验逻辑
2.1 Bundle ID唯一性验证:Go构建流程中CFBundleIdentifier的动态注入陷阱
在 macOS/iOS 应用打包阶段,CFBundleIdentifier 必须全局唯一,但 Go 项目常通过 ldflags 动态注入该值,易引发重复或非法字符问题。
构建时注入的典型错误模式
go build -ldflags "-X main.BundleID=io.example.app" -o app .
⚠️ 此命令未校验 BundleID 格式合法性(如含下划线、大写字母、以数字开头),导致 Info.plist 写入后被系统拒绝签名。
合法性校验建议清单
- ✅ 仅含小写字母、数字、点号和连字符
- ✅ 以字母开头,不以连字符结尾
- ✅ 至少包含一个点号(如
com.example.app) - ❌ 禁止空格、下划线、Unicode 字符
安全注入流程(mermaid)
graph TD
A[读取环境变量 BUNDLE_ID] --> B{正则校验 /^[a-z0-9]([a-z0-9\-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]*[a-z0-9])?)*$/}
B -->|匹配| C[写入 Info.plist]
B -->|失败| D[构建中断并报错]
验证代码片段
import "regexp"
var bundleIDRe = regexp.MustCompile(`^[a-z0-9]([a-z0-9\-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]*[a-z0-9])?)*$`)
if !bundleIDRe.MatchString(os.Getenv("BUNDLE_ID")) {
log.Fatal("invalid CFBundleIdentifier format")
}
该正则强制遵循 Apple Bundle Identifier Specification,确保注入值可被 Xcode 和 codesign 正确识别。
2.2 签名链完整性分析:codesign –verify在Go二进制嵌套资源中的失效场景
Go 构建的二进制默认不嵌入 CodeResources,且静态链接导致 __LINKEDIT 区段无传统签名锚点。codesign --verify 依赖 Mach-O 的 LC_CODE_SIGNATURE 命令与 CodeDirectory 结构,但对 Go 生成的嵌套资源(如 embed.FS 中的 .dylib 或 plist)无法递归校验。
失效根源
- Go 编译器不触发
codesign自动签名链注入 --deep参数对embed.FS内部文件完全无效codesign --verify -vvv仅检查主 Mach-O,忽略data段中序列化的资源
典型验证失败示例
# 对含 embed.FS 的 Go 二进制执行
codesign --verify -vvv ./app
# 输出:valid on disk, satisfies its Designated Requirement → 误导性通过!
该命令未扫描 __DATA,__go_embed 段内二进制资源,签名链实际断裂。
| 检查项 | Mach-O 主体 | embed.FS 内 .dylib | 资源 plist |
|---|---|---|---|
codesign --verify |
✅ | ❌(完全跳过) | ❌ |
手动 codesign -d |
✅ | ✅(需提取后单独验) | ✅ |
graph TD
A[Go build] --> B[静态链接Mach-O]
B --> C[embed.FS序列化至__DATA]
C --> D[codesign --verify]
D --> E[仅解析LC_CODE_SIGNATURE]
E --> F[忽略__DATA段内资源]
F --> G[签名链完整性丢失]
2.3 Team ID绑定一致性:go build -ldflags与Xcode项目配置的双重校验冲突
当使用 Go 构建 macOS/iOS 原生应用(如通过 golang.org/x/mobile 或 flutter-go 混合方案)时,签名体系要求 Team ID 必须在二进制层与 Xcode 工程层严格一致,否则触发 Code Signing Error: Provisioning profile doesn't match signing identity。
冲突根源
- Go 编译器通过
-ldflags="-H=macos"生成 Mach-O 二进制,但不嵌入 Team ID - Xcode 在
Signing & Capabilities中配置的 Team ID 仅作用于打包阶段(codesign --force --sign "Apple Development: ..."),无法反向修正 Go 生成的二进制签名标识
典型错误配置示例
# ❌ 错误:硬编码签名标识,绕过 Xcode 管理
go build -ldflags="-s -w -H=macos" -o MyApp.app/Contents/MacOS/MyApp .
该命令未传递任何 Team ID 相关元数据,导致 codesign -dv MyApp.app 显示 Identifier=(空),与 Xcode 配置的 com.example.myapp 不匹配。
双重校验流程
graph TD
A[go build 输出无 Team ID 的 Mach-O] --> B[Xcode Archive 阶段注入签名]
B --> C{codesign 校验 Bundle ID + Team ID}
C -->|不一致| D[Build Failed: provisioning profile mismatch]
C -->|一致| E[Archive Success]
正确协同方式
- ✅ 在
main.go中通过//go:build darwin条件编译注入CFBundleIdentifier(需配合 Info.plist) - ✅ 使用
xcodebuild -exportArchive前确保go build产出的可执行文件已由 Xcode 重签名(非手动codesign) - ✅ 在 Xcode 的
Build Settings → Code Signing Identity中统一管理证书链,避免-ldflags干预签名流程
2.4 Hardened Runtime启用实践:CGO_ENABLED=1环境下Mach-O LC_RPATH的自动注入风险
当 CGO_ENABLED=1 时,Go 构建链会调用 clang 链接动态库(如 libSystem、libobjc),触发 ld64 自动注入 LC_RPATH 加载路径——这与 Hardened Runtime 的 no-rpath 安全策略直接冲突。
RPATH 注入触发条件
- Go 工具链调用
cgo生成 C 兼容符号; ld64检测到-dynamic或显式-rpath参数(即使未显式指定);- 默认注入
/usr/lib和/System/Library/Frameworks等系统路径。
典型构建日志片段
# 构建命令(隐式触发 RPATH 注入)
go build -ldflags="-sectcreate __TEXT __info_plist Info.plist -buildmode=exe" main.go
此命令虽未显式传
-rpath,但因CGO_ENABLED=1,ld64会扫描依赖并自动写入LC_RPATHload command,导致签名验证失败(code object is not signed at all或invalid signature)。
安全影响对比表
| 场景 | Hardened Runtime 允许 | 实际行为 |
|---|---|---|
CGO_ENABLED=0 |
✅ | 无 LC_RPATH,签名通过 |
CGO_ENABLED=1 |
❌ | ld64 自动注入,签名拒绝 |
规避方案流程图
graph TD
A[启用 CGO] --> B{是否需动态链接?}
B -->|是| C[显式禁用 RPATH:<br>`-Wl,-rpath,`]
B -->|否| D[设 CGO_ENABLED=0]
C --> E[验证 Mach-O:<br>`otool -l binary | grep -A2 LC_RPATH`]
2.5 Notarization预检失败溯源:Go生成的Info.plist缺失com.apple.security.app-sandbox Entitlements映射
macOS Notarization预检阶段会严格校验 Info.plist 与签名 entitlements 的一致性。Go 构建的 macOS 应用若未显式注入沙盒权限声明,将触发 ITMS-90299 错误。
Info.plist 中缺失的关键键值
<!-- 正确示例:必须存在且值为 true -->
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>com.apple.security.app-sandbox</key>
<true/>
⚠️ Go 的
go build -ldflags="-H=macos"不自动注入沙盒键;需通过-ldflags "-w -s"后手动注入 plist 或使用plutil工具补全。
Entitlements 文件与 Info.plist 的映射关系
| Info.plist 键 | Entitlements 键 | 是否必需(沙盒启用) |
|---|---|---|
com.apple.security.app-sandbox |
com.apple.security.app-sandbox |
✅ 必须一致 |
NSCameraUsageDescription |
— | ❌ 仅 plist 需声明 |
校验流程
graph TD
A[Notary Tool 提交] --> B{Info.plist 包含 com.apple.security.app-sandbox?}
B -->|否| C[预检失败:ITMS-90299]
B -->|是| D{entitlements.plist 存在且值为 true?}
D -->|否| C
D -->|是| E[继续签名验证]
第三章:Info.plist元数据配置的合规性断点
3.1 CFBundleExecutable字段的Go交叉编译路径污染问题与修复方案
当使用 go build -o MyApp.app/Contents/MacOS/MyApp 为 macOS 构建 bundle 时,若未显式指定 -ldflags="-H=macos",Go 默认生成的 Mach-O 二进制会将 CFBundleExecutable 字段硬编码为构建主机路径(如 /Users/dev/go/src/app/main),导致 Info.plist 解析失败。
问题根源
Go linker 在 Darwin 平台未自动截取最终文件名,而是保留完整输出路径作为 __TEXT,__info_plist 中的可执行名引用。
修复方案
- ✅ 强制指定
-ldflags="-s -w -H=macos"启用 macOS 原生头格式 - ✅ 使用
install_name_tool -id "@executable_path/MyApp" MyApp重写 ID - ❌ 避免
go build -o ./MyApp.app/Contents/MacOS/MyApp中含多级相对路径
# 正确:确保输出路径扁平且无嵌套
go build -ldflags="-H=macos -s -w" -o MyApp.app/Contents/MacOS/MyApp .
该命令强制 Go linker 以 macOS Mach-O 格式生成二进制,并仅将
MyApp作为CFBundleExecutable值写入 plist;-s -w剥离调试符号,避免路径残留。
| 选项 | 作用 | 是否必需 |
|---|---|---|
-H=macos |
启用 macOS 专用链接器后端 | ✅ |
-s -w |
移除符号表与 DWARF 调试信息 | ✅(防路径泄漏) |
graph TD
A[go build -o path/to/MyApp] --> B{是否含-H=macos?}
B -->|否| C[写入绝对路径到__info_plist]
B -->|是| D[仅写入文件名 MyApp]
D --> E[Info.plist 正确解析 CFBundleExecutable]
3.2 LSMinimumSystemVersion语义歧义:Go runtime.Version()与macOS SDK版本号的错位匹配
LSMinimumSystemVersion 是 Info.plist 中声明的最低 macOS 运行时要求,但其语义常被误读为“构建所用 SDK 版本”。
本质差异
runtime.Version()返回 Go 编译器版本(如go1.22.5),与操作系统无关LSMinimumSystemVersion(如12.0)是链接时由-mmacosx-version-min=12.0决定的 dyld 兼容性锚点
错位根源
# 构建命令隐式绑定 SDK 版本
go build -ldflags="-mmacosx-version-min=13.0" main.go
该标志影响 Mach-O 的 LC_BUILD_VERSION,但 不修改 runtime.Version() —— 后者始终反映 Go 工具链版本。
| 字段 | 来源 | 示例 | 作用域 |
|---|---|---|---|
runtime.Version() |
Go 源码编译时嵌入 | go1.22.5 |
Go 运行时自身 |
LSMinimumSystemVersion |
链接器注入 | 13.0 |
macOS dyld 加载校验 |
graph TD
A[Go 源码] --> B[go build]
B --> C[Go compiler: embed runtime.Version()]
B --> D[linker: inject -mmacosx-version-min]
D --> E[Mach-O LC_BUILD_VERSION]
E --> F[dyld 校验 LSMinimumSystemVersion]
3.3 NSHumanReadableCopyright字段编码异常:UTF-8 BOM导致plist解析失败的实测复现
当 NSHumanReadableCopyright 字段值以 UTF-8 BOM(EF BB BF)开头时,macOS 和 iOS 的 PropertyListSerialization 会静默拒绝解析,返回 nil 且无明确错误码。
复现关键步骤
- 使用 Xcode 编辑 Info.plist 并手动插入含 BOM 的字符串(如通过 TextEdit 保存为“UTF-8 with BOM”)
- 或用 Python 生成带 BOM 的 plist:
# 生成含BOM的copyright字符串 import plistlib data = {"NSHumanReadableCopyright": "\ufeff© 2024 Acme Inc."} # \ufeff → UTF-8 BOM with open("Info.plist", "wb") as f: plistlib.dump(data, f)此代码将 BOM(U+FEFF)编码为 UTF-8 字节序列
EF BB BF,嵌入字符串首部。plistlib默认不校验 BOM,但系统底层CFPropertyListCreateFromXMLData遇到即终止解析。
兼容性对比表
| 环境 | 含 BOM 解析结果 | 错误日志 |
|---|---|---|
| macOS 13+ | nil,NSError code -3840 |
“Invalid character in string” |
| Xcode 15 构建器 | 警告:Info.plist: invalid UTF-8 sequence |
无崩溃,但 Bundle ID 注册失败 |
graph TD
A[读取Info.plist] --> B{检测UTF-8 BOM}
B -->|存在| C[拒绝解析,返回nil]
B -->|不存在| D[正常反序列化]
第四章:沙盒与权限声明的Go特异性漏洞
4.1 com.apple.security.app-sandbox启用后,Go net/http监听localhost:端口的IPC绕过失效分析
macOS App Sandbox 通过 com.apple.security.app-sandbox entitlement 强制隔离进程网络能力,即使 Go 程序调用 http.Listen("127.0.0.1:8080"),系统也会拦截绑定请求并返回 permission denied。
沙箱网络策略限制
localhost绑定被重写为127.0.0.1,但沙箱默认禁用network.client和network.server权限;- 即使显式声明
com.apple.security.network.client,network.server(监听)仍需额外 entitlement 且不被 Apple 允许在 App Store 应用中使用。
Go 运行时行为差异
// main.go
srv := &http.Server{Addr: "127.0.0.1:8080"}
log.Fatal(srv.ListenAndServe()) // panic: listen tcp 127.0.0.1:8080: permission denied
该错误源自 libsystem_kernel.dylib 的 bind() 系统调用被 sandboxd 拦截,非 EACCES 而是 EPERM,表明策略层拒绝而非权限不足。
| 权限项 | 是否允许 | 说明 |
|---|---|---|
network.client |
✅ | 允许 outbound 连接 |
network.server |
❌ | App Sandbox 中不可声明 |
com.apple.security.network.server |
⚠️ | 仅限 macOS 系统级应用,签名验证失败 |
graph TD
A[Go http.Listen] --> B[syscall bind]
B --> C{sandboxd policy check}
C -->|network.server missing| D[EPERM]
C -->|entitlement present| E[allow bind]
D --> F[panic at runtime]
4.2 NSCameraUsageDescription本地化缺失:Go embed.FS资源打包时InfoPlist.strings路径未被识别
当使用 embed.FS 打包 iOS 本地化资源时,InfoPlist.strings 文件若置于非标准路径(如 locales/zh-Hans.lproj/InfoPlist.strings),go build 将忽略其内容,导致 NSCameraUsageDescription 等键值在对应语言下为空。
原因分析
iOS 仅从 bundle 根目录的 *.lproj/InfoPlist.strings 加载本地化字符串。embed.FS 默认不映射目录结构到 bundle 层级路径。
正确嵌入方式
// ✅ 强制映射为 bundle 可识别路径
//go:embed locales/*/InfoPlist.strings
var localesFS embed.FS
关键参数说明:
embed.FS的 glob 模式需覆盖所有.lproj子目录;若写成locales/zh-Hans.lproj/InfoPlist.strings则仅嵌入单语言,且路径不含.lproj后缀,无法被 iOS runtime 解析。
本地化路径验证表
| 路径模式 | 是否被 iOS 识别 | 原因 |
|---|---|---|
zh-Hans.lproj/InfoPlist.strings |
✅ | 符合 CFBundleLocalizations 规范 |
locales/zh-Hans/InfoPlist.strings |
❌ | 缺失 .lproj 后缀,bundle 不扫描 |
graph TD
A[embed.FS声明] --> B{glob匹配路径}
B -->|含.lproj| C[生成正确bundle路径]
B -->|不含.lproj| D[路径不可见于NSBundle]
4.3 com.apple.developer.networking.wifi-info权限的Go运行时检测盲区与静态声明强制要求
运行时无法动态验证的权限现实
Go 语言在 iOS/macOS 平台上无运行时 API 查询 com.apple.developer.networking.wifi-info 是否被授予。syscall.Syscall 或 C.CFNetworkCopySystemConfiguration 均不暴露该授权状态,导致 wifi.GetInfo() 成功返回空结构体或静默失败。
静态声明是唯一准入路径
该权限必须通过 Xcode 的 Entitlements.plist 显式声明,且需配合 App Store Connect 中开启对应 Capability:
| 项目 | 要求 |
|---|---|
Entitlements.plist 键 |
com.apple.developer.networking.wifi-info(布尔值 true) |
| Xcode 设置 | Signing & Capabilities → “WiFi Information” ✅ |
| 提交审核 | 缺失即触发 ITMS-90683(“Missing required entitlement”) |
// ❌ 错误:尝试运行时探测(无效)
func hasWiFiInfoPerm() bool {
// Go runtime 无对应 CGO 接口;此调用永远返回 false 或 panic
return C.hasWiFiEntitlement() == 1 // 未定义符号,链接失败
}
此代码在构建阶段即报错
undefined reference to 'hasWiFiEntitlement'——Apple 未向 Go 生态开放该检测桥接层,依赖C函数属虚构实现。
权限流验证逻辑(mermaid)
graph TD
A[Go 构建] --> B{Entitlements.plist 包含 wifi-info?}
B -->|否| C[ld: warning: missing entitlement]
B -->|是| D[App 安装后调用 C.CNCopyCurrentNetworkInfo]
D --> E[仅当用户授予权限时返回 SSID/BSSID]
E -->|拒绝/未声明| F[返回 nil,无错误码]
4.4 Temporary Exceptions Entitlements滥用:Go程序调用IOKit获取硬件信息时的审核红线
IOKit调用的典型Go封装示例
// 使用cgo调用IOKit获取CPU型号
/*
#cgo LDFLAGS: -framework IOKit
#include <IOKit/IOKitLib.h>
#include <IOKit/cpu/IOCPU.h>
*/
import "C"
func GetCPUModel() string {
machPort := C.IOMasterPort(C.kIOMainPortDefault, nil)
// ⚠️ 需声明 com.apple.developer.kernel.extended-iokit-permissions 权限
return "Intel Core i7"
}
该调用触发macOS Gatekeeper深度检查:未声明temporary-exception entitlement将导致沙盒拒绝,且App Store审核直接拒收。
审核敏感点对照表
| Entitlement Key | 允许场景 | 审核状态 |
|---|---|---|
com.apple.developer.kernel.iokit |
驱动开发工具 | ✅ 明确授权 |
temporary-exception |
仅限调试期硬件探测 | ❌ 2024年起禁用 |
com.apple.developer.kernel.extended-iokit-permissions |
硬件诊断(需Apple书面批准) | ⚠️ 附录B强制验证 |
权限演进路径
graph TD
A[Legacy IOKit Access] --> B[Temporary Exception Entitlement]
B --> C[Extended Permissions with Notarization]
C --> D[HardwareKit API替代方案]
第五章:附录——Go Mac App Bundle校验自动化清单(含脚本源码)
校验目标与覆盖范围
Mac App Bundle 的完整性、签名有效性、权限配置及 Info.plist 合规性是分发前必须验证的四大核心维度。本清单基于 Apple 官方文档(Notarization Requirements)与实际 CI/CD 流水线失败案例提炼,覆盖 12 类高频失效场景,包括 com.apple.security.get-task-allow 意外启用、CFBundleExecutable 路径缺失、Hardened Runtime 未启用、以及 entitlements.plist 中 com.apple.developer.team-identifier 为空等。
自动化校验流程图
flowchart TD
A[启动校验] --> B[解包 .app 目录结构]
B --> C[检查 Info.plist 关键字段]
C --> D[验证签名链与公证状态]
D --> E[比对 entitlements 与 provisioning profile]
E --> F[扫描可执行文件 Mach-O 属性]
F --> G[输出 JSON 格式报告]
G --> H[失败项高亮并退出非零码]
必检清单与对应命令
| 检查项 | 命令示例 | 失败触发条件 |
|---|---|---|
| 签名有效性 | codesign --verify --verbose=4 MyApp.app |
返回非零码或输出 invalid signature |
| Info.plist 存在性 | test -f MyApp.app/Contents/Info.plist |
文件不存在或权限不足 |
| Hardened Runtime 启用 | codesign -d --entitlements :- MyApp.app \| grep 'com.apple.security.cs.*hardened' |
无匹配行或值为 false |
| 可执行文件权限 | stat -f "%Lp" MyApp.app/Contents/MacOS/MyApp |
八进制权限不等于 100755 |
Go 实现的校验脚本(main.go)
package main
import (
"bytes"
"fmt"
"os/exec"
"strings"
)
func checkSignature(appPath string) error {
cmd := exec.Command("codesign", "--verify", "--verbose=4", appPath)
var out bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &out
if err := cmd.Run(); err != nil {
return fmt.Errorf("signature check failed: %v\n%s", err, out.String())
}
return nil
}
func main() {
app := os.Args[1]
if len(app) == 0 {
fmt.Fprintln(os.Stderr, "Usage: go run main.go <MyApp.app>")
os.Exit(1)
}
if err := checkSignature(app); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Println("✅ All checks passed.")
}
集成到 GitHub Actions 示例
在 .github/workflows/build.yml 中添加如下步骤:
- name: Validate macOS Bundle
if: matrix.os == 'macos-latest'
run: |
go build -o bundle-check .
./bundle-check ./dist/MyApp.app
该步骤在每次 PR 提交后自动运行,结合 actions/upload-artifact@v3 可将生成的 report.json 作为构建产物归档,供 QA 团队复核。
常见误报与规避策略
当使用 go build -ldflags="-H=windowsgui" 编译时,Go 会注入 Windows 特有头信息,导致 file MyApp.app/Contents/MacOS/MyApp 输出 PE32+ executable (console) x86-64 —— 此为误报。真实校验应以 otool -l MyApp.app/Contents/MacOS/MyApp \| grep -A2 LC_VERSION_MIN_MACOSX 输出为准,确认存在 version 10.15 或更高版本标识。
Entitlements 动态注入验证
若通过 --entitlements=entitlements.plist 参数签名,需确保该文件中 com.apple.security.network.client 与应用实际网络行为一致。脚本可调用 security find-identity -p codesigning -v 获取当前钥匙串中有效证书列表,并比对 codesign -d --entitlements :- MyApp.app 输出中的 application-identifier 字段是否匹配团队 ID 前缀。
日志与调试增强建议
在 CI 环境中启用 set -x 并重定向所有校验命令 stderr 至 debug.log,同时捕获 spctl --assess --type execute --verbose=4 MyApp.app 输出。该命令能暴露 Gatekeeper 拒绝原因,例如 rejected: insufficient application bundle signature 或 rejected: no suitable application identifier found,比单纯 codesign 更贴近终端用户实际拦截场景。
