Почему люди хранят пароли и ключи прямо в коде и как навести порядок

изображение: recraft
Секреты в репозитории – не проблема безответственных джуниоров, а системный результат того, как устроены инструменты, документация и рабочие процессы. Разберем, почему это происходит, к чему приводит и что здесь реально помогает.
Почему так получается
- «Впишу на пять минут»
Это классика: чтобы проверить интеграцию, легче вставить ключ прямо в код, чем поднимать инфраструктуру для секретов. Разработчик честно собирается его потом убрать, но коммит уходит в ветку, эта ветка мержится в основную, и «потом» не наступает никогда.
- Правильный путь vs удобный
Чтобы локально запустить проект как положено, придется получить доступ к хранилищу секретов, настроить аутентификацию, разобраться с CLI и попросить у DevOps подходящую роль. Но можно поступить иначе: просто вписать строку в конфиг и запуститься через тридцать секунд. Пока второй вариант на порядок удобнее первого, люди будут выбирать именно его, и дело в экономии усилий, а вовсе не в дисциплине.
- Документация учит плохому
В README половины библиотек написано:
client = Client(api_key=»sk-…»)
Человек это копирует, подставляет свой ключ и идет дальше. Этот паттерн документация ежедневно выдает всем, однако в примере никто не написал os.environ[«API_KEY»], потому что так этот пример станет менее наглядным.
- Непонимание того, как устроен git
Git rm, новый коммит, «я почистил» – а секрет при этом остался в истории. Он доступен через git log -p, «уехал» в форки и есть в каждом клоне у любого разработчика. Иллюзия чистоты опаснее самого секрета, ведь что из-за нее никто не станет отзывать ключ.
Чем это опасно
Приватный репозиторий – не защита. Причин много: компрометация аккаунта разработчика, недовольный инсайдер, случайное переключение видимости или зеркало репозитория в CI-системе с более слабым контролем доступа. Приватность – это просто одно кольцо защиты, а не гарантия безопасности.
Секрет в истории живет дольше компании. Он переживет увольнение сотрудника, который его закоммитил, продажу бизнеса с передачей репозиториев новому владельцу и миграцию на другой хостинг. И все это по одной причине: секреты мигрируют вместе с историей.
Docker-образы «протекают». Docker history показывает команды сборки, а распаковка слоев – файлы. Тот секрет, что вы аккуратно удалили в следующем слое, никуда не делся из предыдущего. Слои иммутабельны, и удаление в Docker – просто пометка «этого файла больше нет», а не стирание данных на самом деле.
Что делать, если секрет уже «утек»
Вот самый грамотный порядок действий, и их очередность имеет большое значение:
- Отозвать секрет у провайдера, причем немедленно – до чистки истории, разговоров с командой и расследования. Скомпрометированный ключ должен перестать работать.
- Выпустить новый через нормальный процесс и с хранением в надежном месте.
Только потом чистить историю через git filter-repo. Важно понимать, что чистка истории опциональна: это force push, переписывание всех хешей, боль для каждого, у кого есть клон или открытый PR. Более того, она ничего не гарантирует: секрет мог «осесть» в форке, кеше хостинга, логах CI, чьем-то локальном репозитории или поисковом индексе.
Ротация – единственное надежное действие, все остальное – просто гигиена.
Как предотвратить проблему
- Найти то, что уже есть
Прежде чем строить защиту от будущих утечек, стоит понять масштаб текущих. Gitleaks сканирует историю репозитория по набору правил и находит то, что уже лежит в коммитах. Первый запуск на легаси-проекте обычно неприятен.
- Не полагаться на дисциплину разработчиков
Pre-commit хуки – хорошая идея, но у нее есть фатальный недостаток: они «живут» на машине у конкретного человека и не появятся у нового сотрудника автоматически. Их можно по каким-то причинам не установить, а еще они обходятся через —no-verify.
Работающий вариант – проверка на стороне сервера. Серверные `pre-receive` хуки в GitFlic выполняются при получении пуша до того, как коммиты попадут в репозиторий. Разработчику не надо ничего настраивать локально, обойти проверку нельзя, а покрытие получается стопроцентным для всех репозиториев, где хук включен.
Разница принципиальная: pre-commit – это напоминание, а pre-receive – уже правило. Секрет, который не прошел серверную проверку, физически не попадет в историю, а значит, не будет ни filter-repo, ни force push, ни вопроса «а точно ли мы все почистили».
- Хранить секреты там, где им и место
Запретить секреты в коде можно, только когда их есть куда положить. Эту задачу решает Vault или другое централизованное хранилище: секреты находятся в одном месте, есть версионирование, аудит доступа и возможность отозвать ключ у нужного сервиса, не трогая остальные.
У GitFlic интеграция с Vault сделана, и это закрывает главную проблему, о которой шла речь в начале статьи – экономию усилий. Если получить секрет из хранилища в CI-пайплайне так же просто, как вписать в конфиг, для вписывания «на пять минут» пропадает мотивация.
Итог
Секреты попадают в код не потому, что люди не знают, что так нельзя. Просто правильный путь требует усилий, а неправильный – нет, документация нормализует плохой паттерн, а модель хранения истории в git интуитивно неочевидна. Значит, вместо чтения лекций лучше:
- просканировать существующие репозитории (gitleaks) и отротировать все найденное;
- поставить серверные `pre-receive` хуки – в GitFlic это делается на уровне сервера без участия каждого разработчика;
- дать нормальную альтернативу – интеграцию с Vault, чтобы правильный путь стал самым коротким.
И помните главное: если секрет «утек» – сначала отзыв и только потом все остальное. История переписывается, а ключ – нет.
Эдуард Тихомиров, технический директор GitFlic (входит в «Группу Астра»)




