Безопасность
Управление секретами: почему ключ в git — это уже взлом
Джун пушит быстрый фикс в публичный репозиторий. В диффе спрятан AWS-ключ доступа, который был «просто для теста». За минуту его уже забрал автоматический сканер, следящий за публичным потоком событий GitHub; за одиннадцать минут кто-то поднимает флот GPU-инстансов для майнинга на счёте компании. Разработчик паникует и форс-пушит коммит, удаляющий строку. Ключ всё ещё работает — он по-прежнему живёт в истории git, и ротация, а не удаление, — единственное, что останавливает взлом.
Захардкоженные учётные данные — это утёкшие учётные данные
Инстинкт — относиться к ключу в коде как к неряшливости, которую надо прибрать перед демо. Это не неряшливость, это экспозиция. В тот момент, когда учётные данные попадают в репозиторий, они есть у всех, у кого есть доступ на чтение, их несёт каждый клон, их может напечатать любой CI-лог, а если репозиторий когда-нибудь сделают публичным — они транслируются в интернет. GitHub зафиксировал более 39 миллионов утёкших секретов в 2024 году — рост на 67% за год, а GitGuardian насчитал 28,65 миллиона новых захардкоженных секретов, запушенных в публичные репозитории только за 2025 год. Это не редкая оплошность — это самый частый способ, которым реальные системы взламывают, поэтому OWASP относит управление секретами к первоклассным заботам.
Цифры по эксплуатации — та часть, что пугает людей и заставляет меняться. Исследователи, подбрасывавшие honeypot-ключи AWS, видели, как автоматические боты находят и используют их примерно за одну минуту, а security-фирмы измеряли, как сканеры забирают открытые ключи за одиннадцать минут и сразу запускают GPU-инстансы. Льготного периода нет. К моменту, когда ты замечаешь плохой коммит, атакующий уже использовал ключ.
История git — append-only, поэтому удаление — это ложь
Ловушка, в которую попадает большинство инженеров: они делают git rm файла или удаляют строку, пушат и верят, что секрета больше нет. Его есть. Модель данных git — append-only: каждый коммит сохраняется, и секрет навсегда сидит в истории каждого клона и форка. Любой, кто запустит git log -p или любой сканер, проходящий полную историю (а они все так делают), читает его так же легко, как текущий код.
Поэтому очистка состоит из двух частей, и важна по-настоящему только одна. Историю можно переписать через git filter-repo, чтобы вычистить блоб из каждого коммита, — но это не помогает тем, кто уже сделал клон, и ты никогда не уверен, что копия не утекла. Шаг, который завершает взлом, — это ротация: отзови утёкшие учётные данные у источника, чтобы значение в руках атакующего стало бесполезным. Считай скомпрометированным каждый секрет, который хоть раз касался репозитория, и ротируй его. Переписывание истории — это гигиена; ротация — это фикс.
Почему это работает
Хардкод — не единственный путь утечки. Тот же секрет может быть запечён в слой Docker-образа (любой, кто стянет образ, запускает docker history и читает его), напечатан в логи приложения или в подробную страницу ошибки, или собран в клиентский JavaScript-бандл и отгружен в каждый браузер. «Бэкендовый» секрет в Next.js-компоненте, который не server-only, — в одном неосторожном импорте от публичного бандла. Каждый из этих случаев — один и тот же провал: секрет покинул границу доверия.
Лестница зрелости: хардкод → dotenv → управляемый
Есть чёткий прогресс в том, как команды обращаются с секретами, и большинство аварий случаются потому, что команда застряла на нижней ступени.
Визуал ниже — это лестница, на которую сеньор поднимается осознанно:
| Ступень | Что даёт | Что всё ещё болит |
|---|---|---|
| Хардкод в коде | Ничего — только удобство | Навсегда в истории git; утекает на каждом клоне, в логе, при публичном пуше |
.env + .gitignore | Вне git, вне образа при правильной настройке | Plaintext на диске и в CI; живёт в env процесса; нет access control и аудита; один плохой .gitignore от катастрофы |
| Менеджер секретов (Vault, AWS Secrets Manager, SOPS) | Шифрование at rest, access control, аудит-лог кто что читал | Операционная цена; всё ещё долгоживущий, если хранишь только статические значения |
| Динамические, короткоживущие секреты | Учётка на запрос с TTL, авто-отзыв, аудит по каждому сервису | Больше всего настройки; нужна интеграция, которую понимает приложение |
Большой скачок — от .env к менеджеру секретов. Файл .env действительно лучше хардкода — он держит значение вне коммита — но это всё равно plaintext, лежащий на ноутбуке каждого разработчика и на CI-раннере, читаемый любым, у кого есть доступ к машине, и невидимый для любого аудита. Менеджер вроде HashiCorp Vault, AWS Secrets Manager или SOPS добавляет три вещи, которые .env дать не может: шифрование at rest, access control (этот сервис может читать этот секрет и никакой другой) и аудит-след каждого чтения. Когда случается инцидент, именно аудит-лог говорит, чью учётку ротировать и чего она касалась.
Короткоживущий бьёт долгоживущий: TTL — это радиус взрыва
Верхняя ступень — то, к чему целятся сеньорские команды, и она меняет математику утечки. Статические долгоживущие учётные данные — самый частый вектор атаки именно потому, что они валидны, пока человек не вспомнит ротировать их, — что после утечки часто никогда. Динамический секрет генерируется по запросу со встроенным TTL и автоматически отзывается по истечении. Движок баз данных Vault, например, выпускает уникального пользователя БД на запрос с TTL в один час на проде; утёкшая учётка мертва за шестьдесят минут — заметил это кто-то или нет.
Из этого следуют ещё два свойства. Поскольку каждый потребитель получает уникальную учётку, аудит-лог может назвать точный экземпляр сервиса, который что-то сделал, — ты не пялишься на один общий username, используемый всем подряд. И динамические учётки делают least privilege применимым: каждый lease ограничен ровно теми правами, что нужны роли, поэтому украденный ключ read-only сервиса отчётов не может удалить продовую базу. Сеньорский трейдофф реален — динамические секреты дороже всего в настройке и требуют, чтобы приложение получало и продлевало lease, — но для всего, что защищает продовые данные, радиус взрыва в один час бьёт бесконечный.
Бэкенд-сервису нужны учётные данные БД на проде. У команды есть спринт на это. Выбери подход, который защитит сеньор.
Ты обнаружил, что API-ключ закоммитили в репозиторий месяц назад. Удаляешь строку и форс-пушишь. Секрет теперь в безопасности?
Почему динамическая короткоживущая учётка БД бьёт долгоживущую для прода?
Ты только что нашёл живой облачный ключ, закоммиченный в публичный репозиторий. Расставь реакцию от первого к последнему:
- 1 Ротируй учётку сейчас — отзови утёкший ключ у источника, чтобы он перестал работать
- 2 Проверь аудит/биллинг-логи провайдера на то, что ключ уже сделал
- 3 Перепиши историю git (git filter-repo), чтобы вычистить блоб из каждого коммита
- 4 Добавь pre-commit сканер секретов (gitleaks/trufflehog), чтобы это не повторилось
- 5 Перенеси секрет в менеджер с access control и короткоживущими lease
- 01Коллега закоммитил пароль БД, потом удалил строку и запушил. Объясни, почему это не решает проблему и какая реальная ремедиация.
- 02Проведи коллегу вверх по лестнице зрелости секретов и объясни, почему каждая ступень лучше, закончив тем, почему короткоживущие динамические секреты — цель.
Учётные данные в исходном коде — это не вопрос аккуратности, а утёкший секрет, и утёкшие ключи на публичных репозиториях используют примерно за минуту автоматические сканеры, которые тут же поднимают ресурсы на твоём счёте. Определяющая ловушка в том, что история git — append-only: удаление строки или даже форс-пуш не убирает секрет из прошлых коммитов или из чьего-то клона, поэтому единственное, что завершает экспозицию, — это ротация, отзыв значения у источника, подкреплённый проверкой аудит и биллинг-логов на то, что уже произошло. Дальше сеньор поднимается по лестнице зрелости: прочь от хардкода, мимо файла .env (вне git, но всё ещё plaintext, неконтролируемый и неаудируемый), в менеджер секретов, добавляющий шифрование at rest, access control и аудит-след, и наконец к динамическим короткоживущим секретам, чей TTL ограничивает радиус взрыва любой утечки, обеспечивая least privilege на каждого потребителя. Страховочная сетка под всем этим — pre-commit сканер секретов, чтобы следующая ошибка была поймана ещё до того, как покинет машину.