症状:同一台 Mac 同时保存源码副本、Apple 签名私钥、部署令牌,还能访问内部网络,但 Runner 页面仍显示 Online。

本周建议:先禁止公开仓库和不受信任 Pull Request 使用这台节点;随后建立独立 Runner Group,拆分构建与发布节点,并用“任务后清理、重启恢复、异常停派”三项证据验收。无法彻底重建的共享 Mac,只运行低权限构建。

这篇文章适合需要在 GitHub Actions 中运行 Xcode 构建与 Simulator 测试的移动开发团队,也适合负责 Runner Group、仓库权限、网络边界和节点运维的 DevOps、平台工程师,以及管理 Apple 签名证书、macOS Keychain 和生产发布审批的安全负责人。

01

先划清边界:私有仓库不等于可信 Runner

GitHub Actions 自托管 Mac Runner 安全的第一判断,不是看仓库名称是否为 Private,而是看谁能修改工作流、触发任务,以及任务能控制哪台宿主机。

GitHub 官方明确说明,自托管 Runner 不具备托管 Runner 那种临时、干净虚拟机的持久化隔离保证。不受信任的工作流代码可能修改宿主机,并把影响留给后续任务。公开仓库尤其危险,因为外部贡献者可以通过 Pull Request 触发执行。(docs.github.com)

在上线前,先把节点归入以下三类:

  • 禁止接入:公开仓库、不受信任 Pull Request、可被大量协作者修改的工作流。
  • ⚠️ 隔离试跑:私有仓库,但触发者范围、网络出口或任务后清理还没有证据。
  • 允许上线:受控私有仓库、专用 Runner Group、低权限令牌、独立签名节点,并能完成清理和重建验收。

核实对象包括仓库可见性、分支保护、工作流文件写入权限、Pull Request 审批人、手动触发权限和环境批准人。可观察证据应是一次真实的“允许任务”和一次真实的“拒绝任务”,而不是设置页面截图。

提醒: Runner 显示 Online 只代表它能接收任务,不代表工作区、进程、Keychain 或网络状态可信。安全结论必须来自任务后的检查和重启后的复测。

02

Runner Group 与标签:从“能运行”改成“只能被指定任务使用”

Runner Group 是第一道路由边界。GitHub 支持按组织或企业管理 Runner Group,并通过仓库访问策略限制哪些仓库可以使用其中的 Runner。新建 Runner 默认可能进入默认组,因此不能假设注册完成后就已经完成隔离。(docs.github.com)

核查时记录以下对象:

  • Runner 注册层级:仓库、组织还是企业。
  • Runner 所属 Group:是否仍在默认组。
  • 仓库白名单:是否只包含占位符仓库。
  • 工作流触发条件:是否包含 pull_requestworkflow_dispatch 或不受控分支。
  • runs-on 条件:是否同时指定 Group 和多个标签。
  • 标签命名:是否把标签误当成权限控制。

GitHub 文档说明,工作流可以通过 Group 和标签共同选择 Runner;组合条件必须同时满足。标签适合表达系统类型、芯片架构或用途,但不能单独承担信任边界。真正的访问限制来自 Group 的仓库策略和工作流权限。(docs.github.com)

建议验收一组最小对照:

runs-on:
  group: mac-build-group
  labels:
    - self-hosted
    - macos
    - build

允许任务应来自白名单仓库,并落到预期节点;拒绝任务应来自未授权仓库、错误 Group 或不满足标签的工作流。两者都要保存运行结果、时间、仓库标识和 Runner 名称。

若发现默认组仍可被其他仓库使用,停止派发签名任务。若只能依赖一个容易复制或误用的标签,而没有仓库白名单,也只能进入隔离试跑。

03

凭据分层:构建节点不应同时拥有发布能力

GitHub Actions、self-hosted runner 和 macOS Keychain 的权限边界必须分开处理。一个工作流拥有某个 Secret,不代表宿主机就安全;一个环境设置了人工审批,也不代表宿主机已经隔离。

GitHub 官方指出,环境 Secret 只有在引用该环境且通过必要审批后才会提供给任务,但使用自托管 Runner 时,工作流并不会因此运行在隔离容器中。环境审批能限制发布流程,却不能修复已经被不受信任代码控制的 Mac。(docs.github.com)

我们建议把资产拆成四层:

  1. 普通构建令牌:只读源码、下载依赖或上传测试产物。
  2. 仓库部署密钥:限定单仓库、单环境和最小操作范围。
  3. Apple 签名身份:包含证书与私钥,只存在发布 Runner 的专用 Keychain。
  4. 生产发布权限:独立环境、独立审批人、独立任务,不与普通构建共用。

Apple 文档说明,代码签名身份由证书和私钥共同构成,私钥通常保存在 Mac 的登录 Keychain 中;只有公钥证书本身不能完成签名。(developer.apple.com)

因此,验收对象不是“证书文件是否加密”,而是执行账户能否访问私钥、哪些进程被授权使用 Keychain、工作流是否能读取环境变量,以及任务参数是否把密钥暴露给进程列表或日志。GitHub 也提醒,日志脱敏不是绝对安全边界,任务仍可能主动把秘密变形后发送出去。(docs.github.com)

满足以下条件时,才允许签名节点上线:

  • 只接受受控仓库和受控发布工作流;
  • 构建节点无法读取发布 Runner 的 Keychain;
  • GITHUB_TOKEN 使用最小权限;
  • 发布环境有独立审批;
  • 证书、私钥、部署令牌可以单独轮换。

如果普通 Pull Request 能运行在安装了 Apple Distribution 身份的节点上,应立即停止派单并迁移签名资产。

04

六项隔离指标的上线判断

下面这张表用于区分“可以上线”“只能试跑”和“必须回退”。它不是配置推荐,而是验收结论工具。

安全指标 核实对象 通过证据 可接受结论 停止条件
信任来源 仓库、触发者、工作流修改权 未授权 Pull Request 无法派单 受控私有仓库上线 公开仓库可执行任意任务
任务路由 Runner Group、仓库白名单、标签 允许与拒绝任务结果相反且明确 专用 Group 上线 默认组仍开放给其他仓库
凭据边界 GITHUB_TOKEN、Secret、Keychain 构建任务无法读取发布身份 构建与发布分节点 普通任务可访问签名私钥
任务残留 工作区、缓存、进程、配置 清理后无残留,重启后状态一致 可复用节点 出现未知进程或 Keychain 痕迹
网络权限 内部服务、包仓库、发布接口 允许目标可达,禁止目标被拒绝 最小网络范围 可横向访问管理网络
恢复审计 日志、轮换、重建、备用节点 异常可停派并恢复 具备生产准入证据 只能人工猜测节点是否干净
05

清理工作区不等于清理宿主机

多人共享 Mac Runner 时,最容易被低估的是任务结束后的持久化。删除仓库目录,只能处理一部分源码副本,不能覆盖 DerivedData、Xcode 归档、依赖缓存、用户级配置、临时文件、后台进程和 Keychain 授权。

每次任务结束后,至少执行以下检查:

  • ✅ 仓库副本、构建归档和导出包已删除;
  • DerivedData、依赖缓存和临时目录符合保留策略;
  • ✅ 没有残留的 Shell、编译器、模拟器或下载进程;
  • ✅ 没有新增 LaunchAgent、登录项或用户级配置;
  • ✅ 环境变量写入文件、日志和调试转储已检查;
  • ✅ SSH 密钥、部署令牌和临时证书没有落盘;
  • ✅ Keychain 没有新增身份、授权条目或异常访问记录。

GitHub 建议使用一次性或临时 Runner 来减少前一任务影响后续任务的机会,但官方同时提醒,复用硬件时仍需自动化保证环境干净。JIT 注册只控制 Runner 的生命周期,不等于宿主 Mac 已被彻底清理。(docs.github.com)

经验: 如果节点无法自动重建,且团队没有办法解释某个常驻进程、Keychain 条目或网络连接的来源,就不要继续派发签名、发布和内部系统访问任务。先停派,再人工复核或重置。

06

网络与主机权限:把横向影响限制在任务所需范围

Mac Runner 的风险不仅来自源码。还来自它能访问什么。很多 Xcode CI 节点同时连接 GitHub、私有包仓库、测试 API、发布接口、内部数据库或管理网络。一旦工作流代码获得宿主机控制权,网络边界就决定了影响范围。

GitHub 的自托管 Runner 通常需要向外建立 HTTPS 连接,官方参考中列出 70 kilobits per second 的上下行最低通信要求,以及通过 443 端口进行出站连接的要求。实际任务还可能需要额外访问包仓库、制品服务或团队内部 API。(docs.github.com)

核查对象应包括:

  • Runner 是否需要访问内部管理网;
  • 是否可以访问云元数据服务;
  • 是否能直接连接生产 API;
  • 日常构建是否真的需要 root
  • 是否启用了完整磁盘访问;
  • 是否允许无限制外网;
  • 签名和发布步骤是否能单独使用更窄的出口。

通过条件不是“网络完全断开”,而是建立明确的允许与拒绝清单。例如,允许访问 GitHub、指定包仓库和测试服务;拒绝访问生产数据库、管理接口、未使用的内网网段和不必要的云控制面。

如果某个构建步骤需要高权限,应拆成独立任务,并把它放在专用节点和独立审批之后。若普通测试任务也能以管理员账户运行,或者节点拥有不必要的完整磁盘权限,应回退到低权限构建。

07

从低权限试跑到生产准入

GitHub Actions 自托管 Mac Runner 安全不能靠一次配置完成。我们建议按以下步骤落地:

1.盘点谁能让任务运行

列出仓库管理员、工作流写入者、Pull Request 触发者、手动运行者和环境审批人。对每个角色记录“能修改什么”和“能访问哪个 Runner”。

2.建立信任分级

把公开仓库、不受信任 Pull Request、受控私有仓库和发布工作流分别标记。公开代码不进入带有签名资产或内部网络权限的节点。

3.重建 Runner Group

创建专用 Group,移出默认组,配置仓库白名单,并在工作流中同时使用 Group 与用途标签。保存允许任务和拒绝任务的结果。

4.拆分构建与发布节点

构建节点只负责源码编译、测试和产物生成。发布节点才接触 Apple 签名身份、发布令牌和生产接口。两者使用不同账户、不同 Keychain 和不同网络策略。

5.限制令牌与环境审批

GITHUB_TOKEN 设为最小权限。仓库部署密钥限定仓库范围。发布 Secret 放入独立环境,并要求指定审批人。注意,审批只控制任务是否继续,不会替代宿主机隔离。

6.执行残留检查

模拟一次受控任务,制造临时文件、后台进程和构建缓存,再执行清理。检查工作区、进程、配置、Keychain 和网络连接,而不是只看目录是否为空。

7.验证重启与恢复

重启节点后重新检查 Runner 配置、账户、Keychain、网络规则和工作流状态。若节点无法恢复到已知状态,停止派发敏感任务,执行重置或换用一次性重建环境。

8.完成四类复测

至少覆盖低权限构建、受控签名、恶意残留模拟和节点重启。GitHub 官方建议临时 Runner 只处理单个任务;如果使用长期节点,则必须额外证明清理和恢复流程有效。(docs.github.com)

若团队还没有完成 Mac 节点的重启验收,可先参考我们的 Mac mini 远程租赁方案,重点核对账户隔离、远程访问方式和节点恢复路径,而不是先把生产签名密钥迁移过去。

08

按风险选择最终方案

用下面的条件分支做生产决策:

  • 若仓库公开,或 Pull Request 来源无法确认:不使用自托管 Mac Runner,回退到托管执行环境或无敏感资产的隔离试跑节点。
  • 若仓库私有,但多个仓库共享同一默认 Group:先重建 Runner Group 和仓库白名单,不允许签名任务上线。
  • 若构建与发布共用账户、Keychain 或网络出口:拆分节点;在拆分完成前只运行低权限测试。
  • 若任务后能清理工作区,但不能确认进程、配置和 Keychain 状态:把长期节点视为不可复用,转入人工复核或一次性重建。
  • 若节点可自动重建、发布环境有审批、网络有允许与拒绝证据:可以先迁移非生产仓库,再逐步引入受控签名任务。
  • 若团队需要物理 USB、专用硬件或长期高负载且无法接受远程恢复流程:优先购买并自行维护专用 Mac,不要把租赁节点当成万能替代。

当前使用共享 Mac、普通云主机或临时拼接的 CI 方案,常见缺点是账户边界不清、签名资产和构建缓存混在一起、节点污染后难以证明已经恢复。相比之下,使用 MESHLAUNCH 的远程 Mac 做隔离试跑,可以先验证真实 macOS 环境、独立账户、重启恢复和节点重置,再决定是否迁入生产任务;但签名私钥、内部网络和发布审批仍应由团队自己定义边界。需要了解可用节点时,可从 MESHLAUNCH 的远程 Mac 入口开始,先运行非生产仓库并保留完整验收证据。