Перейти к содержимому

Утечка ключа в git: почему удаление файла не стирает секрет из истории

Вы удалили ключ следующим коммитом - но из Git-репозитория он никуда не исчез

Утечка секрета в Git обычно происходит по одному сценарию. Разработчик случайно добавляет в коммит файл с токеном, паролем или ключом доступа. Через некоторое время ошибка обнаруживается: файл удаляют, изменения сохраняют отдельным коммитом - и кажется, что проблема решена.

На самом деле удаление файла убирает его только из текущего состояния проекта. В предыдущем коммите содержимое продолжает существовать, а значит, его можно извлечь из истории. Если репозиторий публичный, секрет могли скопировать или автоматически обнаружить ещё до того, как разработчик заметил ошибку.

Почему удаление файла не помогает

Git хранит не только актуальные файлы, но и всю последовательность изменений. Каждая зафиксированная версия становится частью базы объектов. Когда выполняется команда:

```bash
git rm config.env
git commit -m "Удалён файл с настройками"
```

в истории появляется новое дерево проекта, где файла уже нет. Однако объект с прежним содержимым не уничтожается. Он по-прежнему связан со старым коммитом и доступен при просмотре истории.

Проверить наличие секрета можно, например, так:

```bash
git log --all --full-history -- config.env
```

Для поиска текста по версиям используют:

```bash
git grep "SECRET" $(git rev-list --all)
```

Даже если файл давно переименовали или удалили, его следы могут сохраняться в старых ветках, тегах и недостижимых объектах.

История репозитория - это не только рабочая директория

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

Поэтому фраза "файла больше нет в репозитории" требует уточнения. В рабочей версии его действительно может не быть, но в истории он останется до тех пор, пока старые объекты не будут удалены окончательно. При этом рассчитывать на автоматическую очистку нельзя: сроки работы сборщика мусора зависят от конкретной системы и настроек хостинга.

Почему переписывание истории не решает проблему полностью

Для удаления секрета из всех коммитов применяют `git filter-repo`, BFG Repo-Cleaner или аналогичные инструменты. Они создают новую историю, в которой нежелательный файл или фрагмент текста отсутствует.

Однако после такой операции возникают важные ограничения:

- у коллег, ранее клонировавших проект, остаётся старая копия со всеми объектами;
- форки могут продолжать содержать исходные коммиты;
- хостинг способен временно хранить старые объекты;
- ссылки на прежние коммиты иногда остаются доступными напрямую;
- локальные клоны, архивы и резервные копии не изменяются автоматически.

Таким образом, очистка истории снижает вероятность дальнейшего обнаружения секрета, но не возвращает ему статус безопасного. Если ключ хотя бы ненадолго оказался в коммите, его нужно считать раскрытым.

Правильный порядок действий при утечке

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

Затем нужно выяснить последствия инцидента:

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

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

Как не допускать попадания секретов в Git

Самый простой уровень защиты - корректный `.gitignore`. В него заранее добавляют файлы и каталоги, которые не должны попадать под контроль версий:

```gitignore
.env
*.pem
*.key
credentials.json
config.local.*
```

Но `.gitignore` не защищает файл, если тот уже был добавлен в индекс. После первого коммита правило лишь не позволит отслеживать будущие изменения, но прежний секрет останется в истории.

Полезно установить локальный `pre-commit`-хук. Инструменты вроде Gitleaks, Detect Secrets и TruffleHog анализируют изменения перед созданием коммита и распознают типовые токены, приватные ключи и пароли. Такие проверки не гарантируют абсолютную защиту, однако позволяют остановить наиболее распространённые ошибки.

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

Где должны храниться секреты

Секреты лучше не держать рядом с исходным кодом даже в локальных конфигурационных файлах. Для этого используют менеджеры секретов, защищённые переменные CI/CD и переменные окружения, передаваемые приложению во время запуска.

Важна и минимизация прав. Ключ для чтения метрик не должен иметь возможность удалять ресурсы, а токен сборочного сервера не обязан обладать доступом к производственной базе данных. Чем уже разрешения, тем меньше потенциальный ущерб при утечке.

Новые ключи желательно выпускать с ограниченным сроком действия и регулярно ротировать их. Автоматическая ротация уменьшает период, в течение которого случайно раскрытый токен остаётся пригодным для использования.

Что делать с уже опубликованным секретом

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

После переписывания рекомендуется уведомить всех разработчиков о необходимости удалить старые клоны и заново получить репозиторий. Простое выполнение `git pull` может привести к конфликтам и сохранить старые объекты локально.

Также нужно проверить теги, релизы, pull request, автоматические сборки и резервные копии. Секрет мог попасть не только в основную ветку, но и в опубликованный архив или артефакт CI.

Главное правило

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

1. отозвать и заменить ключ;
2. проверить журналы и возможное использование;
3. удалить секрет из истории;
4. уведомить всех владельцев копий;
5. усилить автоматические проверки и управление секретами.

Если эти действия выполняются в таком порядке, инцидент не превращается в длительную уязвимость, а случайный коммит не становится постоянным источником риска.

Прокрутить вверх