Сценарий неприятно простой. Я прошу ИИ собрать приложение, подключаю базу, почту, облако и API, вставляю рабочие ключи, вижу, что проект наконец запускается, затем отправляю всё на GitHub. Через минуту замечаю там .env, удаляю файл и считаю проблему решённой.
На самом деле секрет мог уже остаться в истории Git. А если ключ попал в клиентский JavaScript, удаления .env вообще недостаточно, браузер продолжит отдавать значение каждому посетителю. Вайбкодинг не создал проблему утечек, разработчики публиковали пароли задолго до появления Claude Code и Cursor. ИИ изменил скорость. За один вечер теперь легко собрать проект с десятком интеграций, почти не разбираясь, какие файлы и переменные нельзя выпускать наружу.
Масштаб старой проблемы хорошо виден по свежей статистике. По данным GitGuardian, которые подробно разбирал SecurityLab, за 2025 год в публичных коммитах GitHub обнаружили 28,65 млн новых жёстко прописанных секретов, на 34% больше предыдущего года. В той же выборке коммиты с распознаваемым участием Claude Code содержали секреты в 3,2% случаев против базового уровня 1,5% по публичным коммитам.
Последнюю цифру я бы не превращал в заголовок «Claude вдвое чаще сливает ключи». Исследование показывает корреляцию в конкретной выборке, а не доказывает, что причиной утечки стала модель. Коммит мог написать человек, агент мог изменить только часть файлов, а участие ИИ вообще можно обнаружить далеко не во всех проектах. Более аккуратный вывод другой. Ускоренная разработка создаёт больше конфигов, сервисных аккаунтов и временных решений, а значит увеличивает число возможностей совершить старую ошибку.
Сначала проверим свой репозиторий
На базовый аудит я бы выделил десять минут. Первые две уходят на проверку файлов, которые Git уже отслеживает или собирается добавить.
git status --short
git ls-files
git diff --cached
В списке меня настораживают .env, .env.local, .env.production, *.pem, *.key, файлы с названиями credentials и secrets, резервные копии конфигураций, строки подключения к базам и конфиги MCP с токенами.
Затем стоит проверить, действительно ли Git игнорирует локальные файлы окружения.
git check-ignore -v .env .env.local .env.production
Если команда ничего не выводит для существующего файла, правило .gitignore его не покрывает. Но есть ещё одна ловушка. Добавление .env в .gitignore не спасает файл, который Git уже отслеживает. Для такого файла понадобится убрать его из индекса.
git rm --cached .env
Команда прекращает отслеживать текущую копию, но не стирает старые коммиты. Если внутри был настоящий пароль или API-ключ, считать секрет скомпрометированным нужно ещё до любых манипуляций с историей.
Следующим шагом я запускаю специализированный сканер. Gitleaks умеет отдельно проверить текущее дерево и историю Git.
gitleaks dir --redact -v .
gitleaks git --redact -v .
Второй запуск особенно полезен. Gitleaks анализирует патчи из git log -p и способен обнаружить секрет, который попал в репозиторий год назад, а через пять минут был удалён обычным коммитом.
Для второй независимой проверки можно запустить TruffleHog. Для собственного публичного репозитория команда выглядит так.
trufflehog git https://github.com/USER/REPOSITORY --results=verified,unknown
TruffleHog отличается тем, что для многих типов учётных данных умеет проверять действительность найденного значения. Проверка может обращаться к API соответствующего сервиса, поэтому я использую такой режим только для собственных ключей и репозиториев, которые имею право проверять.
Материал предназначен для легального защитного аудита. При работе с чужими системами нужно соблюдать законодательство своей страны, включая российское. Найденный ключ нельзя использовать для несанкционированного доступа, слежки, взлома или обхода ограничений сервиса.
Нулевой результат двух сканеров не означает, что секретов гарантированно нет. Детекторы особенно хорошо узнают структурированные токены AWS, GitHub, Stripe, Anthropic и других сервисов, но произвольный пароль вроде SummerOffice2026! или внутренний токен нестандартного формата обнаружить сложнее. Свежая академическая работа по детектированию секретов также показывает, что даже современные модели и специализированные методы дают ложные срабатывания и пропуски. Автоматический поиск нужно считать фильтром, а не доказательством безопасности.
Почему ИИ-разработка увеличивает поверхность утечки
Проблема обычно начинается не с того, что модель внезапно решает опубликовать пароль. Гораздо чаще срабатывает цепочка мелких решений.
- ИИ создаёт конфигурацию. В проекте появляются
.env,config.json, настройки облака, базы, MCP или стороннего API. - Пользователь хочет увидеть работающий результат. Вместо настройки хранилища секретов рабочий ключ вставляется на место
YOUR_API_KEY. - Проект быстро обрастает интеграциями. Появляются OpenAI или Anthropic, Supabase, Stripe, PostgreSQL, SMTP, аналитика, поиск и облачное хранилище.
- Агент меняет сразу много файлов. Человек уже не просматривает каждый diff так внимательно, как при ручном написании небольшого изменения.
- Всё отправляется одним коммитом. Команда
git add .захватывает файл, который создавался как временный.
GitHub в собственных рекомендациях прямо советует избегать универсальных git add . и git commit -a при работе с чувствительными данными. Я предпочитаю добавлять файлы явно, а перед коммитом смотреть подготовленный diff.
git add src/app.js
git add package.json
git add .gitignore
git diff --cached
git commit -m "Add application"
Именно здесь проявляется отличие вайбкодинга от обычного автодополнения. Человек может получить работающую авторизацию, базу и деплой, не успев разобраться, где приложение хранит токены и какие части выполняются на сервере, а какие попадут пользователю в браузер.
Есть ещё один неприятный класс ошибок, который обычная проверка .gitignore не ловит. В Vite переменные с префиксом VITE_ специально попадают в клиентский код. Next.js аналогично встраивает в браузерный JavaScript значения NEXT_PUBLIC_*. Такой механизм предназначен для публичных настроек вроде идентификатора аналитики, а не для секретных API-ключей.
VITE_STRIPE_SECRET_KEY=...
NEXT_PUBLIC_OPENAI_API_KEY=...
Обе строки выглядят как переменные окружения, но значение может оказаться внутри собранного JavaScript. Репозиторий при этом способен оставаться идеально чистым. Поэтому вопрос «лежит ли ключ в Git» недостаточен. Нужно ещё спрашивать «может ли браузер получить ключ после сборки».
Похожая проблема появилась вокруг MCP. GitGuardian обнаружил в публичных MCP-конфигурациях 24 008 уникальных секретов, 2117 из которых удалось подтвердить как действующие. Среди находок были ключи Google API, строки подключения PostgreSQL, а также учётные данные Firecrawl, Perplexity и Brave Search. MCP здесь не виноват сам по себе. Формат просто свёл в одном месте множество интеграций, которым нужны реальные учётные данные.
Ещё показательнее исследование облачных сред разработки. Исследователь просканировал более 22 млн публичных проектов CodeSandbox, StackBlitz, CodePen и JSFiddle и получил 8792 уникальных подтверждённых секрета. Среди находок оказался токен сотрудника GitHub с правами записи в закрытый репозиторий, связанный с исходным кодом GitHub.com. Случай хорошо показывает, почему проверять только GitHub мало. Прототип, песочница и браузерная IDE тоже становятся частью поверхности утечки.
Почему удалённый .env продолжает жить
Самая распространённая реакция после обнаружения секрета выглядит разумно. Удалить файл, сделать новый коммит и закрыть вкладку GitHub.
Git хранит историю изменений. Старый коммит продолжает содержать значение, а его копии могут сохраниться в клонах, форках, pull request и кэшированных представлениях. Поэтому первым действием после обнаружения настоящего секрета должна стать не косметическая уборка репозитория.
Сначала отозвать или ротировать ключ. Потом чистить Git.
После публичной публикации невозможно надёжно доказать, что значение никто не получил. Поэтому я не пытаюсь оценивать, сколько минут файл провисел в открытом доступе. Если секрет был публичным, старому значению больше не доверяю.
После ротации можно привести проект в порядок.
.env
.env.*
!.env.example
*.pem
*.key
credentials*.json
Шаблон .env.example я, наоборот, оставляю в репозитории. Внутри находятся только имена переменных и пустые значения.
DATABASE_URL=
ANTHROPIC_API_KEY=
STRIPE_SECRET_KEY=
SMTP_PASSWORD=
Если чувствительный файл нужно убрать из всей истории, GitHub рекомендует современный git-filter-repo. Для удаления .env команда может выглядеть так.
git-filter-repo --sensitive-data-removal --invert-paths --path .env
Запускать переписывание истории автоматически после каждой утечки я бы не стал. Меняются хэши коммитов, могут исчезнуть подписи, ломаются ссылки на старые изменения, возникают проблемы с pull request, а коллегам приходится синхронизировать локальные клоны. Форк или чужой клон вообще нельзя очистить удалённо. Если ключ уже отозван, польза полной чистки зависит от типа данных и модели угроз.
Для пароля пользователя, персональных данных или закрытого документа удаление из истории может оставаться обязательной частью инцидента. Для безвозвратно отозванного API-ключа основной риск уже уничтожен ротацией.
Как я бы теперь запускал любой проект с ИИ
Я бы не пытался решить проблему одним идеальным промтом. Фраза «никогда не публикуй секреты» полезна, но модель не должна становиться последней линией защиты.
Минимальный конвейер выглядит так. До первого коммита создаю .gitignore и .env.example. Настоящие значения храню вне репозитория. Перед коммитом запускаю git diff --cached. Историю проверяю Gitleaks. Для чувствительных проектов добавляю второй сканер. На GitHub оставляю включёнными secret scanning и push protection для поддерживаемых типов секретов.
GitHub бесплатно сканирует публичные репозитории на поддерживаемые форматы секретов. Пользовательская push protection по умолчанию пытается остановить поддерживаемый секрет ещё во время отправки в публичный репозиторий. Но полагаться только на серверную защиту нельзя. Нестандартный внутренний пароль может не соответствовать известному шаблону, а ключ способен утечь через браузерный bundle, лог CI, облачную песочницу или конфигурацию, которая вообще не дошла до GitHub.
Есть и полезное статистическое предостережение. Разные отчёты называют разные абсолютные числа утечек. Например, GitHub сообщал более чем о 39 млн утечек секретов за 2024 год, тогда как GitGuardian для того же года насчитал около 23,8 млн новых жёстко прописанных секретов в публичных репозиториях. Противоречия здесь нет. Компании используют разные детекторы, поверхности, правила дедупликации и определения находки. Складывать такие цифры или строить прямое сравнение нельзя.
Главный вывод для меня довольно приземлённый. Вайбкодинг опасен не потому, что модель обязательно напишет API_KEY="secret". Опаснее скорость, с которой человек получает сложную систему, не успев построить вокруг неё нормальную гигиену секретов.
Поэтому после вечера с Cursor, Claude Code, Copilot или любым другим агентом я проверял бы не только то, запускается ли проект. Я бы смотрел, какие файлы попали в Git, что осталось в истории, какие переменные отправляются в браузер и какие ключи вообще существуют у проекта. А если настоящий секрет хотя бы раз оказался публичным, первым делом менял бы сам секрет. Красивую историю Git можно починить позже. Украденные права доступа ждать не будут.