Смена владельца репозитория: проверьте доступ до передачи
Смена владельца репозитория требует большего, чем перенос Git-проекта. Проверьте людей, токены, ключи для деплоя, рабочие процессы, приложения и действия агентов, пока доступ ещё не сохранился.

Смена владельца репозитория - это событие безопасности, даже если перенос выглядит обычной административной задачей. Репозиторий может перейти новой команде за один день, но связанные с ним разрешения останутся у бывших сопровождающих, устаревшей автоматизации, старых облачных аккаунтов и учётных данных, к которым никто не обращался месяцами.
Я видел, как команды объявляли передачу завершённой после смены владельца репозитория и удаления двух людей со страницы участников. Затем забытая задача деплоя продолжала публиковать данные с личным токеном, бесхозный SSH-ключ сохранял доступ к закрытому исходному коду, а старое приложение для Git-хостинга всё ещё могло менять пул-реквесты. Страница репозитория выглядела чистой. Реальная система управления доступом - нет.
Практическое правило простое: передавайте владение только после того, как сможете назвать каждый идентификатор и сервис, способный вызвать существенный эффект. К таким эффектам относятся чтение непубличного кода, отправка коммитов, создание или одобрение пул-реквестов, изменение описаний сборки, доступ к секретам, публикация пакетов, деплой программного обеспечения и изменение самого доступа.
Передача репозитория не означает передачу ответственности
Владелец репозитория - это административная отметка. Ответственность означает, что текущий человек может объяснить, с чем связан репозиторий, зачем нужно каждое подключение, кто его обслуживает и как отозвать доступ во время инцидента.
Это разные вещи. Разрешения Git-хостинга описывают доступ внутри одного сервиса. Современные проекты также зависят от CI-раннеров, реестров пакетов, облачных ролей, приложений для сканирования кода, ботов релизов, публикации документации, уведомлений в чатах, DNS-провайдеров и хранилищ артефактов. У каждой системы свои идентификаторы и собственная память о проекте.
Разница особенно важна, когда проект переходит из-за реорганизации, поглощения, внутренней миграции платформы или ухода сотрудников. Принимающая команда обычно сосредоточена на том, чтобы сборка не сломалась. Уходящая команда хочет удалить свои имена. Ни одна из этих задач не доказывает, что контроль был передан безопасно.
Зафиксируйте границу владения до того, как кто-либо начнёт менять доступ. В ней нужно указать:
- технического владельца со стороны принимающей команды, который берёт на себя операционную ответственность;
- бизнес-владельца, который решает, кто может обращаться к коду и релизам;
- контакт для инцидентов, способный разрешить срочный отзыв доступа;
- источник истины для состава участников репозитория и сервисных идентификаторов;
- дату, когда прежняя команда теряет полномочия.
Не назначайте окончательным владельцем общий почтовый ящик или расплывчатое название команды. Группа может получать письма, но не может принять решение в два часа ночи. Укажите конкретных людей и пересматривайте эту запись при изменении состава команды.
Есть и другая неприятная сторона. Проект без текущего владельца не должен сохранять широкий доступ к production только потому, что его сборка всё ещё проходит. Если никто не может безопасно ответить за учётные данные для деплоя, приостановите релизы или сократите разрешения, пока такой человек не появится. Доступность не оправдывает сохранение неизвестных полномочий.
Составляйте список доступа по возможным действиям, а не по странице репозитория
Полезный список доступа начинается с действий, которые могут произойти, а затем возвращается к идентификаторам, способным их выполнить. Начать со списка участников быстрее, но так вы многое пропустите.
Спросите, что может произойти с проектом без разработчика, сидящего на сайте Git-хостинга. Коммит может прийти от бота. Рабочий процесс CI может получить облачный токен. Пакет может опубликоваться после появления тега. Получатель вебхука может запустить деплой в production. Приложение для ревью кода может оставить комментарий или изменить проверки. Каждое такое действие указывает на разрешение, у которого должен быть владелец.
Используйте рабочую таблицу из пяти столбцов: возможность, идентификатор, расположение учётных данных, текущий владелец и процедура отзыва. Последнему столбцу нужно уделить отдельное внимание, потому что «удалить токен» часто не означает настоящую процедуру. Может потребоваться удалить приложение, убрать правило доверия в облаке, удалить ключ для деплоя, сделать недействительным токен реестра или сменить секрет вебхука на обоих концах.
Первый проход должен охватить такие категории:
- Человеческие идентификаторы, включая прямых участников, команды организации, внешних участников и администраторов организации.
- Нечеловеческие идентификаторы, включая машинных пользователей, сервисные аккаунты, идентификаторы CI-раннеров и установки приложений.
- Учётные данные, включая SSH-ключи для деплоя, личные токены доступа, закрытые ключи приложений, секреты вебхуков, токены реестров и облачные учётные данные.
- Пути выполнения, включая файлы рабочих процессов, ссылки на переиспользуемые рабочие процессы, группы раннеров, окружения деплоя, скрипты релизов и задачи по расписанию.
- Каналы вывода данных, включая вебхуки, публикацию пакетов, публикацию документации, резервные копии, зеркала, интеграции с задачами и сервисы уведомлений.
В списке нужно простыми словами указать назначение каждой учётной записи. «Токен CI» недостаточно. «Читает исходный код и публикует внутренний пакет командной строки после отправки подписанного релизного тега» подсказывает следующему сопровождающему, что проверять и какой риск возникнет при сбое.
Вы найдёте записи, которые никто не узнаёт. Не оставляйте их активными только из-за правдоподобного имени. Попросите доказательства: где всё настроено, какая задача недавно использовала доступ, что именно разрешено и кто будет владельцем после передачи. Если ответ остаётся расплывчатым, запланируйте удаление. Неизвестные учётные данные обычно становятся известны только после инцидента.
Личные учётные данные делают передачу хрупкой
Любой путь в production или к релизу, зависящий от личного токена одного сопровождающего, уже давно пора заменить. Переезд проекта лишь показывает эту слабость.
Личные учётные данные создают два сценария сбоя. Первый очевиден: бывший сопровождающий может сохранить доступ после ухода из проекта. Второй встречается чаще: его аккаунт отключают или срок действия токена заканчивается, и путь к релизу ломается именно тогда, когда он больше всего нужен принимающей команде. Команды часто просят бывшего сопровождающего создать ещё один токен. Это устраняет немедленную проблему и ещё сильнее закрепляет зависимость.
Заменяйте личные учётные данные сервисным идентификатором только тогда, когда сервису действительно нужен постоянный идентификатор. Дайте ему минимальный набор разрешений для документированной задачи. Публикатор релизов может иметь право публиковать один пакет. Ему не нужны широкие административные права в организации или доступ ко всем репозиториям.
Не путайте машинного пользователя с хорошо управляемым сервисным идентификатором. Машинный пользователь - это просто аккаунт, который использует автоматизация. У него всё ещё могут быть неизвестный пароль, личная почта для восстановления, широкие права и отсутствие владельца. Обращайтесь с ним как с обычным идентификатором с полным жизненным циклом: создавайте его осознанно, документируйте владельца, пересматривайте членство и удаляйте, когда задача завершена.
Именно здесь не работает популярный совет «просто используйте один общий аккаунт автоматизации». Он популярен, потому что быстро запускает автоматизацию и избавляет от необходимости разбираться в каждой интеграции. Но он также складывает несвязанные разрешения в один идентификатор. Когда один проект переходит в другую команду, никто не может отозвать его доступ, не подвергнув риску все остальные проекты, зависящие от этого аккаунта.
Разделяйте идентификаторы по операционному назначению. Читателю сборки, публикатору релиза и исполнителю деплоя в production часто нужны разные права и разные владельцы. Такое разделение делает отзыв менее рискованным, а расследование инцидентов - более понятным.
Проверяйте пользовательские токены и за пределами Git-хостинга. Скрипт деплоя может читать токен из секрета CI, но этот токен может принадлежать облачному аккаунту ушедшего сотрудника. Расположение секрета не говорит о его полномочиях. Найдите источник выдачи токена и проверьте разрешения там.
Ключи для деплоя и установки приложений нужно проверять отдельно
Ключи для деплоя, приложения Git-хостинга и интеграции OAuth дают доступ к репозиторию, но выходят из строя по-разному. Если свести их в один список, отзыв получится небрежным.
Ключ для деплоя обычно представляет собой открытый SSH-ключ, прикреплённый к репозиторию. В зависимости от настройки он может давать право на чтение или запись. Его преимущество - узкая область привязки. Его слабость - слабая идентификация: ключ почти ничего не говорит о системе, в которой хранится закрытая половина. Если в комментарии написано «сервер сборки», а этот сервер дважды сменил владельца, запись репозитория не поможет разобраться.
Для каждого ключа проверьте четыре факта: где лежит закрытый ключ, какой процесс его использует, нужно ли ему право записи и кто владеет хостом или хранилищем секретов, где он находится. Уберите право записи у ключей, которые нужны только для получения исходного кода. Удалите любой ключ, который нельзя связать с действующей системой и конкретным владельцем.
Установка приложения создаёт обратную проблему. Обычно у неё лучше определены идентификатор, разрешения и история событий, но приложение может быть установлено во множестве репозиториев. Удаление его из одного проекта может не остановить связанный сервис в другом месте. Проверьте запрошенные разрешения приложения, область установки, процесс ротации закрытого ключа, URL обратных вызовов и аккаунт организации, который может изменить установку.
Документация GitHub разделяет ключи для деплоя и GitHub Apps не случайно. Ключи привязываются к репозиториям, а приложение получает разрешения через установку и использует собственные учётные данные. Не думайте, что удаление ключа для деплоя повлияет на доступ приложения или что удаление приложения сделает SSH-ключ недействительным. Это независимые пути полномочий.
Интеграции OAuth заслуживают такой же осторожности. Они могут действовать от имени пользователя, а не отдельного идентификатора приложения. При передаче выясните, зависит ли авторизация интеграции от бывшего сопровождающего. Если да, переведите её на поддерживаемый сервисный идентификатор или удалите. Ожидание ухода человека из компании не является планом отзыва доступа.
Файлы рабочих процессов могут давать больше полномочий, чем обещают их названия
Рабочий процесс, который выглядит как запуск тестов, всё равно может получать учётные данные, вызывать переиспользуемые рабочие процессы, записывать данные в репозиторий или запускать системы деплоя. Прочитайте файл, прежде чем считать его доступ безопасным.
Проверьте всю исполняемую конфигурацию репозитория, а не только рабочий процесс релиза. Сюда входят определения CI, вызываемые ими скрипты, настройки обновления зависимостей, манифесты деплоя, инфраструктурный код, параметры публикации пакетов и скрипты, запускаемые комментариями или пул-реквестами.
Обратите внимание на места, где задача пересекает границу доверия. Например, рабочий процесс может обменивать токен идентификации на облачную роль, запускать код из пул-реквеста с правом записи в репозиторий или подключать переиспользуемый рабочий процесс из другого репозитория. Название рабочего процесса может быть «линтинг». Правду о его возможностях говорят разрешения.
Документация GitHub Actions предупреждает, что pull_request_target выполняется в контексте базового репозитория и может получать полномочия, недоступные обычному рабочему процессу пул-реквеста. Само по себе это событие не является ошибкой. Ошибка возникает, если вместе с ним выполняется недоверенный код пул-реквеста или скрипты, на которые может повлиять внешний участник. Во время передачи найдите такие рабочие процессы и добейтесь, чтобы принимающая команда явно их приняла.
Простой поиск по репозиторию помогает обнаружить многие очевидные ссылки. Выполните его локально после получения всей истории репозитория, которую нужно проверить:
git grep -nE '(AWS_|AZURE_|GCP_|TOKEN|SECRET|DEPLOY|PUBLISH|ssh |curl |webhook)' -- \
'.github' '.gitlab-ci.yml' 'scripts' 'infra' 'package.json' 2>/dev/null
Результат может выглядеть так:
.github/workflows/release.yml:42: id-token: write
scripts/publish.sh:18: curl -H "Authorization: Bearer $REGISTRY_TOKEN"
infra/deploy.sh:9: ssh -i "$DEPLOY_KEY" "$DEPLOY_HOST"
Команда не доказывает наличие секрета или опасность задачи. Она создаёт очередь для проверки. Проследите каждый результат до источника учётных данных, области разрешений и сценария сбоя. Также ищите ссылки на рабочие процессы за пределами репозитория: код может получать полномочия через переиспользуемый рабочий процесс, которым этот репозиторий не управляет.
Не выдавайте широкие разрешения по умолчанию только для того, чтобы унаследованный рабочий процесс прошёл. Исправьте объявленные разрешения задачи и проверьте единственное действие, которое она должна выполнять. Передача - хорошее время, чтобы убрать разрешения, сохранившиеся лишь потому, что никто не хотел трогать старый конвейер.
Отзывайте доступ в порядке, который сохраняет доказательства и предотвращает сбои
Для отзыва нужен правильный порядок. Если удалить всё сразу, можно потерять доказательства активной зависимости. Если ждать идеальной документации, старый доступ может оставаться бесконечно.
На время проверки приостановите необязательные изменения. Принимающий владелец должен видеть появление новой установки приложения, нового ключа для деплоя или нового администратора организации до завершения базовой проверки. Это не требует остановки обычной разработки, но требует видимости изменений.
Сделайте экспорт или сохранённый снимок состава участников, списка внешних участников, ключей для деплоя, установок приложений, вебхуков, метаданных секретов CI, облачных отношений доверия и последних событий аудита. Не сохраняйте в этой записи значения секретов. Сохраните идентификаторы, области действия, владельцев, контекст создания, если он доступен, и время проверки.
Затем действуйте в таком порядке:
- Удалите прямой доступ бывших сопровождающих и сократите права бывших администраторов организации, если этого требует передача.
- Отключите или удалите неизвестные интеграции и отзовите ключи для деплоя, у которых нет действующего владельца.
- Замените известные личные учётные данные сервисными идентификаторами, затем проверьте точный путь сборки, публикации или деплоя.
- После успешной замены смените общие секреты, например секреты вебхуков, закрытые ключи приложений и учётные данные реестров.
- Проверьте события аудита и неудачные задачи за период, соответствующий ритму релизов проекта, затем удалите временные исключения.
Такой порядок отделяет неизвестные полномочия от известных зависимостей. Неизвестный ключ для деплоя не выполняет поддерживаемой функции, поэтому его можно удалить рано. Для известной учётной записи релиза сначала нужна замена, иначе исправление безопасности превратится в предотвратимый простой.
Заранее определите аварийный путь для неудачной замены. В нём должны быть указаны человек, который может восстановить сервис, срок действия исключения и способ его зафиксировать. Не возвращайте широкие права ушедшего сопровождающего только потому, что релиз задерживается. Создайте временные узкие учётные данные под контролем текущего владельца, запишите исключение и удалите его после исправления.
Доступ агентов должен подчиняться той же границе владения
Автономные агенты для программирования могут редактировать код, вызывать API, использовать SSH, публиковать артефакты и влиять на инфраструктуру через подключённые инструменты. Передача репозитория без проверки доступа агентов оставляет большой пробел.
Не спрашивайте только, кто может запустить агента. Нужно выяснить, какие процессы агентов могут действовать от имени репозитория, какие инструменты они вызывают, какие учётные данные используют эти инструменты и сможет ли проверяющий позже восстановить конкретное действие. Агент, запущенный из оболочки разработчика, задания CI или удалённого раннера, может иметь разные полномочия, даже если использует одну и ту же модель.
Храните долгоживущие учётные данные вне промпта и рабочих файлов агента. Передача секрета через переменные окружения или вывод инструмента делает его доступным журналам, дочерним процессам, случайным коммитам и собственному контексту агента. Маскирование значения в одном просмотрщике журналов не делает границу процесса безопасной.
Для команд, использующих Sallyport, приложение Mac может хранить HTTP- и SSH-учётные данные в зашифрованном хранилище, а агент с поддержкой MCP запрашивает действие через локальный промежуточный слой, не получая саму учётную запись. Журналы сессий и активности дают принимающему владельцу отдельные записи запусков агентов и отдельных вызовов. При проверке передачи это полезнее одной стенограммы.
Во время перехода отзовите сессии агентов, связанные со старыми рабочими станциями или процессами, и попросите нового владельца одобрить новые запуски. Затем проверьте использование каждой учётной записи: получение исходного кода только для чтения и деплой в production не должны проходить по одному и тому же сценарию подтверждения. Цель не в том, чтобы сделать каждую команду мучительной. Нужно сделать важные вызовы видимыми для человека, отвечающего за последствия.
В письменном списке агентов укажите область репозитория, место выполнения, каналы инструментов, владельца учётных данных, владельца действия и экстренную операцию отзыва. Если эти поля нельзя заполнить, доступ агента нельзя ответственно передать проекту.
Записи аудита должны показывать, кто изменил контроль
Записи «токен использован» недостаточно после смены владельца. Нужно знать, чей это был токен, какой процесс его использовал, что он сделал, на какой репозиторий воздействовал и было ли действие разрешено при новом владельце.
Храните вместе в файле передачи или системе инцидентов события аудита репозитория, записи выполнения CI, облачные записи аудита, события реестра пакетов и записи действий агентов. Они не обязаны иметь один формат. Важно использовать общее время и достаточное количество идентификаторов, чтобы проследить событие между системами.
Проверьте записи до того, как объявить передачу завершённой. Выполните безвредное изменение через каждый поддерживаемый путь: обычную отправку коммита разработчиком, событие автоматизации пул-реквеста, задачу по расписанию, если она есть, публикацию пакета в непроизводственную среду, если она доступна, и действие агента, требующее подтверждения. Убедитесь, что принимающая команда может найти доказательства без обращения к бывшим сопровождающим.
Защита от изменений полезна, но не заменяет хранение и контроль доступа. Журнал только для добавления показывает, изменил ли кто-то запись. Но он не поможет, если источник события никогда не записал вызов, срок хранения истёк или во время инцидента ни один текущий человек не может прочитать журнал.
Назначьте дату проверки после переезда. Первая проверка обнаружит автоматизацию, запускающуюся раз в неделю или месяц, а вторая - людей, которые попросят исключение после остановки какой-либо функции. Такие исключения полезны для диагностики. Каждое показывает зависимость, которую пропустил первоначальный список.
Принимающая команда должна уметь удалить любой путь доступа
Передача завершена, когда принимающая команда может отозвать любой существенный путь доступа, не прося прежнюю команду объяснить секрет, найти машину или одобрить изменение. Этот стандарт строже диалога передачи репозитория и предотвращает повторяющийся сбой: владение кодом меняется на бумаге, а контроль остаётся разбросанным по другим системам.
Начните с самого неприметного артефакта, списка доступа. Рядом с каждым подключённым сервисом укажите владельца и процедуру отзыва. Затем удалите записи, у которых нет ни того, ни другого. Работа кажется скучной до первой срочной ротации учётных данных. Тогда список становится разницей между контролируемым исправлением и неделей догадок.
Вопросы и ответы
Что должно произойти, когда программный репозиторий меняет владельца?
Относитесь к передаче как к проверке доступа, а не к административному переименованию. Уточните, кто управляет репозиторием, затем составьте список всех идентификаторов и подключённых сервисов, которые могут читать, изменять, развёртывать, публиковать или администрировать его.
Отменяет ли удаление прежних сопровождающих весь доступ к репозиторию?
Нет. Удаление пользователя из репозитория часто оставляет активными ключи для деплоя, машинных пользователей, токены пакетов, установки приложений и облачные идентификаторы. Для каждого из них нужен отдельный способ отзыва.
Безопасно ли передавать репозиторий без аудита доступа?
Только если новая команда сначала проверила все возможные действия. Тихая передача может сохранить неизвестную автоматизацию, унаследованные полномочия организации и учётные данные людей, которые больше не отвечают за работу проекта.
Какие подключённые сервисы нужно проверить после передачи репозитория?
Начните с прямых участников, команд, ролей в организации, ключей для деплоя, токенов доступа, приложений Git-хостинга, SSH-ключей, идентификаторов CI, облачных ролей, реестров пакетов и вебхуков. Затем добавьте любую систему, которая получает изменения исходного кода или может публиковать сборку.
Остаются ли ключи для деплоя безопасными после перехода проекта в другую команду?
Да, если ключ принадлежит документированному сервисному аккаунту с узкой задачей и действующим владельцем. SSH-ключ, для которого неизвестны владелец, область действия или назначение, лучше удалить, а не сохранять ради удобства.
Должен ли каждый разработчик новой команды быть администратором репозитория?
Нет. Администратор репозитория обычно может менять защиту веток, редактировать рабочие процессы, добавлять учётные данные и выдавать доступ другим пользователям. Административные права должны оставаться только у тех, кто отвечает за такие изменения.
В каком порядке лучше менять учётные данные репозитория?
Сначала перечислите все учётные данные, сервисные идентификаторы и адреса назначения вебхуков, а затем удаляйте доступ. Сначала отзовите неизвестные или личные учётные данные, затем смените общие секреты и проверьте документированную автоматизацию с новым идентификатором.
Заменяют ли журналы аудита репозитория журналы CI и деплоя?
Нужны и те, и другие. Журналы аудита Git-хостинга объясняют изменения разрешений и конфигурации, а журналы CI показывают, что выполнялось после изменения. Храните их достаточно долго, чтобы восстановить ход инцидента, начавшегося до передачи.
Как контролировать ИИ-агентов для программирования при смене владельца?
Каждое чувствительное действие должно быть связано с ответственным человеком и конкретным запуском агента. Шлюз, который хранит учётные данные вне агента, может записать вызов, не помещая долгоживущие секреты в контекст агента.
Как доказать, что прежняя команда больше не управляет репозиторием?
Сначала определите сервисы, которые всё ещё могут действовать от имени репозитория после ухода прежней команды. Владелец должен быть указан в записях доступа, учётных данных, конфигурации рабочих процессов и списке контактов для инцидентов.