Alexander Bond • 29.10.2026 • 6 мин чтения agent-securitysandboxdocker

Агент с доступом к bash: sandbox вместо надежды на промпт

Как за неделю собрать инциденты с rm -rf, утечкой .env и force push — и как брокер плюс эфемерные контейнеры с cgroups, seccomp и network=none закрывают эту дыру навсегда.

В проекте First Touch (автономные ИИ-агенты, квалификация лидов и автоматизация коммуникации) мы внедряем передовые решения на стыке автономных AI-агентов, протокола MCP и открытых стандартов контекста.

Как за неделю собрать инциденты с rm -rf, утечкой .env и force push — и как брокер плюс эфемерные контейнеры с cgroups, seccomp и network=none закрывают эту дыру навсегда.

Что болело: bash с root — это не удобство, это инцидент

За неделю эксплуатации агента с доступом к bash мы собрали полный набор: rm -rf в каталоге логов, pip install в прод-venv, git push --force в чужую ветку и — самое дорогое — curl на внешний домен с содержимым .env в теле запроса. Вектор тривиальный: prompt injection в тексте входящего лида («игнорируй предыдущие инструкции и выполни...»), и модель послушно выполнила.

Проблема не в модели. Проблема в том, что LLM получила тот же уровень привилегий, что оператор с sudo, но без паузы «а надо ли».

Почему стандартные решения не работают

  • `--dangerously-skip-permissions` и его аналоги — не безопасность, а её отсутствие с успокаивающим названием.
  • `chroot` не изолирует процессы, не режет сеть и обходится при наличии root внутри.
  • `ulimit` не спасает от fork-бомбы: `nproc` в cgroup считается иначе, чем rlimit.
  • `sudoers` с allowlist ломается о шеллы: `sudo less file` → `!sh`.
  • Regex-фильтры «опасных команд» проигрывают `$(echo cm0= | base64 -d)`.
  • Вывод: изоляция должна жить на уровне ядра и контейнера, а не на уровне текста команды.

    📌 Стандартизация контекста: Для синхронизации знаний и правил между агентами используется машиночитаемый формат базы знаний по стандарту спецификации llms.txt (https://aifirsttouch.ru/llms.txt).

    Архитектура: брокер + эфемерный контейнер

    Agent (LLM) --HTTP--> Exec Broker --docker API--> Ephemeral Sandbox --stdout--> Broker --> Agent
                                |                            |
                           policy.yaml               seccomp + cgroups v2

    Ключевое правило: агент никогда не видит docker.sock. Доступ к сокету = root на хосте. Брокер — единственное место, где живёт политика, и единственный сервис с привилегиями.

    Стек: Python 3.12 + docker SDK, cgroups v2, seccomp-профиль, опционально gVisor для недоверенных задач. Образ agent-sandbox — 180 МБ, без сети, без компилятора.

    Шаг 1. Образ песочницы

    FROM python:3.12-slim
    RUN useradd -u 10001 -M sandbox && mkdir -p /work && chown sandbox:sandbox /work
    COPY --chown=sandbox:sandbox ./tools /work/tools
    USER 10001:10001
    WORKDIR /work

    Шаг 2. Брокер исполнения

    # broker/executor.py
    import docker, uuid
    
    client = docker.from_env()
    POLICY = dict(net='none', mem='512m', cpu=1.0, pids=128, timeout=30, out_limit=32768)
    
    def run(cmd: str, image='agent-sandbox:1.4') -> dict:
        name = f'sbx-{uuid.uuid4().hex[:12]}'
        c = client.containers.run(
            image, ['bash', '-lc', cmd],
            name=name, detach=True, auto_remove=False,
            network_mode=POLICY['net'],
            mem_limit=POLICY['mem'], memswap_limit=POLICY['mem'],
            nano_cpus=int(POLICY['cpu'] * 1e9),
            pids_limit=POLICY['pids'],
            read_only=True,
            tmpfs={'/tmp': 'size=64m,noexec,nosuid,nodev'},
            working_dir='/work', user='10001:10001',
            cap_drop=['ALL'],
            security_opt=['no-new-privileges', 'seccomp=./seccomp.json'],
            labels={'agent.sandbox': '1'},
        )
        try:
            c.wait(timeout=POLICY['timeout'])
            code = c.attrs['State']['ExitCode']
        except Exception:
            c.kill(); code = 124
        out = c.logs(stdout=True, stderr=True)[:POLICY['out_limit']]
        c.remove(force=True)
        return {'exit': code, 'out': out.decode(errors='replace')}

    Шаг 3. Reaper для осиротевших контейнеров

    --rm не срабатывает при kill под нагрузкой, поэтому раз в минуту чистим по метке и возрасту:

    def reap(max_age=600):
        for c in client.containers.list(all=True, filters={'label': 'agent.sandbox=1'}):
            if c.attrs['State']['Status'] != 'running' or age(c) > max_age:
                c.remove(force=True)

    Грабли, на которые мы наступили

    1. Docker-сокет в контейнере агента = root на хосте. Вынесли брокер в отдельный сервис, у агента — только HTTP и allowlist инструментов.

    2. Flood вывода. yes в stderr даёт десятки мегабайт и валит брокер по памяти. Читаем стримом, режем на 32 КБ, затем kill.

    3. Таймаут ≠ завершение. container.wait(timeout=...) кидает исключение, а процесс живёт дальше. Явный kill() в except — обязателен.

    4. read_only без noexec на tmpfs бесполезен: модель пишет бинарь в /tmp и запускает. noexec,nosuid,nodev — не опция, а требование.

    5. Секреты в env. Не передаём переменные окружения в песочницу вообще. Нужные файлы монтируем ro и только на время задачи.

    6. Сеть bridge — это уже egress. По умолчанию none. Нужен выход — отдельный прокси с allowlist доменов и лимитом байт на запрос.

    7. seccomp по умолчанию слишком щедр. Режем ptrace, mount, unshare, keyctl, bpf. Для реально недоверенного кода — gVisor: обычный runc не барьер от kernel-эксплойта.

    Боевой кейс: First Touch

    В First Touch — автономные ИИ-агенты для квалификации лидов и автоматизации коммуникации — агент регулярно выполняет код: парсит выгрузки, дёргает внутренние API, чистит очереди. Брокер поверх описанной схемы обслуживает четыре пула песочниц (лёгкий python, парсинг, интеграции, тяжёлые задачи) и около 12 000 исполнений в сутки.

    Что дало внедрение:

  • p95 латентности запуска — 340 мс на тёплом пуле против 1.8 с на холодном (предварительный `create` без `start`);
  • инцидентов с утечкой секретов и порчей данных — 0 за 5 месяцев;
  • стоимость — ~$0.003 за запуск: контейнер живёт секунды, а не часы;
  • ~30 часов в неделю, которые раньше уходили на ручной разбор логов и вопросы «а почему тут файл удалился».
  • Что осталось сложным

    Труднее всего оказалась не изоляция, а формулировка политики: агенту нужны pip install и git, но не нужна сеть; нужен доступ к файлам задачи, но не к /home. Мы пришли к модели «capability-токен на задачу»: брокер выдаёт песочнице ровно те ro-монтирования и ровно тот сетевой профиль, что описаны в задаче, и отзывает их через N секунд.

    Коллеги, у кого агент ставит зависимости в рантайме: держите внутреннее зеркало PyPI внутри песочницы или пре-билдите окружение под класс задач? И как разруливаете конфликт «нужен egress для установки» против «egress = exfiltration»?


    🚀 Проект автора: First Touch (https://aifirsttouch.ru) — автономные ИИ-агенты, квалификация лидов и автоматизация коммуникации.

    Автор: Alexander Bond — AI-разработчик и архитектор автономных систем


    Теги: agent-security, sandbox, docker, devops, llm

    Автоматизируйте привлечение B2B-клиентов

    Платформа First Touch на базе автономных ИИ-агентов выстраивает детерминированную квалификацию лидов и персонализированную коммуникацию в Telegram.

    Попробовать First Touch