Случалось ли вам добавлять в Git что-нибудь по ошибке? Если это было что-то вроде функции для дебага, некорректного комментария или случайно скопированного файла, то можно это исправить следующим коммитом. Но сработает ли такое исправление, если вы (или ваш коллега) случайно добавили в репозиторий какой-нибудь секрет? Ключ от стороннего сервиса, логин/пароль от интеграции или даже дамп базы данных или всю папку /private/uploads? На первый взгляд - да, сработает, можно сделать новый коммит и удалить то, что было добавлено по ошибке. Но дело в том, что эти данные все равно останутся в репозитории. По истории коммитов можно вернуться назад во времени (до исправления) и увидеть данные, которых уже нет в последней версии репозитория.
Обычно в репозиторий (даже в приватный) принято не добавлять никакие чувствительные данные. Обычно в контракте с заказчиком это будет прописано отдельной строкой. Причины очевидны: процесс выдачи паролей или ключей интеграций должен иметь возможность отследить, кому они были выданы, при необходимости отозвать или изменить их. Даже в приватном репозитории доступ к этим секретам получит любой, кто может читать репозиторий, или его клон, форк. А также ваши CI/CD и прочие интеграции с репозиторием. И потом доступ на чтение репозитория не должен автоматически означать доступ к реальной базе данных, интеграциям, или личным данным кого-либо. Надеюсь, я убедил, что никаких “секретов”, дампов базы и чувствительных данных в репозитории быть не должно, даже если их добавили не вы, это случилось давно или вы даже не в курсе. Я имею в виду в репозитории вообще, а не только в его последнем состоянии.
Если в репозиторий попал действующий API-ключ, пароль или токен, хорошей практикой считается в первую очередь отозвать его или заменить новым.
Давайте проверим любой ваш репозиторий на предмет присутствия в нем чувствительных данных! Например, вы присоединились к проекту, который активно разрабатывается уже пару лет. Кто вообще знает, что туда добавляли? А мы сейчас посмотрим и, если нужно, исправим.
Можно, конечно, составить промпт для ИИ и поручить ей “очисти историю коммитов от паролей и прочих чувствительных данных, но нужное не удаляй и переспрашивай если надо” и, вероятно, она справится. Можно даже на основе того, что написано ниже создать агента и научить его пользоваться этими утилитами. Можно даже добавить подобную проверку в процесс CI/CD. Но можно и вручную. План такой:
1. Проверить, что в репозитории лишнее
Для изучения репозитория Git есть много разных утилит. Могу порекомендовать https://github.com/betterleaks/betterleaks. Раньше я использовал https://github.com/gitleaks/gitleaks но все быстро менятся, утилиты устаревают, появляются новые… Надо не отставать от жизни.
В основе работы большинства подобных сканеров лежат правила обнаружения, по ключевым словам и регулярным выражениям. Например, правило может искать конструкции, похожие на AWS ключ, GitHub токен и тому подобное. Как один из этапов, утилита Betterleaks использует быстрый фильтр по ключевым словам чтобы найти что то вроде “api_key:123abc”, а затем использует BPE-токенизацию (Byte Pair Encoding) - тот же общий подход к токенизации, который используется в современных языковых моделях. Обычный человеческий текст хорошо разбивается на знакомые длинные токены, а случайная строка вроде API-ключа - на множество маленьких фрагментов. Это дополнительный механизм, который помогает снижать количество ложных срабатываний. Кроме того, умеет автоматически декодировать, например, Base64, hex и URL encoding, а затем проверять результат, а также проверять секреты внутри архивов.
Установка
(Далее взято из https://medium.com/devsecops-ai/i-switched-from-gitleaks-to-betterleaks-heres-what-changed-de286168bf9c)
# Homebrew — easiest on macOS or Linux brew install betterleaks
# Docker — useful for ephemeral CI environment docker pull ghcr.io/betterleaks/betterleaks:latest
# Build from source git clone https://github.com/betterleaks/betterleaks cd betterleaks make betterleaks
Проверка, что установилось и работает
betterleaks version
Проверка репозитория Git
Перейдите в консоли в каталог проекта и запустите
betterleaks git . --log-opts=“–all” -v
Betterleaks просмотрит каждый коммит, каждый патч и каждое изменение. Если ключи или пароли появляются где-либо в истории репозитория, они появятся в отчете как подозрительные - даже если файл был удален десять коммитов назад.
В результате увидите что-то вроде:
[ { “RuleID”: “generic-api-key”, “Description”: “Detected a Generic API Key, potentially exposing access to various services and sensitive operations.”, “StartLine”: 4, “EndLine”: 4, “StartColumn”: 6, “EndColumn”: 50, “Match”: “key: cTAAAAARqyHh71UM**MXjiZKhI", “Secret”: "6LAAV****UMIEGNQKhI", “Attributes”: { “confidence”: “low”, “git.author_email”: ".@.”, “git.author_name”: “***** ", “git.date”: "20-0-0*T07::43Z”, “git.message”: “-9 Webforms switched to v3 reCaptcha.", “git.platform”: “bitbucket”, “git.remote_url”: "https://bitbucket.org/", “git.sha”: "************", “path”: “config/sync/recaptcha_v3.settings.yml”, “resource”: “git.patch_content”, “url”: "https://bitbucket.org///config/sync/recaptcha\_v3.settings.yml#lines-4" }, “Tags”: [], “Fingerprint”: "07016e86988ce525f70d2:config/sync/recaptcha_v3.settings.yml:generic-api-key:4", “File”: “config/sync/recaptcha_v3.settings.yml”, “SymlinkFile”: “”, “Commit”: "********”, “Entropy”: 4.6153116, “Author”: “***** ", “Email”: " @***’, “Date”: "20-0-T07::43Z", “Message”: " Webforms switched to v3 reCaptcha.” }, ]
Видно, кто, куда и когда закоммитил лишнее.
Но можно настроить вывод результатов в файл.
betterleaks git . --log-opts=“–all” --report-format json --report-path …/betterleaks-report.json -v
Параметр --log-opts=“–all” говорит Betterleaks проверять все ветки, а не только текущую. Смотрите --help или описание самой утилиты.
Но не стоит считать что все, что нашлось, нуждается в удалении: там могут быть ложные срабатывания. Нужно вручную проверить найденные значения и решить, действительно ли они являются секретами.
Можем собрать статистику в комментариях, у кого что нашлось :)
2. Удалить лишнее из репозитория
Результат сканирования будем использовать другой утилитой, которая умеет удалять нужные части строк из всех коммитов репозитория. Это https://github.com/newren/git-filter-repo Для нее, на основе найденых результатов, надо составить файл замены. Можно сделать вручную, а можно конвертировать полученный выше файл (команды составлены ИИ):
Собрать секреты из отчета в файл замен для filter-repo
jq -r ‘.[].Secret’ …/betterleaks-report.json | sort -u > …/secrets-raw.txt
Затем надо превратить в формат filter-repo: значение==>замена: Ниже - пример конвертации JSON-отчёта в формат git-filter-repo; конкретную команду стоит проверить на версии вашей Betterleaks и структуре полученного отчета.
awk ‘{print $0"==>REMOVED"}’ …/secrets-raw.txt > …/secrets-to-replace.txt
Теперь найдем “опасные” файлы просто на основе их расширений (команда составлена ИИ):
git log --all --diff-filter=A --name-only --pretty=format: – ‘.sql’ '.sql.gz’ ‘.dump’ '.bak’ ‘*.env’ | sort -u > …/paths-to-remove.txt
Адаптируйте команду изменив список расширений файлов которые она ищет.
Сейчас у нас появились два файла: со списком файлов на удаление и со списком замен.
Следующим шагом - начнем очистку! 🙂 (начали уже смотреть 3-й сезон Silo?)
Установка утилиты https://github.com/newren/git-filter-repo :
brew install git-filter-repo
Затем надо вручную перенести имеющиеся в репозитории секреты в конфигурацию вашего приложения, которая не является частью репозитория. Это зависит от проекта и технологии которую вы используете, используйте имеющуюся возможность переназначать значения конфигурации через settings.local.php, .env.local и тому подобное. То есть надо чтобы ваш прод работал со значениями из другого источника. Git-filter-repo чистит историю, и так же затрагивает последний коммит. Если вы проигнорируете предыдущий шаг, это может сломать работу приложения при деплое и вы можете даже потерять свои ключи, если они были только в репозитории, после того, как они удалятся.
Дальнейшие действия деструктивны и необратимы, ведут к удалению данных, будьте осторожны, согласуйте это с командой, менеджером проекта и заказчиком. На время работы никто не должен делать коммиты.
Обязательно делаем резервную копию
git clone --mirror repo-backup.git
Держите эту копию отдельно пока не убедитесь, что все прошло хорошо. И еще пару недель после этого 🙂 Не используйте эту копию как рабочую и не делайте в неё push очищенной истории.
Закройте все имеющиеся Pull/Merge requests - история и хеши коммитов будут переписаны. PR придется открывать заново после.
Далее сделаем еще одну копию в которой и будем делать очистку
git clone repo-clean cd repo-clean
git-filter-repo требует “чистый” клон (утилита убирает remote origin после работы - защита от случайного push)
Далее сама очистка
Один проход git-filter-repo на оба действия файлы + замена текста секретов
git filter-repo –invert-paths --paths-from-file …/paths-to-remove.txt –replace-text …/secrets-to-replace.txt
Проверка результата
git log --all --oneline | wc -l
история не развалилась
Повторный скан — findings должны исчезнуть
betterleaks git . --log-opts=“–all” -v
Локальная зачистка старых объектов и передача изменений на сервер
git-filter-repo выполняет очистку и repack самостоятельно. При необходимости можно дополнительно выполнить более агрессивную очистку локального репозитория:
git reflog expire --expire=now --all git gc --prune=now --aggressive
Force-push переписанной истории
Добавляем remote репозиторий обратно
git remote add origin
Внимание: переписываем историю на сервере
git push --force --mirror origin
И последнее: после переписывания истории, самый безопасный вариант для ваших коллег - удалить старую рабочую копию и заново клонировать репозиторий именно не pull/rebase, а клонировать репозиторий заново т.к. хэши коммитов стали другие. И CI/CD деплой-скрипты, которые работают с Git, тоже должны получить свежий клон, а не инкрементальный fetch.
Для полноты картины надо сказать, даже после force-push нельзя автоматически считать, что старые данные исчезли абсолютно отовсюду. Они могли остаться в старых клонах, форках, резервных копиях, CI/CD-кэшах, артефактах, логах и т.п. Кроме того, конкретный Git-хостинг может некоторое время сохранять старые объекты или cached views. Поэтому кроме удаления из Git-истории утекшие ключи необходимо отозвать и заменить на новые, то есть сделать их ротацию.

