Самостоятельно управляемый Mac Runner для GitHub Actions не следует допускать к коду из публичных репозиториев или других недоверенных источников; на этой неделе сначала ограничьте триггеры и Runner Group, затем разделите сборочный и подписывающий узлы. Приватный репозиторий подходит для запуска только тогда, когда контролируются инициаторы заданий, права токенов, доступ к macOS Keychain и очистка после каждой задачи.

Эта статья предназначена командам, которые выполняют Xcode-сборки и тесты Simulator в GitHub Actions.
Она также нужна DevOps- и платформенным инженерам, отвечающим за Runner Group, сеть и восстановление Mac.
Руководителям безопасности и релизов здесь важны сертификаты, Keychain и граница между сборкой и публикацией.

01

План проверки: что сделать на этой неделе

Мы рекомендуем идти не от статуса Online, а от доказательств изоляции. В первый рабочий день зафиксируйте владельцев репозитория, workflow и Runner Group. Затем соберите матрицу разрешённых целей, проверьте остаточные процессы и проведите тест восстановления.

На время проверки используйте условные обозначения <ORG>, <REPO>, <RUNNER_GROUP>, <BUILD_USER> и <SIGNING_USER>. Не вставляйте в документы реальные имена сертификатов, токены или пути к рабочим каталогам.

Решение должно быть одним из трёх:

  • Запретить подключение — если публичный или недоверенный код может попасть на узел с ключами, внутренней сетью либо постоянным административным доступом.
  • Разрешить изолированный пробный запуск — если выполняются только низкорисковые задачи, а узел ещё не доказал способность к очистке и восстановлению.
  • Допустить в рабочий контур — только после проверки маршрутизации, прав, секретов, остаточных процессов, сети и восстановления.

GitHub прямо предупреждает, что self-hosted runner не получает той же временной и чистой изоляции, которая ожидается от управляемой среды. Для публичных репозиториев и недоверенного кода это принципиальное ограничение, а не мелкая настройка безопасности. См. официальные рекомендации GitHub по безопасному использованию Actions.

Предупреждение. Приватность репозитория не доказывает безопасность кода. Скомпрометированный участник, разрешённый внешний pull request или изменённый workflow могут получить контроль над узлом в пределах доступных ему прав.

02

Первый барьер: доверие к коду важнее статуса приватного репозитория

Проверяйте не только видимость <REPO>, но и то, кто способен изменить workflow, открыть pull request, запустить его, подтвердить environment и изменить настройки Actions. Узел, на котором одновременно находятся исходный код, сертификаты подписи и доступ к внутренней сети, нельзя считать безопасным только потому, что Runner отображается как Online.

Можно ли использовать GitHub Actions self-hosted Runner в публичном репозитории?

Для узла с постоянным состоянием, чувствительными файлами или доступом к подписи — нет. Публичный репозиторий должен либо использовать изолированный одноразовый исполнитель, который можно полностью уничтожить и восстановить, либо работать на узле без секретов, внутренних маршрутов и прав публикации. Обычный долгоживущий Mac Runner для такого кода запрещается.

Проверьте следующие объекты:

  • список пользователей, команд и приложений, которые могут изменить .github/workflows;
  • события push, pull request и ручного запуска;
  • разрешения на одобрение environment <PRODUCTION_ENV>;
  • правила для внешних pull request;
  • кто может зарегистрировать, удалить или перепривязать Runner;
  • кто просматривает логи и артефакты.

У GitHub наличие self-hosted Runner, его метки и принадлежность к организации не превращают недоверенный workflow в доверенный процесс. Поэтому зафиксируйте хотя бы две попытки: разрешённую задачу от утверждённого источника и отклонённую задачу от источника, которому доступ запрещён.

Условие допуска: разрешённый список инициаторов совпадает с политикой репозитория, а запрещённый pull request не получает маршрут на узел.

Условие остановки: любой автор workflow может направить задачу на Mac с signing-материалом или сетевым доступом, даже если задача формально проходит через approval.

03

Runner Group против одиночной метки: проверяем реальный маршрут

Метки вроде self-hosted, macos или <MAC_LABEL> удобны для выбора исполнителя, но сами по себе не являются границей доверия. Ошибочная метка, общий Runner Group или настройка на уровне организации могут направить задачу из другого репозитория на тот же компьютер.

Сначала определите уровень регистрации:

  • репозиторий;
  • организация;
  • предприятие.

Затем сопоставьте его с разрешёнными репозиториями и группами. В настройке <RUNNER_GROUP> должна быть явная белая списоковая модель: <REPO_A> разрешён, <REPO_B> запрещён. Проверьте также, не имеет ли группа доступа по умолчанию к новым репозиториям.

Официальная модель Runner Group описана в документации GitHub о группах исполнителей. Правила выбора исполнителя и совместного использования меток нужно сверять с документом о маршрутизации заданий.

Как предотвратить чтение файлов между проектами на общем Mac Runner?

Не полагайтесь на смену рабочей директории. Разделите репозитории по Runner Group и, если риск высок, по отдельным узлам и системным пользователям. После этого подтвердите изоляцию отрицательным тестом: задача <REPO_B> должна получить отказ и не должна увидеть рабочий каталог, артефакт или процесс <REPO_A>.

Критерии приёмки:

  • [ ] <RUNNER_GROUP> доступна только заявленным репозиториям.
  • [ ] workflow использует конкретный набор меток и не зависит от общей метки.
  • [ ] разрешённая задача записана в журнале маршрутизации.
  • [ ] запрещённая задача получает отказ.
  • [ ] новый репозиторий не наследует доступ случайно.
  • [ ] ручной запуск проверен отдельно от запуска по push.

Условие остановки: если нельзя доказать, какой именно репозиторий получил узел, чувствительные задания на нём прекращаются до исправления маршрута.

04

Второй барьер: токен, Keychain и подпись не должны жить в одном слое

Для Xcode CI различайте как минимум четыре класса доступа: обычный GITHUB_TOKEN, ключи развёртывания, идентичность Apple для подписи и права публикации. Объединение этих классов в одну учётную запись <BUILD_USER> превращает ошибку в workflow в инцидент с более широким радиусом.

Проверьте:

  1. какие разрешения выдаются GITHUB_TOKEN;
  2. получает ли задача права записи, когда ей достаточно чтения;
  3. где хранится импортированный сертификат;
  4. какой пользователь открывает macOS Keychain;
  5. может ли сборочная задача вызвать публикацию;
  6. кто утверждает environment <PRODUCTION_ENV>;
  7. когда и как отзываются секреты после подозрительной задачи.

Apple описывает внутреннюю модель сертификатов и цепочку доверия в технической заметке о code signing certificates. Это не означает, что любой процесс на узле автоматически получает права подписи. Но если workflow выполняется в той же пользовательской сессии и имеет доступ к разблокированному Keychain, контроль над workflow становится контролем над доступными ему ключами.

Где должна находиться подпись Xcode CI?

Если задача только компилирует и запускает Simulator, сертификаты распространения и права публикации ей не нужны. Разместите такие материалы на отдельном <SIGNING_RUNNER>, доступном только доверенному workflow. Сборочный <BUILD_RUNNER> должен работать без production signing identity, а передачу артефакта между узлами нужно делать через явно разрешённый канал с отдельным approval.

Environment approval снижает риск случайной публикации, но не очищает уже скомпрометированный Mac. Если недоверенный код получил контроль над хостом, он может искать доступные файлы, процессы, настройки и сетевые цели до момента approval. Поэтому approval — дополнительный барьер, а не замена разделению узлов.

Условие допуска: низкорисковая сборка не видит signing Keychain, а релизная задача запускается только на отдельном узле и после явного подтверждения.

Условие остановки: сертификат, ключ или профиль подписи доступен процессам общего сборочного пользователя либо остаётся разблокированным после задачи.

05

Третий барьер: очистка рабочей области не равна очистке Mac

Удаление каталога checkout — только одна операция. После Xcode-сборки остаются DerivedData, временные файлы, логи, архивы, настройки пользователя, фоновые процессы и возможные следы секретов. На долгоживущем Mac необходимо проверять состояние хоста, а не только результат команды rm.

Что очищать после каждого задания Mac Runner?

Минимальный перечень зависит от политики команды, но проверка должна охватывать:

  • каталог репозитория и вложенные рабочие копии;
  • DerivedData, архивы и временные каталоги Xcode;
  • логи, кэш менеджеров пакетов и артефакты;
  • процессы, запущенные workflow;
  • launch agents и пользовательские фоновые задания;
  • переменные окружения и временные файлы с токенами;
  • состояние Keychain и временные профили подписи;
  • изменения в конфигурации SSH, shell и системных разрешениях;
  • открытые сетевые соединения после завершения задачи.

Попросите задачу создать контрольный файл в <WORKSPACE_A>, запустить фоновый процесс и записать тестовый маркер в <TEMP_PATH>. После завершения другой низкорисковой задачей проверьте, что маркер, процесс и путь недоступны. Затем отдельно выполните проверку после перезагрузки.

Опыт эксплуатации. Узел, который нельзя автоматически пересоздать или хотя бы достоверно вернуть к исходному состоянию, не должен принимать секреты публикации. Для него безопаснее оставить только низкоправные сборки и назначить ручную проверку перед любым изменением класса задач.

Как понять, что долгоживущий self-hosted runner загрязнён?

Остановите выдачу новых задач, если обнаружено неизвестное постоянное задание, изменился системный или пользовательский конфигурационный файл, остался процесс предыдущего репозитория, появился неожиданный доступ к Keychain или нельзя объяснить сетевое соединение. Перезапуск Runner-службы не доказывает чистоту хоста. При отсутствии надёжного baseline переходите к пересозданию узла либо ручной криминалистической проверке.

06

Четвёртый барьер: сеть и права хоста ограничивают последствия

Составьте карту доступов Mac Runner: GitHub API, хранилища пакетов, внутренние Git-сервисы, тестовые API, signing-сервисы, системы публикации, SSH-цели и панели администрирования. Для каждой цели укажите, нужна ли она конкретной задаче или доступ появился исторически.

Отдельно проверьте:

  • нужен ли workflow root;
  • требуется ли полный доступ к диску;
  • можно ли убрать административную учётную запись;
  • какие исходящие адреса обязательны;
  • какие внутренние подсети должны быть недоступны;
  • может ли задача подключаться к производственным API;
  • разрешены ли входящие соединения к Mac Runner.

Если Xcode-тесту нужен Simulator, это не доказывает необходимость доступа к базе данных производства. Если fastlane-публикации нужен signing endpoint, это не доказывает необходимость SSH в административную сеть.

Создайте тестовую матрицу: разрешённая цель <ALLOWED_TARGET> должна отвечать, а запрещённая <DENIED_TARGET> — блокироваться и фиксироваться в журнале. Проверяйте это из фактического контекста <BUILD_USER>, а не только из административной оболочки.

Условие допуска: привилегированный шаг выделен в отдельную задачу, имеет собственный approval и не наследует права всего workflow.

Условие остановки: обычный pull request получает root, полный доступ к диску или маршрут во внутреннюю сеть без отдельного обоснования.

07

Пятый барьер: восстановление важнее зелёного статуса Online

Финальная проверка должна моделировать не только успешную сборку. Выполните четыре класса тестов:

  • низкоправная сборка без signing-материалов;
  • контролируемая подпись на отдельном узле;
  • имитация остаточного файла, процесса и изменения конфигурации;
  • перезагрузка и повторная регистрация после восстановления.

JIT-регистрация может сократить время существования регистрации Runner, но не означает, что физический Mac очищен от процессов, файлов или изменений. Не подменяйте жизненный цикл регистрации жизненным циклом доверия к хосту.

Проверьте журналы: кто запустил задачу, какой workflow использовал узел, какой environment был одобрен, какие артефакты были созданы и когда были отозваны временные секреты. В документации GitHub о компрометации self-hosted Runner отдельно рассматривается необходимость считать такой узел потенциально скомпрометированным, а не просто перезапускать службу.

Для процедуры регистрации и эксплуатации сверяйте также документацию GitHub о self-hosted runners. При изменении модели Runner Group, JIT, предупреждений о безопасности или правил Apple для сертификатов проверку нужно повторить.

08

Матрица решения: какой Mac Runner допускать

Используйте следующую развилку до покупки или аренды узла. Она не заменяет техническую проверку, но не даёт смешать задачи с разными уровнями доверия.

  • Если код публичный или внешний pull request не проходит строгую изоляцию, то используйте только временный, уничтожаемый исполнитель без секретов; долгоживущий Mac Runner исключите.
  • Если узел нельзя пересоздать, а очистка не подтверждается процессами, файлами и настройками, то оставьте только низкорисковые сборки и отклоните подпись.
  • Если несколько репозиториев используют один Mac, но для них нет раздельных Runner Group, пользователей и отрицательных тестов маршрутизации, то разделите узлы или запретите совместное использование.
  • Если Xcode-сборка не требует подписи, то направьте её на <BUILD_RUNNER> без signing Keychain.
  • Если публикация требует сертификата и production-доступа, то используйте отдельный <SIGNING_RUNNER> с approval и независимым журналом.
  • Если сетевой список содержит неиспользуемые внутренние цели, то сначала сократите маршруты, а затем повторите тест разрешённых и запрещённых адресов.
  • Если после перезагрузки узел возвращается в известное состояние и есть резервный путь восстановления, то можно рассматривать его для рабочего контура.
  • Если хотя бы одно доказательство отсутствует, то вернитесь к пробному режиму и не переносите на узел production signing.
Схема Допустимые задачи Обязательное условие Решение
Общий долгоживущий Mac Низкорисковые сборки и Simulator-тесты Нет секретов, ограниченная сеть, проверяемая очистка Только ограниченный запуск
Отдельный сборочный узел Сборка, тесты, подготовка артефактов Runner Group и права токена ограничены Допуск после тестов
Отдельный узел подписи Подпись и публикация Выделенный пользователь, Keychain, approval и аудит Допуск для доверенных workflow
Пересоздаваемый узел Недоверенные или краткоживущие задачи Полное восстановление и отсутствие постоянных секретов Предпочтительный вариант
Узел без подтверждённого восстановления Любые чувствительные задачи Доказательств чистоты нет Остановить выдачу заданий

Если собственной команде нужен такой контур, сначала оформите руководство по удалённому Mac для CI-сценариев, а затем проверьте на отдельном узле перезагрузку, учётные записи и восстановление. Для сравнения доступных вариантов Mac можно использовать страницу оформления Mac mini, но решение о подписи принимайте только после фактической проверки изоляции.

09

Когда удалённый Mac лучше общего локального узла

Собственный общий Mac часто выглядит дешевле и проще, но у него есть три системных недостатка: состояние накапливается между задачами, физический доступ и ручные настройки трудно доказать, а разделение сборки и подписи быстро превращается в набор исключений. Виртуализация или Linux-узел также не решают задачу, если требуются реальный macOS, Xcode и Apple signing.

Удалённый Mac от MESHLAUNCH может быть более удобным вариантом для временного тестового контура, миграции CI или проверки схемы разделения. Но аренда не отменяет требования к Runner Group, Keychain, сети и очистке. Перед переносом чувствительных задач проверьте независимую учётную запись, поведение после перезагрузки и процедуру сброса узла. Если нужен только краткосрочный эксперимент без production-ключей, это обычно рациональнее, чем оставлять секреты на общем компьютере без доказуемого восстановления.

Для постоянной тяжёлой нагрузки, физического оборудования или требований к полностью контролируемому железу покупка и самостоятельное администрирование могут быть оправданы. Для временной CI-проверки, изолированного Xcode-теста или резервного Mac Runner аренда MESHLAUNCH позволяет сначала подтвердить модель безопасности, а уже потом принимать долгосрочное инфраструктурное решение.

Итоговый критерий прост: не принимайте узел из-за статуса Online. Принимайте его только после доказательств доверия к коду, ограниченного маршрута, раздельных секретов, чистого состояния, минимальной сети и воспроизводимого восстановления.