Изоляция исходящего сетевого трафика для развёртываний в Docker
Когда Hermes работает внутри Docker, режим network_mode: host по умолчанию
даёт процессу агента неограниченный исходящий доступ в сеть. В этом гайде
показано, как разделить трафик так, чтобы ядро агента дотягивалось только до
нужных ему сервисов, а произвольные исходящие соединения блокировались.
Прежде всего это защита от атак через инъекцию промпта, которые пытаются
утащить данные наружу с помощью curl, wget или сырого HTTP из shell-команд,
сгенерированных инструментами.
Модель угроз
Модель доверия Hermes описана в §2 файла SECURITY.md.
Основная граница исполнения — терминальный бэкенд. Но при network_mode: host
любая команда, которую выполняет агент, может достучаться до любого адреса в
сети, включая внешние.
Изоляция исходящего трафика добавляет второй слой: даже если вредоносная команда выполнится внутри контейнера, до адресов вне явно разрешённого набора она не дотянется.
Архитектура
┌─────────────────────────────────────────────┐
│ Docker Network: internal (no internet) │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ hermes-agent │ │ hermes-dashboard │ │
│ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ hermes-gtw │◄───────────┘ │
│ └──────┬───────┘ │
│ │ │
└──────────┼───────────────────────────────────┘
│
┌──────────┼───────────────────────────────────┐
│ Docker Network: egress (internet-capable) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ egress-proxy │──► allowlisted hosts │
│ │ (squid / envoy) │ │
│ └─────────────────┘ │
└──────────────────────────────────────────────┘
Две Docker-сети:
internal— без маршрута по умолчанию и без доступа в интернет. Здесь работают агент, дашборд и шлюз.egress— с доступом в интернет. К этой сети подключены только сервисы, которым нужно ходить во внешние API.
Сервис шлюза подключён к обеим сетям сразу, чтобы принимать входящие сообщения из Telegram/Slack и других платформ и передавать их агенту во внутренней сети.
Конфигурация Compose
Переопределите стандартный docker-compose.yml файлом
docker-compose.override.yml:
# docker-compose.override.yml
# Изоляция исходящего трафика для production-развёртываний.
#
# Использование:
# HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
#
# Заменяет network_mode: host изолированными Docker-сетями.
networks:
internal:
driver: bridge
internal: true # без маршрута по умолчанию, без интернета
egress:
driver: bridge
services:
gateway:
network_mode: "" # сбросить host-режим по умолчанию
networks:
- internal
- egress # нужен исходящий доступ к Telegram и API LLM
ports:
- "127.0.0.1:9119:9119" # прокси дашборда, только localhost
dashboard:
network_mode: ""
networks:
- internal # только внутренняя сеть, исходящий доступ не нужен
С прокси исходящего трафика (рекомендуется)
Для более жёсткого контроля пропустите весь исходящий трафик через HTTP-прокси с явным списком разрешений:
# docker-compose.override.yml (с прокси исходящего трафика)
networks:
internal:
driver: bridge
internal: true
egress:
driver: bridge
services:
gateway:
network_mode: ""
networks:
- internal
- egress
environment:
- HTTP_PROXY=http://egress-proxy:3128
- HTTPS_PROXY=http://egress-proxy:3128
- NO_PROXY=hermes,hermes-dashboard,localhost
dashboard:
network_mode: ""
networks:
- internal
egress-proxy:
image: ubuntu/squid:6.10-24.04_edge
networks:
- egress
volumes:
- ./config/squid-allowlist.conf:/etc/squid/conf.d/allowlist.conf:ro
restart: unless-stopped
Пример config/squid-allowlist.conf:
# Разрешить HTTPS CONNECT только к этим хостам
acl allowed_hosts dstdomain api.openai.com
acl allowed_hosts dstdomain api.anthropic.com
acl allowed_hosts dstdomain openrouter.ai
acl allowed_hosts dstdomain generativelanguage.googleapis.com
acl allowed_hosts dstdomain api.telegram.org
acl allowed_hosts dstdomain api.github.com
acl allowed_hosts dstdomain discord.com
http_access allow CONNECT allowed_hosts
http_access deny all
Подгоните список разрешений под своего провайдера LLM и платформу сообщений.
Проверка настройки
Подняв стек, убедитесь, что изоляция работает:
# Из контейнера агента: должно ПРОВАЛИТЬСЯ (исходящего доступа нет)
docker compose exec gateway \
curl -sf --max-time 5 https://example.com && echo "FAIL: egress not blocked" || echo "OK: egress blocked"
# Из контейнера агента: должно СРАБОТАТЬ (внутренняя сеть)
docker compose exec gateway \
curl -sf --max-time 5 http://hermes-dashboard:9119/health && echo "OK: internal reachable" || echo "FAIL"
# Если используется прокси исходящего трафика: должно СРАБОТАТЬ (хост в списке разрешений)
docker compose exec gateway \
curl -sf --max-time 5 --proxy http://egress-proxy:3128 https://api.openai.com/v1/models && echo "OK" || echo "FAIL"
Ограничения
-
Разрешение DNS: сеть
internalпо-прежнему может разрешать внешние DNS-имена, если вы не поднимете ещё и локальный DNS-резолвер, блокирующий внешние запросы. Для большинства моделей угроз это приемлемо: само по себе разрешение DNS не уносит наружу ничего существенного. -
Не замена бэкендам-песочницам: этот гайд изолирует сеть контейнера агента. Если вы используете локальный терминальный бэкенд по умолчанию, команды инструментов выполняются в том же контейнере. Для более сильной изоляции сочетайте сегментацию сети с терминальным бэкендом в песочнице (Docker, Modal, Daytona).
-
Адаптерам платформ нужен исходящий доступ: сервису шлюза требуется исходящий доступ к API платформ сообщений. Добавляя новые адаптеры платформ, внесите их API-эндпоинты в список разрешений прокси.
Смотрите также
- SECURITY.md — модель доверия Hermes и сообщения об уязвимостях
- Docker — запуск Hermes в контейнере
- Прокси исходящего трафика — файрвол с подстановкой учётных данных для песочницы
- docker-compose.yml — стандартная конфигурация compose