Почему вайбкодер уязвим по умолчанию
Когда ты собираешь сайт с нейросетью, скорость становится главным наркотиком. Ты пишешь промпт, через минуту получаешь рабочую страницу, ещё через пять — рабочий приём платежей. Это невероятно кайфово и невероятно опасно одновременно. Код рождается быстро, а вот привычка спрашивать «а что если сюда придёт не тот, кто должен» — не рождается вместе с ним.
Классический разработчик годами набивает шишки на security-ревью, code review от коллег, обязательных чек-листах перед релизом. У вайбкодера этого фильтра часто нет вообще: есть ты, есть модель, есть деплой одной командой. Модель отлично пишет функциональность, потому что ты именно её и просил. А про подпись вебхука, secrets management или заголовки безопасности она промолчит, если ты сам не спросил.
В какой-то момент я решил не гадать, а честно проверить свои же проекты — те самые сайты, которые я клепал в режиме «быстро запустить и посмотреть, взлетит ли». Прогнал по ним аудит безопасности. Результат отрезвил: три типовые дыры, каждая из которых потенциально стоила денег или репутации.
Вайбкодинг ускоряет написание фичи, но не ускоряет написание защиты для этой фичи — их приходится добавлять отдельным осознанным шагом, иначе их просто не будет.
Что показал аудит: три находки
Я не искал экзотику — искал типовые вещи, которые ищет любой минимальный security-чеклист. И нашёл ровно те проблемы, которые обычно всплывают у проектов, собранных «на скорости»: доверие к чужому запросу без проверки, секреты не в том месте и отсутствие базовой защиты на уровне заголовков.
Дальше — по каждой находке отдельно: что было не так, почему это опасно и как я это закрыл.
Дыра №1: вебхук приёма платежей верил на слово
Самая неприятная находка. У меня есть обработчик, который принимает уведомления от платёжного шлюза: «оплата прошла, выдай товар/доступ». Проблема в том, что этот обработчик не проверял, действительно ли уведомление пришло от платёжного шлюза, а не от кого угодно с интернета.
На практике это значит следующее: любой, кто узнал или угадал адрес эндпоинта и формат запроса, теоретически мог отправить туда поддельное «оплата прошла» — и получить доступ к платному контенту или товару, не заплатив ни рубля. Никакого продвинутого взлома не требуется, это просто HTTP-запрос на открытый адрес.
Чинится это не сложно, а обидно, что изначально этого не было:
- каждое уведомление от платёжного шлюза должно проверяться на подлинность — подпись, токен или секретный заголовок, который знает только шлюз и твой сервер;
- если подпись не совпадает или отсутствует — запрос отбрасывается, никакой логики выполнения заказа не запускается;
- эндпоинт принимает данные только в ожидаемом формате от ожидаемого источника, а не «верит» телу запроса на слово.
После починки логика простая: сервер сам пересчитывает или сверяет подпись входящего уведомления и только при совпадении помечает заказ оплаченным. Всё остальное отбрасывается ещё на входе.
Вебхук — это дверь, у которой по умолчанию нет замка. Пока ты сам не поставишь проверку подлинности, дверь открыта для любого, кто её нашёл.
Дыра №2: секреты жили прямо в деплой-скрипте
Вторая находка спокойнее по драматургии, но не менее опасна по последствиям. У меня был деплой-скрипт — тот самый, который заливает код на сервер и перезапускает сайт. И в нём прямым текстом лежали чувствительные значения: ключи и пароли для доступа к внешним сервисам.
Проблема очевидна, если произнести её вслух: этот скрипт мог случайно попасть в репозиторий кода. Публичный или даже приватный репозиторий с ключами внутри — это готовый инцидент, который ждёт только момента, когда доступ к репозиторию получит кто-то лишний, или скрипт по ошибке засветится где-то ещё.
Починка заняла один вечер, но стоило сделать это раньше:
- Вынес секреты в переменные окружения. Ключи и пароли теперь живут не в коде, а в окружении сервера — скрипт деплоя их только читает, но не хранит.
- Добавил файлы с секретами в .gitignore. Даже если кто-то по привычке создаст локальный файл с конфигом, git его не подхватит и в коммит он не попадёт.
- Прошёлся по истории репозитория. Проверил, не успели ли старые версии секретов осесть в прошлых коммитах, и там, где нужно, ключи заменил на новые.
Правило на будущее простое: если значение может дать кому-то доступ к деньгам или данным пользователей — оно не пишется в коде, точка. Только переменная окружения, и только вручную выставленная на сервере.
Дыра №3: сайты жили без базовых security-заголовков
Третья находка — самая незаметная, потому что не ломает ничего сразу, а просто снижает планку защиты по умолчанию. Ни на одном из моих сайтов не были выставлены базовые HTTP-заголовки безопасности.
Без них браузер не получает от сервера инструкций вроде «не давай встраивать эту страницу в чужой iframe» или «не пытайся угадывать тип содержимого файла по содержимому вместо заявленного типа». Это открывает дорогу для типовых атак: кликджекинга, когда твою страницу незаметно накладывают поверх невидимого iframe и заставляют пользователя кликать не туда, куда он думает, и атак через неверную интерпретацию типа контента браузером.
Добавляется это буквально несколькими строчками конфигурации на уровне сервера или прокси:
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000
Ничего из этого не требует переписывать приложение — это уровень инфраструктуры, который просто раньше не был настроен, потому что при быстром запуске сайта до него руки не доходят.
Чек-лист для своей проверки и главный урок
Урок из всего аудита один: скорость вайбкодинга — это скорость написания фичи, а не скорость появления защиты для неё. Защиту нужно добавлять осознанно, отдельным шагом, и лучше сразу после того, как фича заработала, а не после инцидента.
Если хочешь быстро проверить свои проекты, начни с трёх пунктов — именно они чаще всего оказываются дырявыми:
| Что проверить | Как понять, что дыра есть | Чем закрыть |
|---|---|---|
| Вебхуки платежей и форм | Обработчик принимает и выполняет любое входящее уведомление без проверки его источника | Проверка подписи/подлинности запроса, отбрасывать всё непроверенное |
| Секреты в коде | Ключи, пароли или токены лежат текстом в скриптах, конфигах или коммитах | Переменные окружения + .gitignore, ключи не в репозитории |
| Security-заголовки | В ответе сервера нет заголовков вроде X-Frame-Options, X-Content-Type-Options | Добавить базовые заголовки на уровне сервера/прокси |
Ни один из этих пунктов не требует глубокой экспертизы в безопасности — только привычки хотя бы раз пройтись по своим проектам с этим списком в руках, вместо того чтобы полагаться на то, что «нейросеть же написала рабочий код, значит, всё в порядке». Рабочий и безопасный — это два разных критерия, и второй приходится проверять отдельно.