Агент с root-доступом к серверу: как собственная автоматизация работала против меня — и три принципа контроля
22 июня 2026 года у меня закончилось 12 задач — ни одна из них не стояла в плане на день. Утро: звонок от клиента из Москвы, перестали приходить «окошки» в Telegram. Полдень: liaison-бот ответил мне «сейчас не могу, передам Юрию» — мне, самому Юрию. Вечер: выяснил, что мой агент с root-SSH к серверу два дня подряд публиковал в Instagram карусели, которые я явно выключил. В финале: настольный помощник, которого я строил для работы, записал в журнал наблюдений «Юрий смотрит Одни из нас, второй эпизод».
Это не ситком. Это стандартный рабочий день когда у тебя в продакшне 12+ автоматических агентов. И именно этот день объяснил мне три вещи про безопасность агентов, которые я раньше знал теоретически, а теперь знаю на практике.
День который начался с бага в формате времени
Полушин — клиент, медицинская компания в Москве — использует YClients для управления расписанием врачей. Мой бот каждые 15 минут опрашивает API YClients, собирает свободные слоты, формирует сводку и постит в рабочий Telegram-чат команды: «Терапевт: 2 окна — пятница 10:00 и 14:30, хирург: 1 окно — суббота 9:00». Простая механика, работает несколько месяцев без нареканий.
В 07:30 22 июня слоты перестали приходить. Бот не падал — статус «running», ошибок в логах нет. Просто тишина в чате.
Первая мысль — API-подписка протухла. Залез в токены — всё действующее. Запустил тестовый запрос вручную — данные пришли. Значит API живой, данные есть, но бот их не постит. Пошёл глубже в логи обработки.
Нашёл через 40 минут. YClients прислал слот с временем «1:00» вместо стандартного «01:00». Один символ. Мой код резал строку времени по фиксированным позициям и ожидал ровно «01:00» — двузначный час. «1:00» не матчилось. Слот считался невалидным и отфильтровывался. Весь постинг вставал.
Бот молчал с полуночи, когда пришёл первый «1:00», до 09:15 когда я нашёл и починил. Это 9+ часов без уведомлений для команды клиники. Врачи не знали о свободных слотах, пациентов не записывали — молча. Тихий отказ, который никто не заметил бы без ручного мониторинга, потому что бот формально «работал».
Починил в два шага. Первый: переписал парсинг времени — теперь «1:00», «01:00», «9:30», «09:30» все приводятся к нормальному виду через datetime.strptime с несколькими паттернами, без резки строки по позиции. Второй: добавил активный алерт — если бот не опубликовал ни одного сообщения за 4 рабочих часа, в мой личный Telegram приходит сигнал. Потому что следующий тихий краш прошёл бы незамеченным снова.
Урок: тихие отказы опаснее громких. Процесс запущен, метрики зелёные, но работы нет. Система должна не только не падать — она должна уметь сообщать что она ничего не делает, когда должна делать.
Это первый и самый коварный класс проблем с автоматизацией: не «сломалось», а «работает, но не работает». Логи говорят что всё хорошо. Результата нет. Обнаруживается случайно или вообще не обнаруживается до тех пор пока клиент не звонит с вопросом «почему второй день нет окошек?». Именно поэтому для каждого критического бота у меня теперь есть не просто healthcheck «процесс запущен», а behavioral check — «сделал ли бот хотя бы одно осмысленное действие за последние N часов». Разница принципиальная.
Я выключил карусели. Они вернулись.
У меня в Instagram @yuriy_solar сейчас 45 000 подписчиков. Накапливались 8 лет. Аудитория — преимущественно СНГ, женщины 35+, аккаунт вырос на теме недвижимости и образа жизни на Бали. Сейчас я пишу про автоматизацию B2B — системы для бизнеса, AI-агенты, Telegram-боты. Аудитория и контент не совпадают.
Instagram это видит. Органический охват упал до 0.3–0.5% от базы. Это 135–225 человек при 45 000 подписчиках. По данным Metricool за май–июнь: средний охват карусели 280 человек, engagement rate 0.8%, переходов на сайт — 0. Каждая карусель — 20–30 минут работы агента: генерация изображений, вёрстка слайдов, публикация. Два месяца, ноль лидов.
22 июня утром я открыл Metricool и принял решение: карусели выключить. Не пауза — полное прекращение. Аудитория не та, ресурсы тратятся, результата нет.
Зашёл на сервер, нашёл конфиг Instagram-агента, установил флаг ig_carousels_enabled: false, перезапустил процесс. Проверил — в планировщике каруселей нет. Закрыл терминал.
На следующий день в журнале публикаций Instagram стояли два новых поста. Карусели, обе. Вышли ночью по старому расписанию.
Перепроверил конфиг — там стояло false. Файл не менялся. Но публикации произошли. Значит что-то другое публиковало, минуя этот конфиг.
Стал разбираться. Нашёл через 25 минут. В Paperclip — системе оркестрации агентов — есть отдельный Instagram-агент. Он не читает тот конфиг который я поменял. Он читает план контента из своей базы задач, достаёт SSH-ключ с root-доступом к серверу, заходит на машину, запускает скрипт публикации напрямую. Конфиг моего сервиса его не интересует — он является отдельным сервисом со своим execution path.
Я выключил одну кнопку. Агент нажимал другую. И у него был прямой ключ к серверу.
Почему агент мог это делать: root-SSH и архитектурное решение
Это не баг — это архитектурное решение которое я принял полгода назад и которое казалось разумным в момент принятия.
Когда я настраивал Instagram-агента в Paperclip, мне нужно было чтобы он мог не просто «вызвать API», а запускать локальные скрипты на сервере. Генерация каруселей — это Python, PIL/Pillow для обработки изображений, Node.js для финальной сборки, временные файлы, загрузка через локальный Instagram-клиент. Весь этот стек на сервере.
Самый простой способ дать агенту доступ к серверу — SSH-ключ. Самый простой SSH-ключ — root. Меньше возни с правами на директории, меньше «permission denied», агент делает что нужно без ограничений.
Я дал агенту root-SSH. Это решило проблему «агент не может запустить скрипт». И создало другую: агент стал сильнее меня в контексте этого сервера.
«Сильнее» — не метафора. Root-агент с SSH может делать всё что можно делать на Linux-машине с правами суперпользователя. Читать любые файлы. Запускать любые процессы. Переписывать конфиги. Перезапускать сервисы. Если его логика говорит «нужно опубликовать карусель» — он опубликует карусель. Мой флаг ig_carousels_enabled: false он не читал, потому что я его не научил это читать, и у него не было обязанности читать конфиги сервисов вне его зоны ответственности.
Это называется нарушением принципа минимальных привилегий. Агент получил больше прав чем нужно для его задачи. Лишние права создали возможность нежелательных действий — не злоумышленных, а просто тех которые агент считал правильными по своей логике.
Это не специфика Paperclip или AI-агентов вообще. Это общий принцип безопасности, который нарушают так же с обычными скриптами, сервисными аккаунтами, API-ключами. Просто с AI-агентами последствия более непредсказуемы — агент принимает решения, а не просто выполняет жёстко прописанный алгоритм.
Шесть слоёв: как я закрывал агента у которого есть root
Когда понял что происходит, встал практический вопрос: как остановить агента у которого есть root-доступ? «Поменять конфиг» не работает — это уже известно. Нужно закрыть все пути которыми агент может выполнить нежелательное действие.
Слой 1: SSH-ключ из конфигурации агента. Зашёл в Paperclip, открыл настройки Instagram-агента, убрал credentials. Агент потерял возможность подключаться к серверу напрямую. Это остановило бы публикации — но я не был уверен что это единственный путь.
Слой 2: аудит агентов с доступом к Instagram. В Paperclip у меня 14 активных агентов. Прошёлся по каждому — кто знает про Instagram-публикации? Нашёл контент-планировщика. Он не публикует сам, но может создать задачу «опубликовать карусель» в очереди Instagram-агента. Этот путь тоже закрыл — убрал из планировщика разрешение создавать Instagram-задачи.
Слой 3: права API-ключей. Даже без SSH, если у агента есть Paperclip API-ключ с широкими правами — он может через внутренний API создать задачу которую другой агент выполнит. Пересмотрел scope API-ключей всех агентов которые работают с Instagram. Ограничил до минимально необходимого.
Слой 4: kill-guard в скрипте публикации. Добавил в carousel_publish.py проверку в самом начале: читаем глобальный конфиг, если ig_publish_enabled: false — выходим с кодом 0 без каких-либо действий. Теперь даже если кто-то вызовет скрипт напрямую — он ничего не сделает при выключенном флаге.
Слой 5: ротация SSH-ключа. Старый root-ключ, который знал агент, отозвал полностью. Создал новый ключ с ограниченным доступом: отдельный системный пользователь paperclip-ig без sudo, с доступом только к директории /opt/instagram/, без права писать в конфиги системы. Агентам больше нет нужды в root — конкретные задачи требуют конкретных прав.
Слой 6: мониторинг нарушений политики. Добавил cron-проверку каждые 30 минут: если за последние 30 минут в Instagram появился пост из Paperclip-источника, а глобальный флаг ig_publish_enabled: false — в мой личный чат приходит алерт «VIOLATION: instagram post despite disabled flag». Узнаю о нарушении за минуты, а не через сутки.
Шесть слоёв за один рабочий день. Каждый закрывал отдельный путь. Остановись на первом — агент мог найти второй. На третьем — оставался четвёртый. Это не паранойя: когда у агента есть root, «выключить» означает закрыть каждый путь к действию, а не только очевидный.
Из этого вывел правило: при любом изменении политики (выключить функцию, ограничить доступ, изменить расписание) — рисуй граф всех путей которыми эта функция может выполниться. Закрывай каждую вершину, а не только корень.
Параллельно: три сервиса без ключей за два дня
Пока разбирался с каруселями, параллельно объявились ещё два сломанных сервиса — с тем же корнем, другой симптом.
В 12:44 написал в рабочий канал вопрос про текущую задачу. Liaison-бот — он фильтрует мои входящие, расставляет приоритеты, отвечает на типовые вопросы от команды — прислал ответ: «Сейчас не могу ответить, передам Юрию». Мне. Самому Юрию. Бот адресовал сообщение создателю обратно к создателю.
В 12:55 написал в Telegram личному ассистенту — планирование, напоминания. Молчание. Ни ответа, ни ошибки — просто ничего.
Оба формально «работали»: процессы запущены, в системных метриках всё зелёное. Но на запросы отвечали либо из кэшированных шаблонов (liaison), либо не отвечали вообще (ассистент).
Корень один. Накануне я переводил авторизацию ботов на новый OAuth-аккаунт — плановая ротация credentials. Прошёлся по основным сервисам, обновил токены, проверил. Но 2 бота остались со старым протухшим токеном. Они не крашились — запускались, принимали запросы и молча не могли авторизоваться для выполнения действий. Liaison-бот в такой ситуации отдавал заготовленный fallback-ответ. Ассистент просто игнорировал входящие.
Я называю это zombie-состоянием сервиса: технически запущен, фактически бесполезен. Обнаруживается только когда кто-то проверяет руками.
Выяснилось что это уже третий сервис с той же симптоматикой за 48 часов. После первой ротации OAuth я прошёлся по сервисам по памяти. Память — плохой реестр. Я не помнил все 12+ сервисов которые используют тот OAuth-аккаунт.
После того дня завёл таблицу зависимостей credentials: сервис → credentials → дата последнего обновления → другие сервисы с теми же credentials → ответственный. При любой ротации — первый шаг: открыть таблицу и обновить всех кто в списке. Занимает 15–20 минут. Без неё — иду по памяти, и в системе где 10+ сервисов память неизбежно кого-то пропустит.
Три сервиса за два дня — не случайность. Предсказуемое последствие отсутствия структурированного учёта зависимостей. Если вы управляете больше чем 3–4 автоматическими сервисами и не ведёте такой реестр — проблема уже есть, просто ещё не встретились с ней.
Три принципа контроля агентов в продакшне
Из этого дня вывел три принципа. Не новые — в информационной безопасности это давно известно. Но когда настраиваешь AI-агентов, о них почему-то забывают, потому что агент «свой», потому что «так проще настроить».
Принцип 1: минимальные привилегии
Каждый агент получает ровно те права, которые необходимы для его конкретной задачи — и не больше. Instagram-агент публикует посты через API? Ему нужен API-ключ с правом публикации в этот аккаунт — без доступа к серверу. Нужно запустить локальный скрипт? Отдельный системный пользователь с доступом только к конкретной директории, без sudo.
Практически: перед выдачей credentials напишите список конкретных действий которые агент должен выполнять. Выдайте права только на эти действия. Если через месяц понадобится что-то новое — добавите тогда, явно.
Root-доступ агенту — это как дать сотруднику ключи от всего офиса, потому что ему иногда нужно зайти в одну кладовку. Удобно в моменте, проблема потом.
На практике пересмотр прав занял один вечер: аудит каждого агента, список действий которые он реально выполняет, создание сервисного пользователя с ограниченными правами под каждую задачу. Сначала кажется лишней работой. После первого инцидента когда агент с root делает что-то что вы не просили — понимаете что это была инвестиция, а не трата времени.
Принцип 2: реестр credentials с зависимостями
Один документ — я использую таблицу — со структурой: сервис/агент → credentials (токен/ключ/аккаунт) → дата последнего обновления → другие сервисы с теми же credentials → владелец/ответственный.
При любом изменении credentials — первый шаг: открыть таблицу, обновить всех кто зависит от меняемых credentials. 15–20 минут. Без такой таблицы — идёте по памяти, и в системе с 10+ сервисами память неизбежно пропустит кого-то.
Это не дорогой инструмент. Notion, Google Sheets, Markdown-файл в репозитории. Важно что он существует и что вы открываете его первым при любом credential-изменении.
Принцип 3: активный мониторинг, не только логи
Логи пассивны: нужно открыть их чтобы что-то увидеть. Работает когда у вас 2–3 сервиса. Когда 12+ — логи огромные, и вы смотрите в них только когда что-то уже сломалось.
Нужны активные проверки. «Если сервис X не выполнял действие Y последние N часов в рабочее время — пришли алерт». «Если действие произошло хотя соответствующий флаг выключен — пришли алерт нарушения политики». «Если сервис запущен но не отвечает на тестовый запрос за 30 секунд — это zombie, пришли алерт».
У меня сейчас 14 таких проверок на разные сервисы, все работают через простые cron-скрипты на том же сервере. 4–6 часов работы на начальную настройку — за первый месяц они поймали 5 проблем которые я бы иначе обнаружил через день или позже. Три из них — zombie-состояние (сервис жив, не работает), одна — нарушение политики (агент делал что-то выключенное), одна — деградация производительности (бот отвечал, но с задержкой 45 секунд вместо 3).
Самое важное: мониторинг должен проверять результат, не только доступность. «Сервис отвечает на ping» — это не то же самое что «сервис делает свою работу». Для бота окошек — результат это опубликованное сообщение в чате. Для liaison-бота — отклик на сообщение не из кэша. Измеряйте результат, а не наличие процесса.
Итого: что значит владеть живой системой
В конце того дня, когда я разобрался с каруселями и тремя молчащими сервисами, запустил настольный помощник — штука которую строил несколько недель: Mac Vision API тихо видит экран, понимает над каким проектом я работаю, передаёт контекст Telegram-ассистенту. Первое что помощник записал в рабочий журнал: «Юрий смотрит Одни из нас, второй эпизод».
Это смешно. Но это точный образ.
Я строю системы которые меня страхуют. Они следят за окошками врачей, фильтруют входящие, публикуют контент, ведут аналитику. Они экономят несколько часов в день которые иначе уходили бы на рутину.
И в один день эти же системы замолкают по глупой причине — один символ «1:00» вместо «01:00». Воскресают против воли — агент с root-SSH читает свой план и делает своё дело. Передают вопросы обратно создателю — liaison-бот без актуального токена. Ловят на сериале — потому что первое что попалось в поле зрения Vision API.
Это не катастрофы. Это рабочий день с живой системой.
Автоматизация — это не «настроил и забыл». Это организм. Он требует обслуживания, мониторинга, понимания архитектуры. Чем больше агентов — тем важнее понимать: у каждого есть права, токены, зависимости. Каждый может молчать когда не надо и делать что-то когда вы его выключили.
Именно поэтому я документирую каждого агента в AGENTS.md — не просто «что делает», а какие у него права, какие credentials использует, что значит «выключить полностью», какие внешние сервисы затрагивает. 20 минут на агента при создании — и часы сэкономлены при следующем инциденте.
Если интересно как выглядит AGENTS.md в реальной системе, скрипты активного мониторинга, шаблон реестра credentials и разборы ещё нескольких аналогичных ситуаций из практики — всё это в моём клубе «Solar — внутрянка». Не демо-примеры, а то что реально крутится в продакшне прямо сейчас.
Полный набор артефактов — AGENTS.md, скрипты мониторинга, шаблоны, разборы инцидентов — в клубе «Solar — внутрянка», от 2 500 ₽/мес. Бери и адаптируй: https://4bos.ru/inside/
— Solar OS.