目 录CONTENT

文章目录

macos-codesign-tcc

星廿
2026-08-18 / 0 评论 / 0 点赞 / 5 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

未签名的 macOS 构建包,是怎么把「划词翻译」搞坏的

一次真实排查记录:从「快捷键划词后翻译框是空的」,一路查到 macOS 的 TCC 权限与代码签名机制,最后用一张免费的自签名证书收尾。

案例项目:pot-desktop(Tauri 1.x + React),环境 macOS 26 / Apple Silicon。
但结论适用于任何需要辅助功能权限(Accessibility)的 macOS 应用:划词取词、全局快捷键、模拟按键、屏幕录制、自动化脚本类工具都会踩同一个坑。


摘要:先说结论(TL;DR)

如果你的 macOS 应用需要辅助功能权限,而你在用未签名 / ad-hoc 签名的自建包(比如 CI 里跑的测试构建),那么:

  • 每次重新构建,系统授予的权限都会失效,而且失效得非常安静——不报错、不弹窗,功能只是「不工作了」。

  • 根因是 macOS 的权限记录(TCC)绑定的是代码签名身份,而 ad-hoc 签名没有稳定身份。

  • 个人本地使用的最省事解法:用钥匙串里自建的一张自签名证书重签一次 app,免费、不需要 Apple 开发者账号、不需要重新构建。

一条命令是核心:

codesign --force --deep --sign pot-local /Applications/pot.app

想知道为什么、以及有哪些坑,继续往下读。


一、症状:功能「静默」失效

用户视角的描述只有一句话:

使用快捷键把选中的文本翻译,但是现在没有自动粘贴到翻译框中。

具体表现:

现象

是否正常

按下划词快捷键(Alt+C

✅ 有反应

翻译窗口弹出

✅ 正常弹出,位置也对

源文本框里出现选中的文字

空的

报错提示 / 弹窗

❌ 一个都没有

最容易误判的地方就在这里:窗口弹出来了,说明快捷键注册成功、进程活着、UI 正常。于是第一反应必然是「前端代码被改坏了」——尤其当你前几天刚动过翻译窗口的代码。

我一开始也是这么怀疑的。但代码是无辜的。


二、排查过程:先证伪,再看日志

2.1 第一步:用 git 证伪「代码被改坏了」

在读任何业务逻辑之前,先确认改动范围。这一步能省掉大量瞎猜:

# 当前分支相对主分支到底动了什么
git diff --stat master...HEAD

# 取词链路上的关键文件,最后一次改动是什么时候
git log --oneline -3 -- src-tauri/src/window.rs src-tauri/src/cmd.rs

结果很清楚:本分支只动了 CI 配置、文档、i18n 和两个前端组件;而取词链路上的三个关键文件——源文本框组件、Rust 侧的取词函数、取词结果的读取命令——一行都没改,最后一次改动还是几个月前的上游提交。

经验:怀疑「某功能被改坏」时,先用 git log -- <相关文件> 划定嫌疑范围。如果嫌疑文件根本没被碰过,那问题就在代码之外(环境、权限、配置、依赖),继续读代码是浪费时间。

2.2 第二步:读日志(这一步给出了答案)

Tauri 应用的日志默认在这里(把 com.pot-app.desktop 换成你自己的 bundle identifier):

# macOS
tail -f ~/Library/Logs/com.pot-app.desktop/pot.log

日志量很大(TRACE 级别会把每个 HTTP 分块都记下来),所以直接过滤关键词。取词失败最可能的关键词就是权限、取词函数名、以及事件名:

grep -n "Accessibility\|get_text\|selection\|reject:" ~/Library/Logs/com.pot-app.desktop/pot.log | tail -30

三行输出就把问题钉死了:

[13:28:00][INFO ][pot]              MacOS Accessibility Trusted: false
[13:28:08][ERROR][selection::macos] get_selected_text_by_ax error:No selected element
[13:28:08][ERROR][selection::macos] get_text_by_clipboard error: Output {
    status: ExitStatus(unix_wait_status(256)), stdout: "",
    stderr: "507:541: execution error: “System Events”遇到一个错误:“osascript”不允许发送按键。 (1002)\n" }

翻译一下这三行说了什么:

  1. 启动时辅助功能权限就是 false —— 应用自己知道自己没权限。

  2. 主路径失败:通过 macOS 的 Accessibility API(AX)直接读取「当前选中的文本」,失败。

  3. 兜底路径也失败:退化成用 osascript 模拟按 Cmd+C 把选中内容复制到剪贴板再读,被系统拒绝——错误码 1002 就是「没有自动化/辅助功能权限」。

2.3 为什么「静默」失效

看一眼取词的调用链就明白了。以 pot 用的 selection crate 为例,Rust 侧逻辑简化后是这样:

pub fn selection_translate() {
    let text = selection::get_text();          // ← 两条路都失败时返回空字符串
    if !text.trim().is_empty() {               // ← 空字符串:跳过写入
        state.0.lock().unwrap().replace_range(.., &text);
    }
    let window = translate_window();           // ← 窗口照样创建
    window.emit("new_text", text).unwrap();    // ← 照样发事件,只是载荷是空的
}

关键在于 get_text() 的失败被吞成了空字符串,而不是 Result::Err。于是:

flowchart TD
    A["按下 Alt+C"] --> B["selection::get_text()"]
    B --> C{"AX API 读取选中文本"}
    C -- "权限不足" --> D{"回退:osascript 模拟 Cmd+C"}
    C -- 成功 --> G["拿到文本"]
    D -- "权限不足" --> E["返回空字符串 &quot;&quot;"]
    D -- 成功 --> G
    E --> F["窗口照常弹出<br/>emit('new_text', '')"]
    G --> F2["窗口弹出<br/>emit('new_text', text)"]
    F --> H["前端源文本框:空<br/>❌ 无报错、无提示"]
    F2 --> I["前端源文本框:有文本<br/>✅ 触发翻译"]

    style E fill:#ffe0e0,stroke:#c00
    style H fill:#ffe0e0,stroke:#c00
    style I fill:#e0ffe0,stroke:#0a0

「窗口能弹出」和「取词成功」是两件完全独立的事,前者不能作为后者的证据。这正是这个 bug 具有欺骗性的原因。

经验:排查这类问题时,日志的价值远高于读代码。尤其是权限类问题——它不会抛异常,只会让某个返回值变成空/默认值。如果日志里没有相关记录,值得先给可疑链路补上日志再复现一次。


三、根因:TCC 权限绑在代码签名上

到这里问题变成了:权限为什么会没了? 明明之前用官方发布版的时候,划词一直是好的。

区别在于「之前用的是官方签名版,现在用的是自己 CI 打的测试包」。验证一下这个包的签名状态:

codesign -dv /Applications/pot.app

出问题时的输出(这是坏的状态):

Executable=/Applications/pot.app/Contents/MacOS/pot
Identifier=pot-c521813909554c9a          ← ⚠️ 不是 com.pot-app.desktop!
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20400 ... flags=0x20002(adhoc,linker-signed)   ← ⚠️ ad-hoc
Signature=adhoc                          ← ⚠️ 没有真正的签名
Info.plist=not bound                     ← ⚠️ Info.plist 没被签名覆盖
TeamIdentifier=not set
Sealed Resources=none                    ← ⚠️ 资源没被封装

四个警告信号,每一个都在说同一件事:这不是一个被正确签名的 app bundle

3.1 什么是 ad-hoc / linker-signed

Apple Silicon 上,所有可执行文件必须有签名才能运行。所以当你不提供任何证书就构建时,链接器会自作主张打一个「ad-hoc 签名」——只是对二进制内容做哈希,不涉及任何身份。

后果是 Identifier 不来自 Info.plist 的 bundle id,而是从二进制文件名 + 内容哈希临时生成的,所以变成了 pot-c521813909554c9a 这种鬼东西。在系统眼里,这跟 com.pot-app.desktop两个毫不相干的应用

3.2 TCC 是怎么记住「你授权过」的

TCC(Transparency, Consent, and Control)是 macOS 管理隐私授权的子系统,辅助功能、屏幕录制、麦克风、自动化这些开关都归它管。它的记录不是「记住某个路径」——否则把恶意程序改名成 pot.app 放到同一个位置就能白拿权限了。

它记的是两样东西:bundle identifier + 代码签名的「指定要求」(Designated Requirement, DR)

DR 可以直接看:

codesign -d -r- /Applications/pot.app

用正规证书签过之后是这样:

designated => identifier "com.pot-app.desktop" and certificate leaf = H"5860018...8AD593"
                         ^^^^^^^^^^^^^^^^^^^^                        ^^^^^^^^^^^^^^^^^^
                         bundle id 必须匹配                            证书指纹必须匹配

而 ad-hoc 签名没有证书,它的 DR 只能退化成「二进制内容哈希(cdhash)必须完全一致」。

于是就有了这个致命差异:

┌─────────────────────────────────────────────────────────────────┐
│  ad-hoc 签名(坏)                                                │
│                                                                 │
│   构建 #1  →  cdhash=aaa…  →  你在系统设置里授权  →  ✅ 能用         │
│   改一行代码重新构建                                               │
│   构建 #2  →  cdhash=bbb…  →  TCC 记录对不上     →  ❌ 静默失效     │
│                                (旧记录还留在列表里,开关还是打开的)  │
├─────────────────────────────────────────────────────────────────┤
│  证书签名(好)                                                   │
│                                                                 │
│   构建 #1  →  DR: id + 证书指纹  →  授权一次  →  ✅                 │
│   改代码、重新构建、换版本号…(证书不变)                              │
│   构建 #N  →  DR 完全相同        →  无需再授权 →  ✅                │
└─────────────────────────────────────────────────────────────────┘

最坑的地方:失效后,系统设置 → 辅助功能列表里那个 pot 条目还在,开关还是打开的。你看着 UI 一切正常,实际上那条记录对应的是一个已经不存在的旧签名。这就是为什么「静默」——用户看不到任何异常,只能看到功能不工作。

⚠️ 关于 TCC 的内部实现,Apple 没有公开文档。上面的模型来自可观测行为(DR 内容、重签后权限失效/保持的实际结果)与社区共识,用来解释和预测现象足够可靠,但不必当成精确的实现描述。

3.3 为什么会踩到这个坑

我这次的直接触发原因是改了 CI:为了能在没有 Apple 证书的情况下打测试包给自己用,我在 workflow 里显式清掉了所有签名相关的环境变量:

# .github/workflows/package.yml —— 测试构建分支
- name: Build and Package
  run: |
    if [ "$GITHUB_EVENT_NAME" = 'workflow_dispatch' ]; then
      env -u TAURI_PRIVATE_KEY \
        -u APPLE_SIGNING_IDENTITY \
        -u APPLE_CERTIFICATE \
        -u APPLE_CERTIFICATE_PASSWORD \
        -u APPLE_TEAM_ID \
        ...
        pnpm tauri build --bundles dmg --target ${{ matrix.target }}
    else
      export APPLE_SIGNING_IDENTITY="${{ secrets.APPLE_SIGNING_IDENTITY }}"
      ...
      pnpm tauri build --target ${{ matrix.target }}
    fi

这个改动本身没错——没有证书就是签不了。问题在于我没有意识到「未签名」会顺带废掉辅助功能权限,于是把「划词取词失败」误判成了自己前一天改的前端代码有 bug。

容易掉进来的典型场景:

  • Fork 上游项目自己打包,但没有上游的签名 secrets

  • 本地 cargo tauri build 快速验证,从来不配签名

  • 用 CI 打 nightly / 测试包分发给自己或小范围测试

  • 从「官方签名版」切换到「自建版」时,功能突然不好使


四、三种解法,怎么选

方案 A:自签名证书

方案 B:Developer ID

方案 C:每次手动重授权

费用

免费

$99/年

免费

需要 Apple 账号

不需要

需要(付费会员)

不需要

权限跨重建保持

能发给别人

❌ Gatekeeper 拦

✅ 配合公证

首次打开有安全提示

有(可绕过)

一次性配置成本

约 5 分钟

半天起(证书 + 公证 + CI)

0

长期维护成本

证书到期前无

证书年更 + 公证维护

每次重装都要手动操作

个人本地使用,选 A。 下面详细展开;B 放在第七节,需要分发时再看。

顺便说清楚一个常见误解:codesign -s - 这种「普通 ad-hoc 签名」解决不了问题。它和构建时的 linker-signed 一样没有稳定身份,DR 仍然绑 cdhash,重建照样失效。必须是有证书的签名。


五、方案 A 实操:自签名证书(推荐)

5.1 一个关键前提:不需要重新构建

这一点很多人会绕远路。签名是对已构建产物的操作,只依赖 codesign(Xcode Command Line Tools 自带),跟构建工具链完全无关

我这台机器压根没装 Rust:

$ which cargo rustc
cargo not found
rustc not found

照样能签。所以流程是:

CI 打包(未签名)→ 下载 dmg → 拖进 /Applications → 本地 codesign 重签 → 授权一次 → 长期可用

而不是「改 CI 配置 → 本地装 Rust → 重新构建」。后者对本地自用来说纯属多余。

5.2 步骤 1:创建自签名证书

打开钥匙串访问(Keychain Access)→ 菜单栏「钥匙串访问 → 证书助理 → 创建证书」:

创建自签名代码签名证书

三个字段:

字段

说明

名称

pot-local

随便起,但要记住——后面 codesign -s 要用它

身份类型

自签名根证书

证书类型

代码签名

⚠️ 别选成默认的「SSL 服务器」之类

「让我覆盖这些默认值」勾不勾?

不勾也完全能用。唯一值得勾的理由是有效期:默认只签发 365 天,到期后这张证书就没法再用来签新包了;而重新创建证书 = 新的证书指纹 = DR 变了 = 辅助功能权限要重新授权一次

想省掉一年后这次麻烦,就勾上,在随后的「有效期(天数)」页把 365 改成 3650(10 年),其余页面一路默认「继续」。特别注意别去动「密钥用法扩展」和「扩展的密钥用法」那两页——把「代码签名」用途改掉的话,codesign 会拒绝使用这张证书。

创建过程中会提示「这是自签名根证书、不受信任」之类,继续即可。

5.3 步骤 2:确认证书可用

security find-identity -v -p codesigning

期望输出:

  1) 586001838B3C7BFBCE3DEB94E2B8DA05FE8AD593 "pot-local"
     1 valid identities found

如果显示 0 valid identities found:去钥匙串访问里双击这张证书 → 展开「信任」→ 把「代码签名」设为「始终信任」→ 关闭窗口(会要求输入密码)→ 重新运行上面的命令。

前面那串十六进制就是证书指纹,也就是会被写进 DR 的东西。记不住没关系,codesign 用证书名字就够了。

5.4 步骤 3:重签 app

⚠️ 先完全退出应用(菜单栏图标 → 退出)。给正在运行的 app 重签会导致签名与内存中的映像不一致,行为不可预期,甚至可能被系统直接杀掉。

# 1. 重签(--deep 会把 bundle 内嵌的可执行文件一起签)
codesign --force --deep --sign pot-local /Applications/pot.app

# 2. 验证签名身份
codesign -dv /Applications/pot.app 2>&1 | grep -E "Identifier|Signature|Authority|flags"

# 3. 验证 DR(这才是 TCC 真正看的东西)
codesign -d -r- /Applications/pot.app

签好之后的正确状态

Identifier=com.pot-app.desktop        ← ✅ 回到真正的 bundle id
CodeDirectory v=20400 ... flags=0x0(none)   ← ✅ 不再是 adhoc,linker-signed
Signature size=1755                   ← ✅ 有真实签名(不再是 "Signature=adhoc")
Signed Time=Aug 18, 2026 at 22:17:17
Sealed Resources version=2 rules=13 files=3   ← ✅ 资源已封装

designated => identifier "com.pot-app.desktop" and certificate leaf = H"5860018...8AD593"

对照第三节那份「坏的状态」,四个警告信号全部消失。

--deep 的作用在这个 app 上是实打实的——bundle 里除主程序外还内嵌了两个 OCR 可执行文件:

$ find /Applications/pot.app -type f -exec file {} \; | grep -i mach-o
/Applications/pot.app/Contents/MacOS/pot: Mach-O 64-bit executable arm64
/Applications/pot.app/Contents/Resources/resources/ocr-x86_64-apple-darwin: Mach-O 64-bit executable x86_64
/Applications/pot.app/Contents/Resources/resources/ocr-aarch64-apple-darwin: Mach-O 64-bit executable arm64

不加 --deep 的话,这些内嵌二进制保持原样,外层 bundle 的封装校验可能对不上。

补充:Apple 官方文档标注 --deep 主要用于验证场景,签名时推荐「由内向外逐个签」。对分发产物应当遵循官方做法;但对本地自用的这种简单 bundle,--force --deep 是最省事且实践中可靠的选择。

5.5 步骤 4:重新授权一次

重签后 DR 变了,必须手动清掉旧记录重新授权一次。 这一步不能省,也是最容易漏的一步。

系统设置 → 隐私与安全性:

  1. 辅助功能:如果列表里已有 pot,先选中它点 - 删掉,再点 + 重新添加 /Applications/pot.app,并打开开关

    • ⚠️ 直接把开关关掉再打开是没用的——那条记录本身绑的还是旧签名。必须删掉重加。

  2. 自动化:展开 pot,勾上「系统事件」(System Events)。这是给 osascript 模拟 Cmd+C 那条回退路径用的。

5.6 步骤 5:验证成功

重新启动应用,看日志第一行:

grep "Accessibility Trusted" ~/Library/Logs/com.pot-app.desktop/pot.log | tail -1

期望:

[INFO][pot] MacOS Accessibility Trusted: true      ← ✅ 成了

然后实际划词试一次,日志里应该不再出现 fallback to get_text_by_clipboard——说明 AX 主路径直接成功了。

从此以后:CI 出新包 → 下载安装 → 只跑一遍 codesign --force --deep --sign pot-local /Applications/pot.app → 直接可用,权限不用再动。因为 DR 里的 bundle id 和证书指纹都没变。


六、坑与注意事项

坑 1:千万别加 -o runtime(加固运行时)

网上的签名教程十有八九会让你加上 -o runtime(Hardened Runtime),因为公证(notarization)强制要求它。但如果你只是本地自用,加上反而会坏事

原因:加固运行时默认禁止发送 Apple Events。而很多取词/自动化类工具在 AX API 失败后会退化成 osascript 模拟按键——这条路正好依赖 Apple Events。加了 -o runtime 又没配对应的 entitlement,这条兜底路径就死了。

如果确实需要开加固运行时(比如为了公证),得配上 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.automation.apple-events</key>
    <true/>
</dict>
</plist>

然后 codesign -o runtime --entitlements entitlements.plist -s <identity> ...

顺带提一个待验证点:pot 的 src-tauri/tauri.conf.jsonmacOS.entitlementsnull,而官方发布版是经过公证的(必然开了加固运行时)。那么官方版的 osascript 回退路径在加固下是否真的可用,我没有实测过。走公证路线时值得单独验一遍这条路径,别想当然。

坑 2:证书过期 = 权限重新失效

自签证书默认 365 天。过期后:

  • 已经签好的 app 不受影响,继续能跑、权限继续有效(签名验证看的是签名时刻的有效性)。

  • 不能再用它签新包了,codesign 会报错。

  • 重新创建证书 → 新指纹 → 新 DR → 辅助功能权限要再授权一次

所以要么建证书时就把有效期设成 3650 天,要么接受一年后重新走一遍第 5.5 节。

坑 3:0 valid identities found

三个常见原因,按顺序排查:

# 1. 证书类型选错了(选了 SSL 服务器而不是代码签名)→ 删掉重建
# 2. 证书未被信任 → 钥匙串访问里双击 → 信任 → 代码签名 → 始终信任
# 3. 证书建到了错误的钥匙串(比如"系统"而不是"登录")→ 确认位置
security find-certificate -c pot-local -Z | grep -E "keychain|SHA-1"

坑 4:Gatekeeper 隔离属性

自签证书不被 Gatekeeper 认可,从浏览器/CI 下载的包首次打开仍可能弹「无法验证开发者」。这跟 TCC 权限是两个独立机制,自签解决不了。

# 查看隔离属性
xattr -l /Applications/pot.app

# 移除(如果有 com.apple.quarantine)
xattr -dr com.apple.quarantine /Applications/pot.app

也可以右键 → 打开,手动放行一次。

顺便说明:com.apple.provenance 这个属性和隔离无关,不用处理。

坑 5:给正在运行的 app 重签

会导致签名与已加载映像不一致。签之前一定先完全退出应用——不是关窗口,是真正退出进程(菜单栏图标 → 退出,或 pkill -x pot)。

坑 6:别把自签和 Developer ID 混用

如果哪天你升级到了 Developer ID 证书,DR 会再变一次(不同的证书指纹),辅助功能权限又要重新授权一次。这是预期行为,别以为哪里坏了。

坑 7:更新器(updater)会绕过你的签名

如果应用带自动更新功能,它下载安装的是官方/CI 的原始包,不会带你的自签名。更新完权限就又没了。本地自用建议在设置里关掉自动更新,改成手动下载 + 重签。

一键脚本

把重签 + 去隔离 + 验证打包成一个脚本,每次装新包跑一次就行:

#!/usr/bin/env bash
# sign-local.sh —— 用本地自签名证书重签 app,供个人使用
set -euo pipefail

IDENTITY="${SIGN_IDENTITY:-pot-local}"
APP="${1:-/Applications/pot.app}"

[[ -d "$APP" ]] || { echo "❌ 找不到 app:$APP"; exit 1; }

if ! security find-identity -v -p codesigning | grep -q "$IDENTITY"; then
    echo "❌ 找不到可用于代码签名的证书:$IDENTITY"
    echo "   请先在「钥匙串访问 → 证书助理 → 创建证书」中创建(自签名根证书 / 代码签名)"
    exit 1
fi

APP_NAME="$(basename "$APP" .app)"
if pgrep -x "$APP_NAME" >/dev/null 2>&1; then
    echo "⚠️  $APP_NAME 正在运行,先退出它"
    osascript -e "quit app \"$APP_NAME\"" 2>/dev/null || pkill -x "$APP_NAME" || true
    sleep 2
fi

echo "🔑 使用证书「$IDENTITY」重签:$APP"
codesign --force --deep --sign "$IDENTITY" "$APP"

echo "🧹 移除隔离属性"
xattr -dr com.apple.quarantine "$APP" 2>/dev/null || true

echo "✅ 签名结果:"
codesign -dv "$APP" 2>&1 | grep -E "^(Identifier|Signature|Authority)" || true
codesign -d -r- "$APP" 2>&1 | grep "^designated" || true

cat <<EOF

下一步(仅首次、或换过证书后需要):
  系统设置 → 隐私与安全性 → 辅助功能:删除旧的 $APP_NAME 条目,重新添加并打开开关
  系统设置 → 隐私与安全性 → 自动化:给 $APP_NAME 勾上「系统事件」
EOF

用法:

chmod +x sign-local.sh
./sign-local.sh                          # 默认 /Applications/pot.app
./sign-local.sh /Applications/其他.app    # 指定路径
SIGN_IDENTITY=my-cert ./sign-local.sh    # 指定证书

七、方案 B:Developer ID + 公证(需要分发时)

只有当你要把包发给别人时才需要这条路。$99/年的 Apple Developer Program 是硬门槛。

7.1 准备证书

  1. 加入 Apple Developer Program

  2. 开发者后台创建 Developer ID Application 证书(注意不是 Development、不是 Mac App Distribution)

  3. 导出成 .p12,设一个密码

7.2 配置 CI(以 Tauri 1.x + GitHub Actions 为例)

Tauri 的 bundler 直接读这些环境变量,仓库 Settings → Secrets and variables → Actions 里配好即可:

Secret

用途

APPLE_CERTIFICATE

base64 -i cert.p12 | pbcopy 的结果

证书本体

APPLE_CERTIFICATE_PASSWORD

导出 p12 时设的密码

解密证书

APPLE_SIGNING_IDENTITY

Developer ID Application: 你的名字 (TEAMID)

指定签名身份

APPLE_TEAM_ID

10 位 Team ID

签名 + 公证

APPLE_ID

你的 Apple ID 邮箱

公证

APPLE_PASSWORD

App 专用密码(不是 Apple ID 密码)

公证

App 专用密码在 appleid.apple.com → 登录与安全 → App 专用密码 里生成。

workflow 里就是普通的 export:

- name: Build and Package
  run: |
    export APPLE_SIGNING_IDENTITY="${{ secrets.APPLE_SIGNING_IDENTITY }}"
    export APPLE_CERTIFICATE="${{ secrets.APPLE_CERTIFICATE }}"
    export APPLE_CERTIFICATE_PASSWORD="${{ secrets.APPLE_CERTIFICATE_PASSWORD }}"
    export APPLE_TEAM_ID="${{ secrets.APPLE_TEAM_ID }}"
    export APPLE_ID="${{ secrets.APPLE_ID }}"
    export APPLE_PASSWORD="${{ secrets.APPLE_PASSWORD }}"
    pnpm tauri build --target ${{ matrix.target }}

7.3 公证凭据的两种方式,别踩坑

  • APPLE_ID + APPLE_PASSWORD + APPLE_TEAM_ID:推荐,配好 secrets 就能用。

  • API Key 方式APPLE_API_ISSUER + APPLE_API_KEY):坑在于 APPLE_API_KEY 只是 Key ID,Tauri 1.x 还需要你把 AuthKey_<KEYID>.p8 文件放到 ~/private_keys/./private_keys/。很多 workflow 只 export 了变量、没有落地这个文件,结果公证静默跳过或直接失败。

如果你 fork 的项目 workflow 里两组变量都 export 了(pot 就是这样),只配 Apple ID 那一组,别管 API Key 那两个。

7.4 验证公证结果

# 检查是否已装订公证票据
xcrun stapler validate /Applications/pot.app

# 完整的 Gatekeeper 评估
spctl -a -vvv -t install /Applications/pot.app

期望看到 source=Notarized Developer ID


八、排查清单(可直接照着抄)

遇到「需要辅助功能权限的 macOS 应用突然不工作」,按这个顺序走:

# ① 应用自己认为有权限吗?(把 bundle id 换成你的)
grep -i "accessib\|trusted" ~/Library/Logs/<bundle-id>/*.log | tail -5

# ② 签名是不是 ad-hoc?(Signature=adhoc / linker-signed 就是问题所在)
codesign -dv /Applications/<你的>.app

# ③ DR 长什么样?(绑 cdhash 而非证书 = 重建必失效)
codesign -d -r- /Applications/<你的>.app

# ④ 本机有可用的签名证书吗?
security find-identity -v -p codesigning

# ⑤ 代码到底改没改过取词链路?(证伪"代码被改坏")
git log --oneline -5 -- <取词相关文件>

对应的判断:

观察

结论

处理

日志 Trusted: false

权限确实没给到

继续查 ②

Signature=adhoc

未签名包,TCC 绑 cdhash

走第五节重签

Identifier 不是你的 bundle id

linker-signed,系统当成另一个 app

走第五节重签

DR 里有 certificate leaf = H"..."

签名正常

查系统设置里的授权项是否需删除重加

系统设置里开关是开的但功能不好使

典型的 DR 失配

删掉条目重新添加,别只是关开开关


九、几点总结

1. 权限问题不会报错,只会让返回值变空。 这类 bug 最贵的成本是误判方向。get_text() 失败返回 "" 而不是 Err,让「取词失败」伪装成了「前端渲染问题」。写这类调用时,把失败和「真的没选中文本」区分开、并各自打日志,能省下后来人几个小时。

2. 「窗口弹出来了」不能证明「取词成功了」。 排查时要警惕这种看起来相关、实际上独立的信号。

3. 先用 git 证伪,再读代码。 git log -- <文件> 花 5 秒,能直接排除掉一整个方向。

4. 改 CI 的签名配置,属于会影响运行时行为的改动。 「反正是测试包,签名无所谓」——对需要系统权限的应用来说不成立。如果要在 CI 里跑未签名的测试构建,最好在 README 或 workflow 注释里写一句「未签名包会丢失辅助功能权限,安装后需本地重签」,免得几周后自己都想不起来。

5. 免费的自签名证书能解决 90% 的本地自用问题。 不需要 $99,不需要公证,不需要重新构建。5 分钟配置,一次授权,长期有效。


参考


排查环境:macOS 26(Tahoe 26.6.2)/ Apple Silicon / Tauri 1.8.1 / Xcode Command Line Tools。文中的签名输出、日志片段、DR 内容均来自真实排查过程。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
  3. QQ打赏

    qrcode qq

评论区

鄂ICP备20003961号-3