Видалити секрет з історії складно, а після публікації - вже запізно: ключ треба вважати скомпрометованим. Тому головне - не дати йому потрапити в коміт. Захист будують у кілька шарів.
1. Структура проєкту:
- секрети лише в
.env, який у.gitignore; у репозиторії -.env.exampleз порожніми значеннями; - у коді - лише
config('services.stripe.secret'), ніяких ключів у тестах, сидерах і фікстурах; - для CI - секрети платформи (
${{ secrets.NAME }}), а не файли в репозиторії.
2. Локальна перевірка до коміту - сканер секретів у pre-commit:
#!/bin/sh
gitleaks git --pre-commit --staged --redact --verbose
gitleaks шукає за сотнями шаблонів (ключі AWS, Stripe, GitHub-токени, приватні ключі) і за ентропією рядків. Хибні спрацювання - через .gitleaksignore чи конфігурацію.
3. Захист на сервері - push protection на GitHub:
- для користувачів увімкнено за замовчуванням: GitHub блокує ваш push із розпізнаним секретом у публічні репозиторії;
- для репозиторіїв (потрібна GitHub Secret Protection) - вмикається адміністратором і блокує push із секретами у конкретний репозиторій, зокрема з командного рядка, через вебінтерфейс і API.
При блокуванні push GitHub показує, у якому файлі й коміті знайдено секрет. Обхід можливий із зазначенням причини (хибне спрацювання, тестовий ключ), і для захисту на рівні репозиторію він створює сповіщення й запис у журналі аудиту.
4. Сканування в CI - той самий gitleaks на кожен PR: ловить те, що пройшло повз локальний хук через --no-verify чи відсутність хука.
5. Мінімізація шкоди:
- ключі з мінімальними правами й обмеженим терміном дії;
- окремі ключі для розробки, staging і продакшену;
- план дій на випадок витоку: спочатку відкликати ключ, а вже потім чистити історію.
Чому шари: кожен окремо можна обійти - хук не встановлено, push protection не знає формату вашого внутрішнього токена. Разом вони ловлять переважну більшість випадків.