Одна строка, открытый порт и взломанный сервер: цена вайбкодинга


У меня есть пет-проект. Простенький Telegram-бот для тренировки устного счёта. Разработка велась через Claude Code, код полностью писал он, я лишь задавал направление и проверял результат. На сервере, где работает бот, параллельно запущены и другие проекты. Один из них отправляет мне уведомление, если сервер уходит в перегрузку.
Сначала не придал значения, подумал «Ну наверное это какой-то из проектов забирает ресурсы» и забил, ведь сервер не самый мощный, и сообщение о перегрузке вполне ожидаемо, с учётом того, сколько проектов было запущено на тот момент.
Но прошёл первый день, второй, третий…
Уведомления о перегрузке сервера продолжали приходить, и я решил разобраться в причине.

Я останавливал процессы, которые нагружали сервер, и перезапускал сервисы. Нагрузка падала, но ненадолго, через время всё повторялось. И на третий день разбирательств нашлась подозрительная строка в docker-compose файле одного из проектов.
ㅤ
Порты базы данных были открыты наружу. Из-за этого к PostgreSQL можно было подключиться из интернета и получить доступ к данным сервера, а потенциально и управлять им.
Это произошло по моей ошибке. Разработка велась с помощью ИИ, и я не смог должным образом проконтролировать результаты его работы.
Я оставил внешний порт базы данных открытым, хотя боту он не требовался. При проверке я убедился, что приложение запускается и отвечает, но не заметил, что эта настройка открывает базу для внешних подключений.
Дальше разберём, что можно восстановить по косвенным признакам, и как не допустить такую же ошибку при разработке собственных продуктов.
❯ Реконструкция атаки
После этого я попытался восстановить события. Но, к сожалению, полных логов, сетевого дампа и снимка диска у меня не осталось. Поэтому дальше я буду рассказывать про возможный сценарий атаки на сервер, по косвенным признакам.
Вероятная цепочка атаки выглядела так:
1. Сканер перебирает доступные адреса в интернете и проверяет открытые порты. |
2. Он обнаружил ваш сервер, подключился к порту |
3. По ответу PostgreSQL атакующий распознаёт службу и пытается войти. В данной ситуации у него оказался рабочий пароль. Его могли подобрать, найти в утёкшей конфигурации или получить другим способом. |
4. Учётная запись, созданная через |
5. С такими правами можно не только работать с данными. PG позволяет суперпользователю запускать серверные команды через |
6. Получив доступ к PG, злоумышленник использовал штатный функционал для выполнения команд внутри контейнера. С его помощью он загрузил и запустил майнер, который начал использовать вычислительные ресурсы сервера. |
В тот момент я не пытался проводить полноценное расследование. Я видел постоянную перегрузку и не понимал, насколько глубоко проникновение, и не мог доверять состоянию сервера. В такой ситуации переустановка оказалась практичнее, чем бесконечно убивать процессы и надеяться, что больше ничего не осталось.
❯ Как этого не допустить

Если проект разрабатывается с помощью ИИ, то одного запроса мало. Нужно понимать, что ты разрабатываешь, и проверять конечный результат. Я пренебрёг этими правилами и поплатился за это.
❯ Сначала архитектура, потом код
Дом сначала ставят на прочный фундамент. Когда стены уже возведены, ошибку в основании сложнее заметить, а исправление затронет готовую конструкцию. В продукте такую роль играет архитектура. Если заранее не определить связи между компонентами и границы доступа, то в будущем могут появиться проблемы из-за упущенных деталей на этапе проектирования.
Не нужно заранее расписывать каждую мелочь. Для первого плана достаточно ответить на несколько практических вопросов:
1. Кто будет пользоваться продуктом и как выглядит основной путь от запроса до результата? |
2. Какие входы доступны из интернета, какие предназначены только для администраторов, а какие должны оставаться внутренними? |
3. Что может делать каждый компонент? |
4. Что произойдёт при сбое внешней зависимости или потере данных? |
После генерации кода сравните с этим планом конфигурацию, права и фактические соединения. Тогда ревью отвечает не на расплывчатый вопрос «всё ли безопасно?», а на конкретный: совпадает ли то, что построено, с тем, что было задумано.
❯ Промпт — половина успеха

Запрос «Добавь PostgreSQL в Compose, чтобы бот запускался» говорит, какого результата ждут, но не задаёт сетевые границы, права приложения и правила работы с данными. Если это не уточнить, агент сам заполнит пробелы. Он может добиться запуска, но одновременно оставить ненужный внешний доступ к базе.
Для изменений инфраструктуры я бы сначала попросил агента изучить текущую конфигурацию и предложить план, а не сразу менять файлы. В задании должны быть понятны цель, ограничения, критерии готовности и случаи, когда нужно остановиться.
Пример правильного запроса
Нужно подготовить план именений Docker Compose и настроек подключения к PostgreSQL для Telegram-бота.
До правок:
1. Изучи Compose-файлы, настройки подключения, запросы приложения и миграции базы.
2. Не открывай файлы с реальными секретами и не выводи значения переменных окружения.
3. Перечисли опубликованные порты, опиши текущий путь подключения к базе и предложи план. Пока не меняй файлы, дождись моего подтверждения.
Требования:
- приложение подключается к PostgreSQL по имени сервиса во внутренней сети Compose;
- порт 5432 не публикуется на хосте;
- для обычной работы приложения не используется роль суперпользователя. Определи необходимые права по коду и миграциям; если данных недостаточно, не назначай широкие права, а задай вопросы;
- не меняй данные, тома и реальные секреты, не выполняй деплой на сервер.
Если для решения нужен внешний порт, удаление или пересоздание тома либо права шире перечисленных, остановись и объясни почему.
После моего подтверждения внеси только согласованные изменения. Покажи diff, список опубликованных портов, выполненные проверки и то, что проверить не удалось. Не печатай полный вывод docker compose config, если в нём могут оказаться секреты. Не утверждай, что база недоступна из интернета, если проверка выполнялась только локально.В этом примере агент сначала выясняет, что уже настроено, и не должен молча выбирать за автора спорные права или сетевые входы. Запрос не гарантирует безопасный результат. Он задаёт критерии, по которым результат можно проверить. После правок всё равно нужно посмотреть diff и проверить, что приложение действительно соблюдает ограничения.
Тот же подход полезен и вне Docker. Для загрузки файлов укажите допустимый размер, место хранения и запрет на выполнение загруженного содержимого. Для API перечислите операции, которым нужна авторизация. При обновлении зависимостей попросите назвать добавленные пакеты и объяснить, зачем они нужны.
❯ Правила проекта и права самого агента

Если в каждом запросе приходится повторять одни и те же правила и договорённости, то лучшим решением будет вынести их в инструкции проекта. Это постоянные правила работы с кодом, которые прописываете вы сами.
Можете определить требования для проекта и попросить агента записать их в правила проекта. Теперь при каждой новой сессии перед работой он будет подтягивать файл с общими правилами для всего проекта.
Как правильно записывать
Инструкции должны описывать решения именно вашего проекта, а не общие пожелания вроде «пиши качественно». Например, если в проекте есть отдельные API-обработчики, сервисный слой и модуль хранения, правило можно записать так:
## Архитектура
- API-обработчики вызывают сервисный слой; доступ к базе остаётся в модуле хранения.
## Изменения и проверки
- Перед правкой найди текущую реализацию и связанные с ней тесты.
- Не добавляй новую библиотеку, пока не объяснишь, почему не хватает текущих средств.
- После правок запусти тесты затронутой части и укажи, что не удалось проверить.
## Ограничения
- Не выводи значения секретов.
- Перед удалением данных, деплоем или изменением боевой среды остановись и запроси подтверждение.Это пример, а не готовый набор для любого репозитория. Если у проекта другая архитектура, правило должно описывать её. В инструкции стоит хранить то, что остаётся верным от задачи к задаче.
Цель и конкретный объём текущих изменений всё равно задаются в промпте. Подробную документацию лучше не копировать целиком, а указать, где агенту искать нужные сведения.
Правила проекта задают ожидаемое поведение, но сами по себе не меняют технические права агента. Если ему доступны файлы, команды или production-среда, текстовая просьба не заменит ограничения на уровне среды и подтверждение рискованных действий.
❯ Сеть: разрешать только нужные подключения

Как показала практика, открытый порт может привести к серьёзным последствиям.
Опубликованный порт становится проблемой, когда до него может дойти тот, кому этот доступ не предназначался. Одного пароля здесь мало. Лучше заранее решить, какие соединения сервер вообще должен принимать, и отбрасывать остальные ещё на границе сети.
Сетевое правило задаёт, с каких адресов и на каких портах допустимо подключение. Аутентификация проверяет, кто вошёл, а права самого сервиса определяют, что этому клиенту доступно. Одно не заменяет другое, даже сложный пароль не повод открывать внутреннюю службу всем сетям.
Для каждого входящего соединения полезно записать назначение, источник и порт
Сервис | Кто подключается | Что разрешать снаружи |
Cайт или API | Посетители | 443/TCP для HTTPS. |
SSH и админ-панели | Администратор | Только нужный порт и только с доверенного IP-адреса. |
База данных, кэш, очередь | Приложение во внутренней сети | Не открывать доступ из интернета и не публиковать порт на хосте. |
В моём случае PostgreSQL требовался боту, а не внешним клиентам. Значит, правило для 5432 в интернете не решало задачу проекта и не должно было появиться. Тот же принцип действует для Redis, Docker API и внутренних панелей.
Сначала определите клиента, потом разрешайте соединение.
Ниже покажу настройку входящих правил для уже созданного облачного сервера в панели Timeweb Cloud. У других провайдеров названия и расположение пунктов будут отличаться, но задача одна. Разрешить только нужные источники и порты.
Настройка входящих правил в панели.
Пошаговая инструкция
Инструкция ниже начинается после создания сервера. Предполагается, что аккаунт уже зарегистрирован и облачный сервер появился в панели.
Если вы ещё не дошли до этого шага, можете посмотреть, как зарегистрировать аккаунт и создать сервер, в этой статье. Пункт«Создаём и настраиваем сервер».
После возвращайтесь сюда. Здесь будем настраивать только сетевые правила.
У других провайдеров названия и расположение пунктов будут отличаться. В Timeweb Cloud правила задаются в панели, отдельно от настроек операционной системы. Cloud Firewall фильтрует внешний трафик к серверу, но не трафик внутри приватных сетей. Поэтому шаги ниже относятся к внешним подключениям, приватные соединения нужно контролировать отдельно.
Шаг 1. Составьте список до открытия панели
Определите список IP-адресов, которым будет разрешено подключение к серверу, и необходимые порты для открытия. Отдельно укажите параметры доступа к панели управления и SSH.
Шаг 2. Откройте форму Firewall

Войдите в панель Timeweb Cloud, перейдите в «Сети» → «Firewall» и нажмите «Создать». Выберите вкладку «Разрешить трафик».
Сервер будет принимать только соединения, совпавшие с правилами группы. Создайте группу с входящими правилами ниже. Не стоит рассчитывать что пустая группа будет работать как запрет на весь трафик. Пока в ней нет правил, фильтрация не применяется.
Шаг 3. Добавить входящие правила
В блоке «Входящий трафик» нажмите «Добавить правило». Эти поля определяют, откуда, по какому протоколу и на какой порт разрешить подключение:
Адрес | источник подключения. «Для всех адресов» означает любой IPv4-адрес ( |
Тип правила | готовый тип подставляет протокол и порт. Для нестандартной службы выберите собственное правило и задайте их вручную. |
Протокол | выберите тот, который использует служба: TCP, UDP или ICMP. Для HTTPS и SSH обычно нужен TCP. |
Диапазон портов | укажите только порт, который действительно нужен. Широкий диапазон «на всякий случай» не добавляйте. |
Базовые настройки зависят от того, кто должен подключаться
Публичному сайту или API разрешите
TCP 443для всех адресов.TCP 80добавляйте, только если нужен HTTP или перенаправление на HTTPS.Для SSH укажите фактический порт, обычно
22/TCP, и ограничьте источник своим IP-адресом.Базе данных, кэшу и очереди обычно не нужно публичное правило. Если внешний доступ необходим, разрешите соединение только с конкретного IP клиента.
Блок «Исходящий трафик» здесь оставьте пустым. Первое исходящее правило включает фильтрацию соединений с сервера наружу, поэтому добавляйте такие правила только при отдельной необходимости.

Шаг 4. Привязываем группу к серверу
Открываем нужный нам сервер, и находим вкладку сеть

После того как перешли во вкладку «Сеть» ищем Firewall, и жмём кнопку настроить.

Ну и выбираем заранее подготовленные настройки.

До сохранения ещё раз проверьте IP и порт SSH: ошибочное правило может закрыть удалённый доступ. Если сервер уже добавлен в другие разрешающие группы, проверьте их тоже. Правила таких групп складываются, поэтому широкое разрешение в старой группе останется активным.
Шаг 5. Проверяем результат
После сохранения нужно убедится, что группа появилась в сетевых настройках нужного сервера. Проверка должна выполняться с другой сети, с домашнего компьютера, если сервер находится в облаке. Затем проверьте подключение к разрешённым портам и доступность внутренних портов снаружи.
В Windows PowerShell выполните:
Test-NetConnection <ПУБЛИЧНЫЙ_IP> -Port 443
Test-NetConnection <ПУБЛИЧНЫЙ_IP> -Port 5432В Linux или macOS при наличии nc:
nc -vz <SERVER_IP> 443
nc -vz <SERVER_IP> 5432Для публичного веб-сервиса проверка 443 должна пройти. Подключение к 5432
Замените <ПУБЛИЧНЫЙ_IP> на внешний IP сервера. Для работающего сайта на разрешённом порту 443 ожидается TcpTestSucceeded: True, а для закрытой снаружи базы на 5432 False. Если сайт не использует 443, укажите порт другого работающего сервиса, который разрешён правилами.
❯ Скиллы: собираем и применяем накопленный опыт
Скилл — это не волшебная кнопка и не просто ещё один промпт, а инструкция к повторяющейся работе. В SKILL.md описывают, для каких задач она нужна, что проверить и какой результат вернуть. Рядом могут лежать скрипты, шаблоны и справочные материалы.
Так специалист постепенно собирает в одном месте опыт, который нарабатывался годами: типовые ошибки, приёмы проверки и критерии, помогающие отличать реальную проблему от ложной тревоги.
Сам по себе этот опыт не переносится в инструкцию автоматически. Человек отбирает полезные решения, проверяет их и обновляет скилл, когда меняется практика. Скилл задаёт порядок работы, но не расширяет технические права агента и сам по себе не доказывает безопасность результата.
Свои скиллы я храню в отдельном репозитории dotcore-skills. Там находится множество скиллов для разных целей. От подготовки README и поддержки правил проекта до аудита безопасности перед деплоем.
Готовые скиллы для аудита безопасности тоже существуют. И я взял за основу лучшие практики аудита из множества скиллов, и адаптировал под свои требования. Разделил поиск утечек и анализ кода, добавил выбор глубины проверки, маскирование секретов и понятный формат отчёта.
Что проверяет pre-deploy-audit
В скилле есть два направления аудита. Они отвечают на разные вопросы и при необходимости запускаются вместе.
Поиск утечек — нет ли в проекте секретов, ключей, токенов, персональных данных и других файлов, которые нельзя публиковать. При подготовке репозитория к открытию важно проверить не только текущие файлы, но и историю Git. Удалённый ключ может сохраниться в старом коммите. Найденные секреты скилл маскирует и не выводит целиком.
Проверка кода и инфраструктуры — есть ли в логике приложения и настройках деплоя уязвимые места. Например, ошибки обработки пользовательского ввода и авторизации, опасные вызовы и десериализация, небезопасные настройки контейнеров, проблемы в зависимостях или CI.
Для каждого направления выбирается глубина проверки.
Поверхностная: помогает быстро поймать очевидные проблемы. Cлучайно добавленный
.env, ключ в файле, опасный вызов вроде eval или отключённую проверку сертификата. Это первичный осмотр, а не полный аудит логики приложения.Средняя: проверяет настройки экспозиции.
К примеру: опубликованные порты и слишком широкие разрешения. Скилл разбирает важные или недавно изменённые участки кода. Входные точки, авторизацию, работу с базой, файлами и сетью. Также проверяются зависимости.Полная: проходит по всей кодовой базе и инфраструктуре, включая Docker и CI. Для поиска утечек дополнительно изучается история Git и проверяется, не остались ли там удалённые секреты или приватные данные. Серьёзные находки проходят отдельную проверку, чтобы отсеять ложные срабатывания.
Эти направления и уровни можно сочетать под задачу. Например, перед открытием репозитория особенно важен полный поиск утечек с проверкой истории Git. Если нужно оценить не только публикацию файлов, но и безопасность самого приложения, включается также аудит кода.
Я запускаю этот скилл в своих проектах перед тем, как сделать репозиторий публичным. Такие проверки уже помогли мне найти немало уязвимых мест и небезопасных настроек. Скилл сам ничего не исправляет, он указывает файл и строку, объясняет риск и предлагает, что проверить или изменить. Найденные проблемы я исправляю, а затем запускаю проверку повторно.
Если в отчёте подтверждаются находки уровня Critical или High, то скилл весит плашку FAILED в README.
Если находки средней серьёзности, то ставится PASSED WITH WARNINGS.
Если обязательные проверки не завершены, результат отмечается как INCOMPLETE. На положительном результате скилл выдаёт плашку PASSED.
Всё результаты проверки сохраняются в отчёт в docs/audit/ и добавляет ссылку на него в README.
Скилл не останавливает сам деплой, он просто выдаёт предупреждения. Конечное решение принимает человек.
В случае с проектом, из-за которого взломали сервер, проверка конфигурации могла бы обратить внимание на опубликованный порт 5432:5432 и потребовать выяснить, действительно ли базе нужен внешний доступ. Это помогло бы заметить опасную настройку и устранить до наступления серьезных проблем.
❯ Тесты: новая функция не должна ломать старые

Бот запускается, отвечает на команды и в принципе всё выглядит нормально. Но как только вы решили добавить новый функционал, как часть старого просто перестаёт работать. Специально для такого и были придуманы автотесты.
Автотесты ловят ошибки в поведении бота, проверяя каждый функционал по отдельности на разные сценарии. И если какое-то изменение сломает основной функционал, вы сразу об этом узнаете.
Хорошей практикой является добавлять как минимум три сценария тестирования нового функционала:
Обычный: корректные данные приводят к ожидаемому результату.
Ошибочный: неполный или неправильный ввод обрабатывается понятно, а приложение не падает.
Граничный или необычный: например, пустое значение, ноль, очень большое число или повторный запрос.
Тесты можно писать на разных уровнях.
Модульные проверяют отдельные функции и расчёты.
Интеграционные, взаимодействие частей приложения, например бота с базой данных.
Сквозные проходят важный сценарий целиком. От действия пользователя до ответа бота. Обычно модульных проверок больше, они быстрее и помогают точнее найти источник ошибки. Сквозными достаточно покрыть главные пути пользователя.
Отдельно проверяйте права, с которыми приложение работает с базой. Тесты должны подтвердить, что обычная роль бота может выполнить нужные операции, но не получает административный доступ. Если для миграций нужны расширенные права, лучше отделить их от прав, используемых ботом при обычной работе.
Автотесты функций не проверяют всю безопасность системы. Полезно различать четыре проверки:
1. функциональные тесты показывают, что поведение бота не сломалось |
2. проверка итоговой конфигурации, что в ней не появился лишний опубликованный порт |
3. проверка прав, что может сделать учётная запись приложения |
4. внешний тест, совпадает ли реальная доступность сервиса с тем, что задумано. |
После изменений обязательно нужно запускать тесты. Они покажут, сломал ты старый функционал или всё нормально.
❯ Безопасность начинается до деплоя

До инцидента я относился легкомысленно к безопасности проектов. Я выпустил проект в прод, не проверив риски, которые следовало заметить до деплоя.
И сервер был скомпрометирован.
Из этого я вынес простой урок: безопасность нужно закладывать с самого начала. Сначала продумать архитектуру. Какие части системы взаимодействуют, где проходят границы доступа, что должно быть открыто в интернет, а что должно оставаться внутри. Это фундамент проекта. Если упустить важное на этапе планирования, позже ошибка может затеряться среди кода и настроек, а исправление затронет уже готовые компоненты.
ИИ может помочь спроектировать и написать продукт, но не отвечает за принятые решения. Поэтому до начала работы стоит зафиксировать правила проекта, а повторяющиеся проверки оформить в скиллы. Например, мой pre-deploy-audit проверяет утечки и уязвимые места перед публикацией. В каждом своём проекте я запускаю его перед тем, как открыть репозиторий. Скилл помогает не пропустить шаги, но его вывод, как и результат работы агента, всё равно нужно проверять.
Перед выпуском я теперь смотрю на проект целиком.
Проверяю изменения и итоговую конфигурацию, оставляю снаружи только необходимые порты, ограничиваю права приложения. Для каждой новой функции добавляю автотесты хотя бы на обычный, ошибочный и граничный сценарии, а тесты запускаю после изменений, что бы проверить, что ничего не сломал новым функционалом.
Короткий список перед запуском |
Архитектура и границы доступа продуманы. Понятно, кому нужны внешние подключения. |
В итоговой версии продукта нет случайно опубликованных внутренних секретов. |
Приложение работает с необходимыми, но не избыточными правами. |
Тесты ключевых сценариев проходят, а изменения проверены аудитом. |
Логи сохраняются, резервные копии создаются, восстановление проверено. |
Если инцидент всё же произошёл, не спешите сразу переустанавливать сервер. Сначала изолируйте затронутое окружение и, если возможно, сохраните журналы и снимок диска. Затем отзовите затронутые секреты, восстановите систему из проверенных источников и заново проверьте внешние подключения. В моём случае переустановка вернула контроль над сервером, но не восстановила потерянные следы атаки.
Работающий бот, ещё не доказательство того, что система готова к запуску. Перед деплоем важно проверить не только то, что она делает, но и кому она доступна, с какими правами работает и получится ли восстановить её после сбоя.
Ссылки
Может быть интересно:

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram‑канале ↩
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.