缓存能命中,但你不确定哪些 Mac CI 任务能写入它?
最快做法:先按任务信任等级设置 cache-mode,再用 Job 级配置覆盖工作流基线,并从实际运行日志验收权限。

维护 Apple 平台工作流、需要区分缓存读写权限的开发者,适合看这篇。
负责限制低信任事件权限的仓库安全工程师,以及要推广多仓库 CI 规则的平台团队,也能用下面的矩阵和步骤做验收。

最后更新于 2026 年 10 月 2 日;配置规则核实自 GitHub 官方发布公告与 Actions 文档。cache-mode 已于 2026 年 9 月 10 日正式推出,适用于所有计划。(GitHub 发布公告)

先按缓存访问能力选模式,不要给所有任务同一把钥匙

GitHub Actions 的 cache-mode 有四种模式,控制任务能否恢复缓存、保存缓存,或完全不访问缓存。最小权限的判定方式是从任务实际需要出发:测试任务只需复用已有依赖时,不必同时获得写入能力;不需要缓存的任务则可完全关闭访问。模式定义见官方工作流语法。

模式 恢复缓存 保存缓存 Mac CI 中的常见适用情形
read ✅ 允许 ❌ 禁止 需要复用可信依赖、但不应改写缓存的测试任务
write ✅ 允许 ✅ 允许 可信触发的构建任务,需要读取并更新依赖缓存
write-only ❌ 禁止 ✅ 允许 必须写入新缓存,但不应读取既有缓存的任务
none ❌ 禁止 ❌ 禁止 不使用缓存,或需要验证无缓存构建的任务

这张表说明的是缓存访问权限,不是整个工作流的安全等级。缓存里不应放令牌、签名凭据等敏感内容;能读取缓存的任务会拿到缓存内容,而缓存本身也不能替你验证其中的文件可信与否。GitHub 的依赖缓存安全说明建议将恢复内容视为不可信输入。

对 Xcode 构建而言,先明确缓存用途:复用依赖下载结果,还是复用可能进入后续构建步骤的中间文件。后一种情况要额外审查缓存路径和消费方式,不能因为路径名是 DerivedData 或“构建缓存”就默认它安全。若任务只需编译验证而不需要缓存,可用 none 做对照运行;这有助于定位构建流程是否意外依赖了缓存内容,但不会替代代码和工作流安全审查。

Job 级设置如何改变工作流基线?

工作流顶层的 cache-mode 为各 Job 提供默认值;某个 Job 单独设置后,该 Job 使用自己的模式,其他 Job 不受影响。Job 级优先,因此只看工作流文件开头的配置,不能证明每个任务的最终权限。(GitHub 工作流语法文档)

例如,把可信构建和只读测试分开配置:

name: Mac CI

on:
  push:
  pull_request:

cache-mode: read

jobs:
  build:
    runs-on: macos-latest
    cache-mode: write
    steps:
      - uses: actions/checkout@v6
      # 在此配置构建与缓存步骤

  test:
    runs-on: macos-latest
    cache-mode: read
    steps:
      - uses: actions/checkout@v6
      # 在此配置测试与缓存恢复步骤

  audit_without_cache:
    runs-on: macos-latest
    cache-mode: none
    steps:
      - uses: actions/checkout@v6
      # 在此运行不依赖缓存的验收任务

这是权限结构示例,不代表对任意仓库都应把 build 设为 write。如果同一个 Job 在低信任事件下执行,显式授予写权限可能改变原本的安全限制,必须先确认该 Job 不会处理不可信代码或输入。还要注意:被禁止的缓存操作可以只在日志中提示并继续执行,任务本身不一定失败;因此“工作流绿了”不等于权限设置通过。工作流语法文档说明了 Job 覆盖规则及缓存操作受限时的行为。

pull_request_target 不应为了命中率开放缓存写入

对 pull_request_target,默认先保持只读。该事件具有较高信任权限,常用于响应来自分叉仓库的拉取请求;若工作流显式设置 write 或 write-only,就可能覆盖低信任触发的只读缓存限制,使恶意输入影响之后由可信工作流读取的缓存,形成缓存投毒风险。GitHub 对此类覆盖配置给出了明确警告,详见安全使用 pull_request_target 的说明。

更稳妥的职责拆分是:让可信的默认分支 push 工作流负责更新缓存;pull_request_target 任务只读取已有缓存,必要时关闭缓存访问。若它尝试保存缓存,缓存保存可能被拒绝并在日志中警告,但 Job 仍继续运行。换言之,构建通过不能证明写入成功,也不能作为放宽权限的理由。

还要检查触发事件与缓存作用域的组合,而不是只查事件名称。GitHub 文档指出,低信任事件在默认分支作用域下通常是只读,而 pull_request 的缓存作用域行为有所不同;明确写出 cache-mode: write,则可能覆盖原有只读保护。逐项对照依赖缓存的事件与作用域规则,不要把一类事件的默认值推断成所有拉取请求任务的通用结论。

⚠️ 如果低信任任务必须生成缓存,先问能否把写入移到可信触发的构建任务。确实无法拆分时,需同时审查不可信代码是否在写入前执行、哪些任务之后会读取该缓存,以及这些读取任务持有何种秘密和权限;单独调整 cache-mode 并不能消除缓存投毒风险。

可复用工作流的权限由调用关系约束

把缓存步骤封装进可复用工作流,并不意味着调用方可以不管访问边界。cache-mode 会沿调用关系传递;调用 Job 显式设置的模式,或调用方工作流继承的显式模式,会限制被调用工作流可以请求的缓存权限。若被调用工作流要求超出调用方上限的模式,运行会因校验错误而无法启动。(可复用工作流官方说明)

有一个容易漏掉的边界:如果调用 Job 和调用方工作流都没有显式指定 cache-mode,低信任触发事件的默认只读模式,不一定足以限制被调用工作流自行声明 write。因此,对低信任触发的调用,应在调用 Job 上明确设置 cache-mode: read,再检查被调用工作流是否符合这个上限。可复用工作流的权限规则与 GITHUB_TOKEN 权限不是同一套配置,不要用 Token 的权限设置代替缓存权限审查。

按这六步在受控仓库验收

  1. 列清任务和触发事件。 分别记录可信 push、外部拉取请求、pull_request_target、手动运行,以及调用可复用工作流的 Job。标记哪些任务会接触不可信分支代码、脚本、依赖或构建产物。

  2. 逐项写出缓存用途。 对每个 Job 说明需要恢复什么路径、是否需要保存、后续是否会执行恢复出的内容。若某项缓存的用途说不清,先移除该缓存步骤或改用 none 验证构建是否仍能完成。

  3. 先定工作流基线,再设 Job 例外。 将顶层配置设为大多数任务所需的最小权限,再仅为有充分理由的 Job 设置覆盖值。尤其要搜索全部 cache-mode 声明,避免一个 Job 级 write 意外覆盖安全基线。

  4. 核对低信任任务的写入路径。 对触发事件、检出的提交、执行的脚本和缓存路径逐项确认。若低信任工作流含有写权限,先撤销;只有完成风险评估并有受控的必要性时,才考虑例外,不要用“提高缓存命中率”作安全理由。

  5. 单独测试可复用工作流的上限。 在调用 Job 显式限制为 read 后,验证被调用工作流是否能按预期运行;再检查其声明的模式。若权限请求超出调用方授予范围,校验错误本身就是应记录的失败证据,而不是通过提升调用方权限绕过。

  6. 分开验收权限、缓存和构建。 查运行日志中恢复与保存步骤的提示,确认是命中、未命中、被模式跳过,还是保存被拒绝;再分别检查 Xcode 构建结果。被禁止的缓存操作可能不让 Job 失败,所以不能仅凭绿色状态判定权限生效。GitHub 缓存管理文档说明了如何查看缓存条目,供你对照运行后的缓存状态。

用证据评分,而不是用速度给权限打分

验收时可以给每个检查项标记“通过、待核、未通过”。只有配置与运行证据都齐全,才算通过;看到了缓存命中但没有核对有效模式,记为待核;低信任 Job 获得无理由的写权限,则记为未通过并先收紧。下面的第二张表将权限证据和补救动作放在一起,便于安全评审留下可复核记录。

验收指标 通过证据 未通过时的收紧动作
最终权限是否符合任务需要 顶层与 Job 级声明相符,已确认 Job 级覆盖值 删除不必要的 Job 级写权限,重新明确基线
低信任触发是否可写 触发器、受影响代码与 cache-mode 均已审查 恢复 read 或 none,把缓存更新移到可信任务
调用方是否限制可复用工作流 调用 Job 显式声明上限,运行结果符合预期 在调用 Job 补上限制;处理越权请求而非扩大授权
缓存操作是否按模式执行 日志能区分恢复、保存、跳过、拒绝或未命中 对照 ACTIONS_CACHE_MODE 和步骤日志复查;必要时关闭缓存
构建是否独立通过 Xcode 构建结果单独记录,不以缓存命中代替 用无缓存或只读配置重跑,定位构建对缓存的隐式依赖

GitHub 文档说明,运行器会通过 ACTIONS_CACHE_MODE 暴露有效模式,缓存操作也会按模式跳过或继续;这可作为核对线索,但你仍需同时审查 YAML 与运行日志。缓存命中只说明找到了可用条目,不证明 Job 仅获得了该任务所需的权限;缓存未命中也不一定表示权限有误。

跨仓库推广前,先在代表性仓库保存配置差异、触发事件、测试结果、日志证据、权限理由和回滚方式。若规则导致可信构建无法更新缓存,回滚应恢复经过审查的可信写入任务,而不是把所有工作流统一改成 write。此外,缓存隔离只是安全控制之一;工作流仍需检查秘密暴露、不可信代码执行、第三方动作和运行器隔离。GitHub 安全使用指南强调,单个动作或工作流环节被攻破也可能影响令牌与秘密,因此 cache-mode 不能被当成完整工作流安全方案。

需要 Mac CI 验证环境时,再比较本地与远程方案

如果现有方案依赖个人主力 Mac,常见代价是测试窗口要与设备使用时间协调、复现环境不易固定、排查时需要直接访问机器;如果只有非 macOS 执行环境,则不能据此验证依赖 Xcode 或 macOS 的构建环节。另一边,托管 CI 能减少自管实体设备的日常维护,但遇到特定工具链或调试需求时,仍需核对其环境与访问方式是否适用。缓存权限验证并不会自动解决这些环境差异。

先完成工作流权限审查,再为需要的 Apple 平台任务准备相应 Mac 环境;可查看 SSHMac 的 Mac mini 租赁方案,评估是否适合短期试跑或隔离测试。若你的工作负载长期稳定、持续高负载,或必须连接特定物理设备,自购与自管 Mac 可能更合适;若只是临时验证 Job 覆盖、可复用工作流继承和 Xcode 构建,短期租用环境往往比挪用主力机更容易控制影响范围。

如果测试还需要人工登录核对 Xcode 环境,可参考 SSHMac 的 VNC 连接说明,确认远程图形会话的访问方式。最终仍应以你实际配置与运行日志确认权限生效,不能把租用 Mac 或缓存命中当作安全验收的替代品。