Пароли и ключи в исходном коде: поиск в истории, замена скомпрометированных, защита от повторного попадания

изображение: recraft
Самая частая реакция на найденный в коде секрет: удалить строку и закоммитить. Это худшее, что можно сделать, потому что это создает ложное чувство, что проблема решена. Git хранит историю: удаленный из текущей версии ключ остается в предыдущих коммитах, во всех ветках, у всех, кто уже склонировал репозиторий, в форках, в зеркалах и в кешах CI. Как только секрет попал в историю, его нужно считать скомпрометированным, независимо от того, публичный репозиторий или внутренний.
Правильная работа с утечкой — это три отдельные задачи: найти, заменить, не допустить повторно. Разберем по порядку, потому что путать их порядок — дорого.
Найти: сканировать историю, а не текущий срез
Проверять последнюю ревизию бессмысленно: секрет мог быть добавлен и «удален» год назад и все это время лежать в истории. Сканировать нужно всю историю: все коммиты, все ветки, теги.
Инструменты (Gitleaks, trufflehog и аналоги) работают на двух принципах: сигнатуры (регулярные выражения под известные форматы: токены облаков, приватные ключи, connection strings) и энтропия (высокая случайность строки как признак ключа). Часть детекторов умеет еще и проверять найденный токен на живость обращением к провайдеру, это резко сокращает разбор ложных срабатываний.
Что искать, кроме собственно кода: приватные ключи и сертификаты, токены доступа облаков и реестров, пароли в конфигах и .env, строки подключения к БД, ключи API, вебхук-URL с секретом. И не только в файлах кода: секреты регулярно всплывают в логах CI, в описаниях задач, в вики и в комментариях к коммитам.
Заменить: ротация — это и есть починка
Ключевое, что нужно понять: единственное надежное средство — ротация. Удаление из истории вторично. Логика простая: вы не можете гарантировать, что копию истории уже кто-то не забрал. А вот если скомпрометированный ключ отозван и перевыпущен на стороне провайдера, старое значение перестает что-либо открывать, где бы оно ни лежало.
Отсюда и порядок действий:
1. Сначала оцените радиус поражения. Что открывал ключ, где использовался, какие права давал, есть ли следы использования в логах. Это определяет срочность и то, что придется перевыпустить следом.
2. Отзовите и перевыпустите секрет у провайдера. Это останавливает «кровотечение» сразу, не дожидаясь переписывания истории.
3. Обновите потребителей: сервисы, пайплайны, конфиги, переведя их на новый секрет из защищенного хранилища (а не снова в код).
4. Только потом: переписывание истории, если оно вообще нужно.
Порядок именно такой. Если начать с чистки истории, секрет все это время остается валидным и доступным в чужих копиях.
Переписать историю: осторожно и по желанию
Убрать секрет из истории можно (git filter-repo, современный инструмент; BFG Repo-Cleaner для крупных репозиториев; старый filter-branch медленный и чреватый ошибками). Но это операция с последствиями: она переписывает хеши коммитов, требует force-push, и после нее все участники должны переклонировать или аккуратно перебазироваться, иначе вернут удаленное обратно. Форки, резервные копии и уже сделанные клоны при этом все равно могут хранить старое значение.
Именно поэтому переписывание истории: гигиена и compliance, но не защита. Защита — это ротация из предыдущего шага. Переписывайте историю осознанно, а force-push в общие ветки держите под контролем прав (об этом ниже), чтобы такая операция не сломала работу команды случайно.
Не допустить повторно: убрать зависимость от дисциплины
Разовая чистка бессмысленна, если через месяц прилетит новый секрет. Защита строится в несколько эшелонов, и важный принцип: не полагаться на то, что каждый разработчик все сделает правильно.
- Клиентские pre-commit-хуки. Ловят секрет до коммита, на машине разработчика. Дешево и быстро, но обходятся (хук можно отключить), поэтому это первый эшелон, а не единственный.
- Серверная проверка на пуше (pre-receive). Отклоняет пуш с секретом на стороне сервера, независимо от настроек и дисциплины разработчика. Это и есть тот барьер, который реально не дает секрету попасть в историю.
- Секрет-менеджмент вместо секретов в коде. Корень проблемы в том, что секреты в принципе лежат в репозитории. Их место в защищенном хранилище, откуда они выдаются приложению и пайплайну в рантайме, желательно короткоживущими токенами. В коде остается ссылка, а не значение.
- Наименьшие привилегии и короткий срок жизни. Если ключ узкий по правам и живет часы, а не годы, цена утечки радикально падает, а секрет протухает сам.
- `.gitignore` и шаблоны. Файлы окружения в игнор; в репозитории только .env.example с плейсхолдерами.
Как это закрывается на платформе
В нашем случае поиск секретов в self-hosted-контуре реализуется через серверные git-хуки (Gitleaks): проверка идет на стороне сервера, то есть работает как барьер на пуше, а не как необязательная клиентская привычка. Для CI отдельная история: интеграция с Vault, секреты выдаются в пайплайн по JWT и политикам доступа (в редакции Enterprise), так что в самих определениях пайплайна значений нет. Аккуратное переписывание истории поддерживается управлением правами: защищенные ветки, разграничение прав на push/merge и отдельный контроль push —force не дают случайно сломать общие ветки при чистке. А audit logs показывают, кто и когда внес изменение, и это нужно и для оценки радиуса поражения, и для расследования. Все это стандартные механизмы контроля доступа, а не отдельный продукт «поверх».
Чек-лист реагирования на найденный секрет
1. Оцените радиус: что открывал, где использовался, есть ли следы применения.
2. Отзовите и перевыпустите секрет у провайдера — до любых операций с историей.
3. Переведите потребителей на новое значение из защищенного хранилища.
4. При необходимости перепишите историю (git filter-repo/BFG), затем force-push и переклонирование у всех.
5. Включите серверную проверку на пуше, чтобы это не повторилось.
6. Уберите секреты из кода в секрет-менеджер; сократите права и срок жизни ключей.
Правило, которое стоит запомнить одной строкой: удаление скрывает секрет, ротация его обезвреживает. Все остальное: как аккуратно навести порядок после того, как угроза уже снята.

Эдуард Тихомиров, технический директор GitFlic (входит в «Группу Астра»)



