Docker 部署的网络出口隔离
在 Docker 中运行 Hermes 时,默认的 network_mode: host 让 agent 进程拥有不受限制的出站网络访问。本指南展示如何切分流量,让 agent 核心只能访问它需要的服务,同时阻断任意出站连接。
这主要是为了防御提示注入攻击——这类攻击试图通过工具生成的 shell 命令里的 curl、wget 或原始 HTTP 把数据外泄。
威胁模型
Hermes SECURITY.md §2 定义了信任模型。终端后端是主要执行边界。然而,当以 network_mode: host 运行时,agent 执行的任何命令都能访问网络上的任何端点,包括外部端点。
网络出口隔离增加了第二层:即使一个恶意命令在容器内执行,它也访问不到明确允许集合之外的端点。
架构
┌─────────────────────────────────────────────┐
│ Docker 网络:internal(无互联网) │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ hermes-agent │ │ hermes-dashboard │ │
│ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ hermes-gtw │◄───────────┘ │
│ └──────┬───────┘ │
│ │ │
└──────────┼───────────────────────────────────┘
│
┌──────────┼───────────────────────────────────┐
│ Docker 网络:egress(可联网) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ egress-proxy │──► 允许列表主机 │
│ │ (squid / envoy) │ │
│ └─────────────────┘ │
└──────────────────────────────────────────────┘
两个 Docker 网络:
internal——无默认路由,无互联网访问。agent、仪表盘和网关跑在这里。egress——有互联网访问。只有需要访问外部 API 的服务才接到这个网络。
网关服务是双宿主的(同时接到两个网络),因此它能接收来自 Telegram/Slack 等的入站消息,并转发给 internal 网络上的 agent。
Compose 配置
用 docker-compose.override.yml 覆盖默认的 docker-compose.yml:
# docker-compose.override.yml
# 生产部署的网络出口隔离。
#
# 用法:
# HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
#
# 这用隔离的 Docker 网络覆盖了 network_mode: host。
networks:
internal:
driver: bridge
internal: true # 无默认路由,无互联网
egress:
driver: bridge
services:
gateway:
network_mode: "" # 清除 host 模式默认值
networks:
- internal
- egress # 需要出站访问 Telegram、LLM API
ports:
- "127.0.0.1:9119:9119" # 仪表盘代理,仅 localhost
dashboard:
network_mode: ""
networks:
- internal # 仅内部,无需 egress
配合出口代理(推荐)
要更严格的控制,把所有出站流量经一个带明确允许列表的 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 提供商和消息平台调整允许列表。
验证设置
起栈后,验证隔离:
# 从 agent 容器:这条应失败(无 egress)
docker compose exec gateway \
curl -sf --max-time 5 https://example.com && echo "FAIL: egress not blocked" || echo "OK: egress blocked"
# 从 agent 容器:这条应成功(internal 网络)
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 解析本身不会外泄有意义的数据。 - 不能替代沙箱后端: 本指南隔离的是 agent 容器的网络。如果你用默认的本地终端后端,工具命令在同一容器内执行。要更强的隔离,把网络切分与沙箱终端后端(Docker、Modal、Daytona)结合使用。
- 平台适配器需要 egress: 网关服务需要出站访问消息平台 API。如果你新增平台适配器,把它们的 API 端点加到代理允许列表。
相关
- SECURITY.md —— Hermes 信任模型与漏洞上报
- Docker —— 在容器中运行 Hermes
- 出口代理 —— 面向沙箱的凭据注入防火墙
- docker-compose.yml —— 默认 compose 配置