Почему секреты нельзя хранить в коде

Разбираем, во что обходится привычка держать пароли и токены прямо в коде и конфигах.

Секрет — это любой чувствительный фрагмент данных (пароль, токен, ключ API, приватный ключ TLS), компрометация которого даёт злоумышленнику доступ к системе или данным.

Почти каждый проект начинается одинаково: разработчику нужно подключиться к базе данных, и он пишет строку подключения прямо в исходник. Работает — значит хорошо. Через полгода в репозитории десятки паролей, ключей платёжного шлюза и токенов облака, и никто уже не помнит, какие из них реальные, а какие тестовые. Это корень проблемы.

Чем плох хардкод

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

import psycopg2

# ТАК ДЕЛАТЬ НЕЛЬЗЯ: пароль прямо в исходнике
conn = psycopg2.connect(
    host="db.internal",
    user="app",
    password="S3cr3t-Pr0d-Pass!",  # утечёт вместе с кодом
    dbname="orders",
)
print("connected")

Стоит этому файлу попасть в публичный или даже внутренний репозиторий — пароль скомпрометирован. Боты непрерывно сканируют GitHub на предмет ключей: типичное время от публикации ключа AWS до его эксплуатации измеряется минутами.

Почему .env в гите не спасает

Следующий шаг команды — вынести секреты в файл .env и добавить его в .gitignore. Это лучше, но решает лишь половину задачи.

DATABASE_URL=postgres://app:[email protected]/orders
STRIPE_KEY=sk_live_51H...
JWT_SECRET=k8s-super-secret-value

Проблемы остаются. Файл всё равно лежит на диске в открытом виде. Его копируют коллеги через мессенджер. Один неосторожный git add . — и секрет в истории навсегда. Нет ротации: пароль один и тот же годами. Нет аудита: невозможно узнать, кто и когда читал секрет. Нет разграничения: любой, у кого есть файл, видит ВСЕ секреты сразу.

Как работает под капотом утечка из git

Git хранит полную историю. Даже если вы удалили строку с паролем следующим коммитом, она остаётся в объекте предыдущего коммита и достаётся одной командой:

# любой, у кого есть клон, достанет старый секрет
git log -p -- .env
git show <старый-хеш-коммита>:.env

Поэтому правило простое: если секрет хоть раз попал в git — считайте его скомпрометированным и ротируйте, а не «удаляйте коммит».

Частые ошибки

  • «У нас приватный репозиторий, всё ок» — приватность не отменяет ни утечек через подрядчиков, ни логи CI, ни случайную публикацию.
  • Логирование строки подключения целиком — пароль оказывается в системе сбора логов, доступной полкоманде.
  • Один пароль на все окружения — компрометация dev тянет за собой prod.
  • «Удалю коммит и всё» — секрет уже в истории и, возможно, в форках; нужна ротация.

Итог

  • Секрет в коде = секрет, скомпрометированный при первом же копировании, форке или логе.
  • .env в .gitignore убирает случайный коммит, но не даёт ротации, аудита и разграничения доступа.
  • Попавший в git секрет нужно ротировать, а не пытаться «затереть» из истории.
Проверьте себя
1. Почему секрет, попавший в историю git, считают скомпрометированным?
AGit шифрует историю слабым алгоритмом
BОн остаётся в объектах прошлых коммитов и достаётся даже после удаления строки
CGitHub автоматически публикует все секреты
DGit не поддерживает приватные репозитории
2. Что НЕ решает вынос секретов в .env с добавлением в .gitignore?
AСлучайный коммит файла в репозиторий
BОтсутствие ротации, аудита и разграничения доступа
CЛокальный запуск приложения
DЧтение переменных окружения приложением
3. Что такое секрет в контексте безопасности?
AЛюбая переменная окружения
BКомментарий в коде
CЧувствительные данные, компрометация которых даёт доступ к системе
DИмя пользователя базы данных