Сначала песочница: как я запускаю coding-агентов, не отдавая им ноутбук
Coding-агент получает shell, ставит пакеты и читает вашу файловую систему. Показываю конкретную схему: контейнер, allowlist на egress и гейт на зависимости - чтобы плохой совет стоил мне контейнера, а не credentials.
Pavel Duglas
AI Automation & MVP Architect
В прошлом месяце агент, который допиливал мне парсер, уверенно выполнил npm install puppeteer-stealth-plus. Пакета с таким именем не существует - зато существует очень похожий, опубликованный за одиннадцать дней до этого, с postinstall-скриптом внутри. Агент до него не добрался: единственный способ добавить зависимость в моей схеме - это обёртка, которая просто отказала. На эту обёртку я потратил двадцать минут год назад, и она уже несколько раз отработала свою стоимость.
Это ровно та часть vibe coding, которую не показывают в демо. В демо агент пишет фичу за четыре минуты. В демо не показывают, что у этого же агента есть ваши SSH-ключи, ~/.aws/credentials, .env с URL продовой базы и полноценный shell. За последний год мы уже видели и agent CLI, тихо отгружающие локальные файлы, и агентов, рекомендующих вредоносные пакеты, и агентов, решивших, что самый короткий путь к зелёным тестам - это rm -rf по чему-то нужному. Для всего этого не требуется злой умысел модели. Достаточно некомпетентности в промышленных объёмах.
Поэтому: сначала песочница. Ниже - схема, которой я реально пользуюсь.
Модель угроз без абстракций
Забудьте про рассуждения об «AI safety». Есть четыре конкретных плохих дня:
- Утечка credentials. Агент читает файл с долгоживущими секретами и отправляет содержимое куда-то: в API-вызов, в лог, в установленный пакет или в prompt, который потом хранится на серверах вендора.
- Supply chain. Агент ставит пакет, который делает не то, что написано в имени. Модели галлюцинируют названия пакетов, атакующие эти галлюцинации регистрируют. Это уже давно не гипотеза, а рабочая поверхность атаки.
- Деструктивные локальные команды. Force push, дропнутая таблица, удалённая директория,
git clean -fdxпо репозиторию с незакоммиченной работой. - Побочные эффекты в реальном мире. Агент дёргает живой API, отправляет настоящие письма, списывает настоящие деньги или логинится в настоящий аккаунт с настоящей сессией. В автоматизации это больнее всего: BAS-скрипты и боты работают с аккаунтами, которые нельзя разбанить обратно.
Всё дальше написано ради одной цели: чтобы каждый из этих пунктов стоил мне контейнера, а не недели.
Разделите агентов по радиусу поражения
Я не запускаю всех агентов одинаково. Три уровня.
Tier 0 - читает и предлагает. Никакого shell, никакой записи. Читает репозиторий, отдаёт diff или план, применяю я. Сюда идёт всё, что касается авторизации, биллинга, миграций и инфраструктурного кода. Медленно? Медленно. В этом и смысл.
Tier 1 - автономия в песочнице. Полный shell, полная запись, но внутри одноразового контейнера: отдельная копия репозитория, фейковые credentials, урезанная сеть. Здесь происходит 80% моей работы с агентами - фичи, рефакторинг, тесты, парсеры, склейка сервисов. Агент может делать что угодно, потому что «что угодно» ограничено рамками.
Tier 2 - общая инфраструктура. Всё, что трогает staging или прод. Для агентов этого уровня у меня не существует. Либо человек, либо агент готовит артефакт, который деплоит человек. Если кажется, что это перестраховка - почитайте пару разборов инцидентов, где отсутствие failover положило торговую систему, и спросите себя, хотите ли вы там агента в контуре.
Дальше - про то, как правильно собрать Tier 1, потому что именно он приносит деньги.
Контейнер
Devcontainer, обычный Docker, отдельная VM - инструмент вторичен, важны свойства. Мне нужно: никакого доступа к хостовой файловой системе кроме одной директории, никаких хостовых credentials, не root, лимиты по ресурсам и сетевой трафик через то, что я контролирую.
docker run --rm -it \
--name agent-box \
--network agent-net \
--user 1000:1000 \
--cap-drop ALL \
--security-opt no-new-privileges \
--pids-limit 512 --memory 6g --cpus 4 \
-v "$PWD/worktrees/feat-parser:/work" \
-w /work \
-e HTTPS_PROXY=http://egress:3128 \
-e HTTP_PROXY=http://egress:3128 \
--env-file ./secrets/agent.env \
agent-image:latest
Что здесь важно:
- Монтируется git worktree, а не сам репозиторий.
git worktree add worktrees/feat-parser -b feat/parser. Агент получает настоящий рабочий чекаут на своей ветке. Испортил дерево - удаляю worktree. Моя основная рабочая копия и незакоммиченные правки в контейнер вообще не попадают. - Никакого Docker socket. Прокинуть
/var/run/docker.sockв контейнер с агентом - это выдать ему root на хосте. Нужны контейнеры внутри - давайте rootless-рантайм или не давайте вообще. --env-fileуказывает на отдельные агентские credentials. Не на моё окружение шелла. И никогда-v ~/.aws:/root/.aws.- Лимиты ресурсов. Агенты пишут бесконечные циклы. Забитое ядро - это раздражает; аллокация на 40 ГБ, которая укладывает ноутбук посреди созвона - это уже другое.
Какие credentials агенту вообще можно давать
Правило простое: агент может держать только те секреты, которые я готов ротировать вечером в пятницу, никому об этом не сообщая.
На практике это:
- Отдельный LLM API-ключ с жёстким лимитом расходов в месяц, не мой основной - чтобы видеть, сколько именно сжёг агент.
- Тестовые ключи для всех внешних сервисов. Stripe в test mode, sandbox-эндпоинты платёжек, одноразовый токен Telegram-бота, привязанный к приватному тестовому чату.
- Локальный Postgres в той же docker-сети, залитый анонимизированным дампом. Никаких туннелей в staging.
- Ноль SSH-ключей. Если агенту нужно пушить - он пушит в форк или в ветку через fine-grained token, который умеет писать ровно в один репозиторий и не умеет ни аппрувить, ни мержить.
И бесплатная привычка: pre-commit сканирование секретов внутри контейнера (gitleaks, git secrets - что угодно). Агенты очень любят вставлять реальные ключи в конфиги «в качестве примера».
Egress-allowlist: самый ценный час вашей жизни
Контейнер с неограниченным интернетом - это контейнер, который может отгрузить наружу всё, что прочитал. Я поднимаю крошечный proxy в сети agent-net и разрешаю только то, что нужно для задачи:
# squid.conf (сокращённо)
acl allowed_dst dstdomain registry.npmjs.org pypi.org files.pythonhosted.org
acl allowed_dst dstdomain github.com codeload.github.com objects.githubusercontent.com
acl allowed_dst dstdomain api.openai.com api.anthropic.com
http_access allow allowed_dst
http_access deny all
access_log stdio:/dev/stdout
Пользы две. Первая очевидная: агент, поставивший что-то подозрительное, не позвонит домой на произвольный хост. Вторая оказалась неожиданной - лог прокси стал лучшей телеметрией поведения агента, которая у меня есть. Когда я держу tail -f во время прогона, я вижу по порядку, за чем именно агент полез. Половина улучшений в моих промптах родилась именно из чтения этого лога: видишь, что агент качает документацию по библиотеке, которую я запретил в инструкциях, - и правишь инструкции.
Если задача действительно требует широкого интернета (скрапинг, например), я выношу её в отдельный контейнер: там есть сеть, но нет ни репозитория, ни секретов, а два контейнера общаются через узкий локальный API.
Гейт на зависимости
Если бы можно было построить только одну вещь из этого списка, я бы построил эту. Агенту запрещено вызывать npm install или pip install напрямую: команды перекрыты обёрткой, которая падает с сообщением «используй guard-add».
#!/usr/bin/env bash
# guard-add.sh - единственный легальный способ добавить npm-зависимость
set -euo pipefail
pkg="$1"
meta=$(npm view "$pkg" --json) || { echo "BLOCKED: пакет не найден"; exit 1; }
created=$(jq -r '.time.created' <<<"$meta")
age=$(( ( $(date +%s) - $(date -d "$created" +%s) ) / 86400 ))
weekly=$(curl -sf "https://api.npmjs.org/downloads/point/last-week/$pkg" | jq -r '.downloads // 0')
echo "$pkg age=${age}d weekly_downloads=$weekly"
[ "$age" -lt 180 ] && { echo "BLOCKED: слишком новый, нужен человек"; exit 1; }
[ "$weekly" -lt 5000 ] && { echo "BLOCKED: слишком редкий, нужен человек"; exit 1; }
npm install --ignore-scripts --save-exact "$pkg"
echo "$(date -Iseconds) $pkg age=$age dl=$weekly" >> /work/.agent/deps.log
Порог по возрасту и по загрузкам отсекает подавляющее большинство slopsquatting-атак - пакет атакующего по определению новый и непопулярный. --ignore-scripts убирает саму возможность выполнения postinstall. --save-exact не даёт версиям уплывать под ногами. А deps.log даёт аудит по одной строке на зависимость - это несравнимо быстрее, чем читать 900 строк diff’а в lockfile.
Когда гейт блокирует что-то легитимное, я добавляю руками за тридцать секунд. Меня такой обмен полностью устраивает.
Сделайте откат дешёвым
Песочница ограничивает ущерб; дешёвый откат ограничивает потерянное время. Три привычки:
- Автокоммит по таймеру. Простой цикл
git add -A && git commit -m "wip: $(date -Iseconds)"каждые две минуты в ветке агента. История уродливая, зато bisect идеальный. Когда агент ломает что-то на сороковой минуте, я откатываюсь на тридцать восьмую, а не перезапускаю задачу целиком. - Снапшот базы.
pg_dumpперед стартом, скрипт восстановления в одну команду. Миграции - то место, где агенты проявляют максимум творчества. - Squash перед ревью. Двести wip-коммитов никто читать не будет. Я сжимаю в один diff на логическое изменение и смотрю его. Черновая история остаётся локально, пока ветка не смержена.
Как выглядит ревью, когда песочница работает
С песочницей я перестаю ревьюить на катастрофу и начинаю ревьюить на корректность - а это гораздо более осмысленное применение внимания. Чек-лист из четырёх пунктов: есть ли в deps.log что-то, чего я не ожидал; трогает ли diff файлы вне заявленного скоупа; проверяют ли тесты поведение, а не просто существование функций; появилось ли в логе прокси что-то, не относящееся к задаче.
Всё. Четыре пункта, пять минут - и нагрузка на ревью остаётся управляемой, даже когда параллельно работают два-три агента. Сейчас во многих командах жалуются, что AI завалил очереди на код-ревью. По моему опыту, большая часть этого потока - это тревога, а не информация. Когда ты не уверен, что агент не полез в твои credentials, ты читаешь каждую строку. Когда ты знаешь, что он физически не мог, ты читаешь то, что действительно важно.
Соберите песочницу один раз. Переиспользуйте на каждом проекте. Это самое высокорентабельное действие, которое превращает разработку с агентами в скучную рутину - а скука это ровно то, что нужно от инфраструктуры.
Вопросы и ответы
Не убивает ли песочница всю скорость, за которую мы вообще берём агентов?
Разовые затраты - примерно полдня на сборку образа, прокси и обёртки; дальше это переиспользуется на всех проектах. В работе замедляют только два места: гейт на зависимости (тридцать секунд на ручное добавление легитимного пакета - раз в несколько дней) и запрет на прямой доступ к staging. Зато исчезает главный тормоз: параноидальное построчное чтение каждого diff'а. У меня суммарно вышло быстрее, потому что я спокойно запускаю двух-трёх агентов параллельно, чего без изоляции не делал никогда.
Как быть, если задача - скрапинг, и агенту реально нужен широкий интернет?
Разделяйте роли по контейнерам. Один контейнер с сетью, но без репозитория и без секретов, делает саму загрузку страниц и отдаёт сырые данные через узкий локальный API или в общий volume. Второй контейнер с кодом и с egress-allowlist пишет логику разбора. Тогда «есть интернет» и «есть доступ к вашему коду и ключам» никогда не совпадают в одном процессе. Для BAS-задач я делаю то же самое: профили браузера и прокси живут отдельно от кода, который агент правит.
Достаточно ли доверять встроенным разрешениям агентского CLI вместо контейнера?
Нет. Разрешения на уровне инструмента - это дополнительный слой, а не граница безопасности: их можно обойти цепочкой команд, скриптом, который вызывает другой скрипт, или просто согласием пользователя, нажатым на автопилоте в двадцатый раз за час. Граница - это ядро ОС и сеть: отдельный пользователь, `--cap-drop ALL`, ограниченный mount, allowlist на egress. Разрешения CLI держите включёнными, но исходите из того, что однажды они не сработают.
Похожие статьи
Сделаю под ключ
Доведу вайб-кодинг-прототип до рабочего продукта
Разберу, что нагенерировал ИИ, закрою дыры в безопасности и данных и выложу в прод.
от 1 500 $ · 1-2 недели