Случай, с которым часто сталкиваются инди-разработчики без опыта в безопасности, с убранными всеми идентифицирующими деталями и превращённый в урок. Шагов для воспроизведения атаки здесь нет. Цель — защита, чтобы та же ошибка не случилась с вами.
- Класс
- Утечка API-ключа / мошеннические списания
- Серьёзность
- Критическая (злоупотребление опубликованной CVSS 10.0)
- Корневая причина
- Pre-auth RCE в веб-фреймворке, месяцами оставшаяся незакрытой
- Масштаб утечки
- Все секреты окружения
- Масштаб выполнения
- Осталось внутри непривилегированного контейнера (root хоста не пострадал)
- Постоянное решение
- ①обновиться до исправленной версии ②сменить все ключи ③машинный мониторинг CVE
«Остановили списания» ≠ «разобрались»
Остановить счёт — временная мера. Закрыть путь утечки — отдельная задача. Вы закончили только тогда, когда сделали и то, и другое.
Что произошло (хронология)
День 0 — выкатили и запустили
Приложение, собранное с помощью ИИ, было выпущено и работало в продакшене.Однажды — счёт взлетает
Счёт за облачный ИИ внезапно подскакивает. Массовые задания на модели, которой никогда бы не пользовались, делающие чужую работу.Расследование — злоупотребление со стороны
«Наше приложение сошло с ума» этого не объясняет. Кто-то посторонний запускал его с украденным ключом.Поиск — выясняем путь утечки
«Так откуда утёк ключ?» С этого вопроса и началось настоящее расследование.
Почему сразу не поймали
В том порядке, в котором совершались ошибки, — потому что именно ошибки и есть урок.
Слишком рано определились с причиной
→ Прежде чем решить «моя вина» или «их вина», сначала посмотрите на данные.
Чистый grep чуть не завершил поиск
Скрытие симптома показалось исправлением
Чистый grep — это ещё не повод успокоиться
Даже если ни в одном файле нет ключа, уязвимость может вытащить переменные окружения из работающего процесса. Утечки случаются в среде выполнения (RCE, HTTP-заголовки), а не только в файлах. См. Что такое RCE.
Настоящая причина: опубликованная CVSS 10.0, оставленная без исправления
Странная сигнатура в старых логах доступа быстро совпала с данными threat intelligence. У используемого диапазона версий веб-фреймворка была опубликованная pre-auth RCE (CVSS 10.0), которую уже эксплуатировали в реальных атаках, — и она месяцами оставалась незакрытой.
- Это была не пассивная ошибка — злоумышленник выполнял код на сервере и выкачивал окружение.
- Ущерб ограничило одно: выполнение осталось внутри непривилегированного контейнера (root хоста не захватили). Разбор взлома не нашёл устойчивого бэкдора, майнера или C2 — подтверждённым ущербом была кража секретов.
- Поскольку внутренняя БД была достижима, в ответных мерах исходили из того, что содержимое БД тоже утекло.
Урок: когда что-то ведёт себя странно, первым делом подозревайте известную CVE. Не считайте по умолчанию, что это ваш собственный баг.
Сузьте путь утечки: где хранился этот ключ?
Если grep ничего не находит, измените способ поиска. Перечислите, где был записан ключ, которым злоупотребили, — и круг возможных путей утечки сузится. Если, например, этот ключ существовал только в переменных окружения рабочего сервера, подозревайте рабочий сервер или хостинг-провайдера, а не Git или браузер.
| Где утекает | Частая причина | Как проверить свою настройку | Как закрыть |
|---|---|---|---|
| История Git / публичные репозитории | Коммит .env, ключи прямо в коде | Просканируйте всю историю через gitleaks git. Проверьте оповещения secret scanning на GitHub | Сначала отзовите, затем блокируйте до коммита |
| JavaScript, отправляемый в браузер | Ключ в переменной NEXT_PUBLIC_, вызов API прямо из браузера | Найдите префикс ключа в результатах сборки | Перенесите вызов API в серверный код |
| Публичная веб-папка | .env или резервные копии в публичной папке | Откройте /.env на своём сайте и убедитесь, что возвращается 404 или 403 | Вынесите секретные файлы за пределы публичной папки |
| Логи / страницы ошибок | Выпуск с включённым отладочным выводом, запись заголовков запросов как есть | Проверьте отладочные настройки продакшена и не появляются ли Authorization и подобные в логах | Отключите отладочный вывод и маскируйте секреты в логах |
| Работающий сервер | Опубликованная уязвимость (например, RCE), оставленная без исправления | Сравните реально работающую версию с исправленной версией в бюллетене | Обновитесь, затем замените каждый секрет окружения |
| Хостинг-провайдер | Взлом на стороне провайдера | Проверьте бюллетени безопасности и уведомления провайдера по почте | Храните секреты так, чтобы значения нельзя было просмотреть позже, и меняйте их |
| Ваша машина разработки | Вредоносный пакет или расширение | Проверьте, совпадают ли установленные версии пакетов или расширений с опубликованной компрометацией | Удалите эту версию и замените секреты, которые были на машине |
Этот случай — строка «Работающий сервер». Четыре нижние строки — пути, которые не оставляют следов в вашем коде или Git. Шпаргалка OWASP по управлению секретами (Secrets Management Cheat Sheet) также предупреждает, что переменные окружения часто доступны другим процессам в той же среде и могут попасть в логи или дампы. Поэтому, когда злоупотребили одним ключом, считайте утёкшими все секреты, хранившиеся в том же месте.
Отдельные ключи ускоряют расследование
Если для каждого сервиса и каждого окружения (продакшен и разработка) используется свой ключ, один лишь ключ, которым злоупотребили, говорит, в какой вы строке таблицы. Используйте один ключ везде — и придётся подозревать все места. Как разделять ключи, описано в статье Когда из-за украденного API-ключа блокируют аккаунт OpenAI.
Считайте уязвимости по реально работающей версии
При проверке остальных зависимостей сообщили о «ещё 8 уязвимых зависимостях». Это тоже было неверно. Их посчитали по нижней границе в package.json (диапазон с символом ^). По-настоящему опасными были лишь 2 зафиксированные (pinned) зависимости, оставшиеся позади.
Проверяйте свои реально работающие версии
Смотрите версии, которые на самом деле разрешились, — в lock-файле или в работающем контейнере.
# npm: see what's actually installed
npm ls next react react-dom
# inside a running container
docker exec <container> npm ls <package>Здесь видны версии, которые реально работают, а не ^ из package.json.
Как проверить свою настройку
Заметили ли вы только что странный счёт или ничего ещё не произошло — проверять можно в таком порядке. Каждый шаг касается только вашего собственного аккаунта и сервера.
Посмотрите, какой ключ использовался и когда
Проверьте файлы, отправляемые в браузер
NEXT_PUBLIC_, при сборке встраиваются в JavaScript, отправляемый в браузер. После сборки найдите в результатах префикс своего ключа, например grep -rl "sk-" .next/static. Всё, что там нашлось, мог прочитать кто угодно.Убедитесь, что секретные файлы недоступны из публичной папки
curl -sI https://example.com/.env для своего сайта (замените example.com своим доменом) и убедитесь, что статус в первой строке не 200 (должно быть 404 или 403). Подробнее — в статье проверка секретных файлов в публичных каталогах.Сравните версию в продакшене с бюллетенем
npm ls внутри контейнера продакшена, чтобы увидеть версию. osv-scanner scan -L pnpm-lock.yaml проверяет, есть ли в lock-файле версии с известными уязвимостями.Проверьте логи на секреты
gitleaks dir --redact для папки, где хранятся логи приложения, чтобы найти ключи, записанные в логи. --redact не выводит на экран сами найденные значения.Проверьте лимиты и оповещения
Что сделали в первую очередь (воспроизводимые шаги)
Немедленно отозвать ключ, которым злоупотребляли
Обновить фреймворк до исправленной версии
Сменить каждый секрет
Разбор взлома
Защита на стороне аккаунта
Профилактика: автоматический мониторинг CVE
Если свести к сути, этот инцидент был «человек не заметил опубликованную CVSS 10.0». Ручные проверки всегда что-то пропускают. Автоматический мониторинг это находит.
Сканирование зависимостей, которое можно начать сегодня
Бесплатно для старта — один шаг в CI.
# OSV scanner (Google) checks your lockfile
osv-scanner scan -L pnpm-lock.yaml
# On GitHub, enable Dependabot (repo Settings → Security)Этот сайт, согласно тому же уроку, сам мониторит свои зависимости на предмет CVE — мы практикуем то, что советуем.
Частые ошибки и как их исправить
- Новый ключ кладут на сервер, который всё ещё открыт: тот же путь крадёт и новый ключ. Сразу остановите ключ, которым злоупотребили, устраните уязвимость и уже затем завершите замену остальных секретов.
- Оповещение о бюджете принимают за ограничение: Google Cloud прямо указывает, что бюджет только с оповещениями не останавливает автоматически использование или расходы. Если нужно, чтобы оно останавливалось, добавьте лимит, блокирующий использование, например месячный жёсткий лимит OpenAI или лимит расходов на Workspace у Anthropic.
- Ключ AI API кладут в браузер или мобильное приложение: любое значение, попавшее на устройство пользователя, можно извлечь. Вызывайте AI API со своего сервера, а браузер пусть обращается к вашему серверу.
- Удалили из файлов или истории Git и на этом остановились: тот, кто уже скопировал ключ, всё ещё может им пользоваться. Сначала отзовите, историю чистите потом (см. почему утёкшие на GitHub учётные данные остаются действительными).
- Один и тот же ключ в разработке и в продакшене: нельзя понять, с какой стороны утечка, и приходится останавливать оба. Выпускайте отдельный ключ для каждого окружения.
Публичный пример: переменные окружения утекли со стороны провайдера
Утечки, которые не найти никаким поиском по файлам, случаются и за пределами вашего сервера. 19 апреля 2026 года Vercel сообщила о несанкционированном доступе к своим внутренним системам. По данным компании, компрометация стороннего ИИ-инструмента, которым пользовался сотрудник, привела к краже части переменных окружения клиентов, не помеченных как «sensitive» (настройка, при которой значение нельзя просмотреть после сохранения).
Vercel посоветовала клиентам проверить и сменить переменные окружения, не помеченные как sensitive, а также проверить журналы активности и недавние развёртывания. Компания также указала, что удаления проектов или аккаунта недостаточно, чтобы устранить риск.
Клиенты в такой ситуации могли бы сколько угодно искать в своём коде и Git и не найти следов утечки. Действие то же, что и в этом случае: заменить каждый секрет, хранившийся в этом месте. Порядок действий — в материале инцидент безопасности Vercel (апрель 2026) (на английском).
Уроки
Частые ошибки
- Считать делом сделанным на «остановили списания»
- Расслабляться, потому что grep чистый
- Маскировать симптом на прокси и чувствовать, что исправлено
- Считать уязвимости по нижней границе
package.json - Менять лишь один ключ, по которому подтверждено злоупотребление
Правильная защита
- Считать остановку списаний и закрытие пути утечки двумя разными задачами и делать обе
- Подозревать и утечки в среде выполнения (RCE, заголовки)
- Обновлять фреймворк, который стал причиной
- Судить по реально работающей версии
- Заменять всё окружение, если оно утекло
Итого: скачок счёта был симптомом, а причиной была RCE с CVSS 10.0, оставленная без исправления. Верную причину нашли не с первой попытки, а после нескольких ошибок и повторных проверок.
Читать дальше
- После утечки: 543 699 учётных данных, раскрытых на GitHub, всё ещё действительны — почему нужно отзывать, а не только удалять
- Глоссарий: Что такое RCE · Что такое CVE · Что такое CVSS
- Защита: Безопасный запуск Next.js (гигиена CVE)
- Инцидент: Когда .env был доступен всему миру
- Основы: Основы .env и API-ключей (что делать после утечки)
- Профилактика: Останавливайте секреты до коммита с gitleaks
- Инцидент: Когда из-за украденного API-ключа блокируют аккаунт OpenAI
Источники
- OWASP, «Secrets Management Cheat Sheet» (риски переменных окружения, реагирование на утечки): cheatsheetseries.owasp.org
- Документация Next.js, «How to use environment variables in Next.js» (встраивание
NEXT_PUBLIC_): nextjs.org - OSV-Scanner, «Usage»: google.github.io
- OpenAI, «Production best practices» (обращение с ключами, оповещения и лимиты): developers.openai.com
- Claude Help Center, «Creating and managing Workspaces» (лимиты и ключи Workspace): support.claude.com
- Google Cloud, «Create, edit, or delete budgets and budget alerts»: docs.cloud.google.com
- Vercel, «Vercel April 2026 security incident»: vercel.com