跳到主要内容

安全

Hermes Agent 采用纵深防御安全模型设计。本页涵盖所有安全边界——从命令审批到容器隔离,再到消息平台上的用户授权。

概述

安全模型共有七层:

  1. 用户授权 — 谁可以与 Agent 交互(白名单、DM 配对)
  2. 危险命令审批 — 针对破坏性操作的人工审核机制
  3. 容器隔离 — 采用加固设置的 Docker/Singularity/Modal 沙箱
  4. MCP 凭证过滤 — MCP 子进程的环境变量隔离
  5. 上下文文件扫描 — 检测项目文件中的提示注入
  6. 跨会话隔离 — 会话之间无法访问彼此的数据或状态;定时任务存储路径已针对路径遍历攻击进行加固
  7. 输入消毒 — 终端工具后端中的工作目录参数会经过白名单验证,防止 shell 注入

危险命令审批

在执行任何命令之前,Hermes 会将其与一份精心整理的危险模式列表进行比对。若匹配,用户必须明确审批才能执行。

审批模式

审批系统支持三种模式,通过 ~/.hermes/config.yaml 中的 approvals.mode 配置:

approvals:
mode: manual # manual | smart | off
timeout: 60 # 等待用户响应的秒数(默认:60)
模式行为
manual(默认)危险命令始终提示用户审批
smart使用辅助 LLM 评估风险。低风险命令(如 python -c "print('hello')")自动审批,明显危险的命令自动拒绝,不确定的情况升级为手动提示。
off禁用所有审批检查——等同于使用 --yolo 运行。所有命令无需提示直接执行。
注意

设置 approvals.mode: off 会禁用所有安全提示。仅在可信环境(CI/CD、容器等)中使用。

YOLO 模式

YOLO 模式会绕过当前会话的所有危险命令审批提示。可通过三种方式激活:

  1. CLI 标志:使用 hermes --yolohermes chat --yolo 启动会话
  2. 斜杠命令:在会话中输入 /yolo 切换开关
  3. 环境变量:设置 HERMES_YOLO_MODE=1

/yolo 命令是一个切换开关——每次使用都会翻转开/关状态:

> /yolo
⚡ YOLO mode ON — all commands auto-approved. Use with caution.

> /yolo
⚠ YOLO mode OFF — dangerous commands will require approval.

YOLO 模式在 CLI 和网关会话中均可使用。在内部,它设置 HERMES_YOLO_MODE 环境变量,该变量在每次命令执行前都会被检查。

危险

YOLO 模式会禁用会话中所有危险命令安全检查——但硬性黑名单除外(见下文)。仅在你完全信任所生成命令的情况下使用(例如,在一次性环境中经过充分测试的自动化脚本)。

硬性黑名单(始终生效的底线)

某些命令极具破坏性——不可逆的文件系统清除、fork 炸弹、直接写入块设备——无论以下情况如何,Hermes 都会拒绝执行:

  • --yolo / /yolo 已开启
  • approvals.mode: off
  • 定时任务以无人值守的 approve 模式运行
  • 用户明确点击"允许始终"

硬性黑名单是 --yolo 之下的底线。它在审批层看到命令之前就会触发,且没有覆盖标志。当前覆盖的模式(不完整;与 tools/approval.py::UNRECOVERABLE_BLOCKLIST 保持同步):

模式为何列为硬性黑名单
rm -rf / 及明显变体清除文件系统根目录
rm -rf --no-preserve-root /明确表示"我就是要删根目录"的变体
:(){ :|:& };: (bash fork 炸弹)使主机卡死直到重启
mkfs.* 作用于已挂载的根设备格式化正在运行的系统
dd if=/dev/zero of=/dev/sd*清零物理磁盘
将不受信任的 URL 通过管道传给 sh(顶层文件系统)远程代码执行攻击面过广,无法审批

若触发黑名单,工具调用会向 Agent 返回解释性错误,不执行任何操作。如果合法工作流需要这些命令(例如你是清除重装流水线的操作者),请在 Agent 外部运行这些命令。

审批超时

危险命令提示出现后,用户有一段可配置的时间来响应。若在超时时间内无响应,命令将被拒绝(fail-closed,失败时关闭)。

~/.hermes/config.yaml 中配置超时时间:

approvals:
timeout: 60 # 秒(默认:60)

触发审批的模式

以下模式会触发审批提示(定义于 tools/approval.py):

模式描述
rm -r / rm --recursive递归删除
rm ... /删除根路径下的内容
chmod 777/666 / o+w / a+w全局/其他可写权限
chmod --recursive 配合不安全权限递归设置全局/其他可写权限(长标志)
chown -R root / chown --recursive root递归将所有者改为 root
mkfs格式化文件系统
dd if=磁盘复制
> /dev/sd写入块设备
DROP TABLE/DATABASESQL DROP
DELETE FROM(不带 WHERE)SQL DELETE 无 WHERE 条件
TRUNCATE TABLESQL TRUNCATE
> /etc/覆盖系统配置
systemctl stop/restart/disable/mask停止/重启/禁用系统服务
kill -9 -1杀死所有进程
pkill -9强制杀死进程
Fork 炸弹模式Fork 炸弹
bash -c / sh -c / zsh -c / ksh -c通过 -c 标志执行 shell 命令(包括 -lc 等组合标志)
python -e / perl -e / ruby -e / node -c通过 -e/-c 标志执行脚本
curl ... | sh / wget ... | sh将远程内容通过管道传给 shell
bash <(curl ...) / sh <(wget ...)通过进程替换执行远程脚本
tee 写入 /etc/~/.ssh/~/.hermes/.env通过 tee 覆盖敏感文件
> / >> 重定向到 /etc/~/.ssh/~/.hermes/.env通过重定向覆盖敏感文件
xargs rmxargs 配合 rm
find -exec rm / find -deletefind 配合破坏性操作
cp/mv/install/etc/将文件复制/移动到系统配置目录
sed -i / sed --in-place 作用于 /etc/就地编辑系统配置
pkill/killall hermes/gateway防止自我终止
gateway run 配合 &/disown/nohup/setsid防止在服务管理器外启动网关
信息

容器绕过:在 dockersingularitymodaldaytonavercel_sandbox 后端运行时,危险命令检查会被跳过,因为容器本身就是安全边界。容器内的破坏性命令不会损害宿主机。

审批流程(CLI)

在交互式 CLI 中,危险命令会显示内联审批提示:

⚠️ DANGEROUS COMMAND: recursive delete
rm -rf /tmp/old-project

[o]nce | [s]ession | [a]lways | [d]eny

Choice [o/s/a/D]:

四个选项:

  • once — 允许本次执行
  • session — 本次会话期间允许此模式
  • always — 添加到永久白名单(保存至 config.yaml
  • deny(默认)— 阻止该命令

审批流程(网关/消息平台)

在消息平台上,Agent 会将危险命令详情发送到聊天中,等待用户回复:

  • 回复 yesyapproveokgo 表示批准
  • 回复 nondenycancel 表示拒绝

运行网关时,HERMES_EXEC_ASK=1 环境变量会自动设置。

永久白名单

使用"always"审批的命令会保存到 ~/.hermes/config.yaml

# 永久允许的危险命令模式
command_allowlist:
- rm
- systemctl

这些模式在启动时加载,在所有后续会话中静默审批。

提示

使用 hermes config edit 查看或删除永久白名单中的模式。

用户授权(网关)

运行消息网关时,Hermes 通过分层授权系统控制谁可以与机器人交互。

授权检查顺序

_is_user_authorized() 方法按以下顺序检查:

  1. 平台级全部允许标志(如 DISCORD_ALLOW_ALL_USERS=true
  2. DM 配对已批准列表(通过配对码批准的用户)
  3. 平台特定白名单(如 TELEGRAM_ALLOWED_USERS=12345,67890
  4. 全局白名单GATEWAY_ALLOWED_USERS=12345,67890
  5. 全局全部允许GATEWAY_ALLOW_ALL_USERS=true
  6. 默认:拒绝

平台白名单

~/.hermes/.env 中以逗号分隔的方式设置允许的用户 ID:

# 平台特定白名单
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=111222333444555666
WHATSAPP_ALLOWED_USERS=15551234567
SLACK_ALLOWED_USERS=U01ABC123

# 跨平台白名单(适用于所有平台)
GATEWAY_ALLOWED_USERS=123456789

# 平台级全部允许(谨慎使用)
DISCORD_ALLOW_ALL_USERS=true

# 全局全部允许(极度谨慎使用)
GATEWAY_ALLOW_ALL_USERS=true
注意

未配置任何白名单且未设置 GATEWAY_ALLOW_ALL_USERS所有用户将被拒绝。网关启动时会记录警告:

No user allowlists configured. All unauthorized users will be denied.
Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access,
or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).

DM 配对系统

为实现更灵活的授权,Hermes 内置基于验证码的配对系统。无需提前提供用户 ID,未知用户会收到一次性配对码,由机器人所有者通过 CLI 批准。

工作流程:

  1. 未知用户向机器人发送 DM
  2. 机器人回复一个 8 位字符的配对码
  3. 机器人所有者在 CLI 中运行 hermes pairing approve <platform> <code>
  4. 用户获得该平台的永久访问权限

~/.hermes/config.yaml 中控制如何处理未授权的私信:

unauthorized_dm_behavior: pair

whatsapp:
unauthorized_dm_behavior: ignore
  • pair 是默认值。未授权的 DM 会收到配对码回复。
  • ignore 静默丢弃未授权的 DM。
  • 平台节的设置会覆盖全局默认值,因此可以在 Telegram 上保持配对,同时让 WhatsApp 保持静默。

安全特性(基于 OWASP + NIST SP 800-63-4 指南):

特性详情
验证码格式从 32 字符无歧义字母表(不含 0/O/1/I)中生成的 8 位字符
随机性加密级随机(secrets.choice()
验证码有效期1 小时过期
频率限制每用户每 10 分钟最多 1 次请求
待处理上限每个平台最多 3 个待处理验证码
锁定5 次失败审批尝试 → 1 小时锁定
文件安全所有配对数据文件设置 chmod 0600
日志记录验证码永不记录到标准输出

配对 CLI 命令:

# 列出待处理和已批准的用户
hermes pairing list

# 批准配对码
hermes pairing approve telegram ABC12DEF

# 撤销用户访问权限
hermes pairing revoke telegram 123456789

# 清除所有待处理验证码
hermes pairing clear-pending

存储: 配对数据存储在 ~/.hermes/pairing/,按平台分为独立 JSON 文件:

  • {platform}-pending.json — 待处理配对请求
  • {platform}-approved.json — 已批准用户
  • _rate_limits.json — 频率限制和锁定追踪

容器隔离

使用 docker 终端后端时,Hermes 会对每个容器应用严格的安全加固。

Docker 安全标志

每个容器都使用以下标志运行(定义于 tools/environments/docker.py):

_SECURITY_ARGS = [
"--cap-drop", "ALL", # 删除所有 Linux capabilities
"--cap-add", "DAC_OVERRIDE", # root 可以写入绑定挂载的目录
"--cap-add", "CHOWN", # 包管理器需要文件所有权
"--cap-add", "FOWNER", # 包管理器需要文件所有权
"--security-opt", "no-new-privileges", # 阻止权限提升
"--pids-limit", "256", # 限制进程数量
"--tmpfs", "/tmp:rw,nosuid,size=512m", # 有大小限制的 /tmp
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m", # 禁止执行的 /var/tmp
"--tmpfs", "/run:rw,noexec,nosuid,size=64m", # 禁止执行的 /run
]

资源限制

容器资源可在 ~/.hermes/config.yaml 中配置:

terminal:
backend: docker
docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
docker_forward_env: [] # 仅显式白名单;留空可防止密钥进入容器
container_cpu: 1 # CPU 核心数
container_memory: 5120 # MB(默认 5GB)
container_disk: 51200 # MB(默认 50GB,需要 XFS 上的 overlay2)
container_persistent: true # 跨会话持久化文件系统

文件系统持久化

  • 持久模式container_persistent: true):从 ~/.hermes/sandboxes/docker/<task_id>/ 绑定挂载 /workspace/root
  • 临时模式container_persistent: false):使用 tmpfs 作为工作区——清理时所有内容都会丢失
提示

对于生产网关部署,使用 dockermodaldaytonavercel_sandbox 后端将 Agent 命令与宿主机系统隔离。这完全消除了危险命令审批的需要。

注意

如果你在 terminal.docker_forward_env 中添加变量名,这些变量会被故意注入到容器的终端命令中。这对任务特定的凭证(如 GITHUB_TOKEN)很有用,但也意味着在容器中运行的代码可以读取并泄露这些变量。

终端后端安全对比

后端隔离性危险命令检查最适用场景
local无——在宿主机上运行✅ 是开发、可信用户
ssh远程机器✅ 是在独立服务器上运行
docker容器❌ 跳过(容器本身是边界)生产网关
singularity容器❌ 跳过HPC 环境
modal云沙箱❌ 跳过可扩展的云隔离
daytona云沙箱❌ 跳过持久化云工作区
vercel_sandbox云微虚拟机❌ 跳过具备快照持久化的云执行

环境变量透传

execute_codeterminal 都会从子进程中剔除敏感环境变量,以防止 LLM 生成的代码泄露凭证。然而,声明了 required_environment_variables 的技能确实需要访问这些变量。

工作原理

两种机制允许特定变量通过沙箱过滤器:

1. 技能级透传(自动)

通过 skill_view/skill 命令加载技能时,如果技能声明了 required_environment_variables,那些在环境中实际已设置的变量会自动注册为透传。缺失的变量(仍处于待设置状态)则不会注册。

# 在技能的 SKILL.md frontmatter 中
required_environment_variables:
- name: TENOR_API_KEY
prompt: Tenor API key
help: Get a key from https://developers.google.com/tenor

加载此技能后,TENOR_API_KEY 会透传到 execute_codeterminal(本地)以及远程后端(Docker、Modal)——无需手动配置。

Docker 与 Modal

在 v0.5.1 之前,Docker 的 forward_env 与技能透传是独立的系统。现在它们已合并——技能声明的环境变量会自动转发到 Docker 容器和 Modal 沙箱,无需手动添加到 docker_forward_env

2. 基于配置的透传(手动)

对于未由任何技能声明的环境变量,在 config.yaml 中将其添加到 terminal.env_passthrough

terminal:
env_passthrough:
- MY_CUSTOM_KEY
- ANOTHER_TOKEN

凭证文件透传(OAuth 令牌等)

某些技能需要在沙箱中访问文件(而非仅环境变量)——例如 Google Workspace 会将 OAuth 令牌以 google_token.json 的形式存储在活动配置文件的 HERMES_HOME 下。技能在 frontmatter 中声明这些文件:

required_credential_files:
- path: google_token.json
description: Google OAuth2 token (created by setup script)
- path: google_client_secret.json
description: Google OAuth2 client credentials

加载时,Hermes 会检查这些文件是否存在于活动配置文件的 HERMES_HOME 中,并将它们注册为挂载:

  • Docker:只读绑定挂载(-v host:container:ro
  • Modal:在沙箱创建时挂载,并在每次命令前同步(处理会话中的 OAuth 设置)
  • 本地:无需操作(文件已可访问)

你也可以在 config.yaml 中手动列出凭证文件:

terminal:
credential_files:
- google_token.json
- my_custom_oauth_token.json

路径相对于 ~/.hermes/。文件会挂载到容器内的 /root/.hermes/

各沙箱的过滤规则

沙箱默认过滤透传覆盖
execute_code阻止名称中含 KEYTOKENSECRETPASSWORDCREDENTIALPASSWDAUTH 的变量;只允许安全前缀的变量通过✅ 透传变量绕过两项检查
terminal(本地)阻止明确的 Hermes 基础设施变量(提供商密钥、网关令牌、工具 API 密钥)✅ 透传变量绕过黑名单
terminal(Docker)默认不传入宿主机环境变量✅ 透传变量 + docker_forward_env 通过 -e 转发
terminal(Modal)默认不传入宿主机环境/文件✅ 凭证文件挂载;环境变量通过同步透传
MCP阻止所有内容,仅保留安全系统变量 + 显式配置的 env❌ 不受透传影响(请改用 MCP env 配置)

安全注意事项

  • 透传仅影响你或你的技能明确声明的变量——默认安全态势对任意 LLM 生成的代码不变
  • 凭证文件以只读方式挂载到 Docker 容器中
  • Skills Guard 在安装前会扫描技能内容,检测可疑的环境变量访问模式
  • 缺失/未设置的变量永远不会被注册(泄露不存在的内容是不可能的)
  • Hermes 基础设施密钥(提供商 API 密钥、网关令牌)绝不应添加到 env_passthrough——它们有专用机制

MCP 凭证处理

MCP(模型上下文协议)服务器子进程接收一个经过过滤的环境,以防止意外的凭证泄露。

安全环境变量

只有以下变量会从宿主机传递给 MCP stdio 子进程:

PATH, HOME, USER, LANG, LC_ALL, TERM, SHELL, TMPDIR

以及所有 XDG_* 变量。其他所有环境变量(API 密钥、令牌、密钥等)均被剔除

在 MCP 服务器的 env 配置中明确定义的变量会被传递:

mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..." # 只有这个会被传递

凭证脱敏

MCP 工具的错误信息在返回给 LLM 之前会经过脱敏处理。以下模式会被替换为 [REDACTED]

  • GitHub PAT(ghp_...
  • OpenAI 风格的密钥(sk-...
  • Bearer 令牌
  • token=key=API_KEY=password=secret= 参数

网站访问策略

你可以限制 Agent 通过网络和浏览器工具能够访问的网站。这对于防止 Agent 访问内部服务、管理面板或其他敏感 URL 很有用。

# 在 ~/.hermes/config.yaml 中
security:
website_blocklist:
enabled: true
domains:
- "*.internal.company.com"
- "admin.example.com"
shared_files:
- "/etc/hermes/blocked-sites.txt"

当请求被屏蔽的 URL 时,工具会返回一个解释该域名被策略屏蔽的错误。黑名单在 web_searchweb_extractbrowser_navigate 及所有支持 URL 的工具中统一生效。

详情请参阅配置指南中的网站黑名单

SSRF 防护

所有支持 URL 的工具(网页搜索、网页提取、视觉分析、浏览器)在获取前都会验证 URL,以防止服务器端请求伪造(SSRF)攻击。被阻止的地址包括:

  • 私有网络(RFC 1918):10.0.0.0/8172.16.0.0/12192.168.0.0/16
  • 回环地址127.0.0.0/8::1
  • 链路本地地址169.254.0.0/16(包括 169.254.169.254 处的云元数据)
  • CGNAT / 共享地址空间(RFC 6598):100.64.0.0/10(Tailscale、WireGuard VPN)
  • 云元数据主机名metadata.google.internalmetadata.goog
  • 保留地址、多播地址和未指定地址

SSRF 防护在面向互联网的使用场景中始终处于活动状态,DNS 失败被视为被阻止(fail-closed)。重定向链在每一跳都会重新验证,以防止基于重定向的绕过。

故意允许私有 URL

某些场景确实需要访问私有/内部 URL——将 home.arpa 解析到 RFC 1918 地址空间的家庭网络、仅限局域网的 Ollama/llama.cpp 端点、内部 Wiki、云元数据调试等。对于这些情况,提供了全局选项:

security:
allow_private_urls: true # 默认:false

启用后,网络工具、浏览器、视觉 URL 获取和网关媒体下载将不再拒绝 RFC 1918/回环/链路本地/CGNAT/云元数据目标。这是一个有意为之的信任边界——只在以下情况下启用:Agent 在运行任意提示注入的 URL 访问本地网络时,风险是可以接受的。公共网关应保持关闭。

主机子字符串防护(阻止 Unicode 域名欺骗攻击,即使底层 IP 是公共的)无论此设置如何都保持启用。

Tirith 预执行安全扫描

Hermes 集成了 tirith 进行内容级命令扫描。Tirith 能检测仅靠模式匹配无法发现的威胁:

  • 同形字母 URL 欺骗(国际化域名攻击)
  • 管道到解释器模式(curl | bashwget | sh
  • 终端注入攻击

Tirith 在首次使用时从 GitHub Release 自动安装,并进行 SHA-256 校验验证(如果有 cosign,还会进行来源验证)。

# 在 ~/.hermes/config.yaml 中
security:
tirith_enabled: true # 启用/禁用 tirith 扫描(默认:true)
tirith_path: "tirith" # tirith 二进制文件路径(默认:PATH 查找)
tirith_timeout: 5 # 子进程超时时间(秒)
tirith_fail_open: true # tirith 不可用时允许执行(默认:true)

tirith_fail_opentrue(默认)时,如果 tirith 未安装或超时,命令会继续执行。在高安全环境中设置为 false,当 tirith 不可用时阻止命令执行。

Tirith 的判断结合到审批流程中:安全命令直接通过,可疑和被阻止的命令则触发用户审批,并附带完整的 tirith 发现(严重程度、标题、描述、更安全的替代方案)。用户可以批准或拒绝——默认选择是拒绝,以确保无人值守场景的安全。

上下文文件注入防护

上下文文件(AGENTS.md、.cursorrules、SOUL.md)在包含到系统提示之前会进行提示注入扫描。扫描器检查:

  • 忽略/无视先前指令的指令
  • 含可疑关键词的隐藏 HTML 注释
  • 读取密钥的尝试(.envcredentials.netrc
  • 通过 curl 泄露凭证
  • 不可见的 Unicode 字符(零宽空格、双向文本覆盖)

被阻止的文件会显示警告:

[BLOCKED: AGENTS.md contained potential prompt injection (prompt_injection). Content not loaded.]

生产部署最佳实践

网关部署检查清单

  1. 设置明确的白名单 — 生产环境中绝不使用 GATEWAY_ALLOW_ALL_USERS=true
  2. 使用容器后端 — 在 config.yaml 中设置 terminal.backend: docker
  3. 限制资源使用 — 设置适当的 CPU、内存和磁盘限制
  4. 安全存储密钥 — 将 API 密钥保存在具有适当文件权限的 ~/.hermes/.env
  5. 启用 DM 配对 — 尽可能使用配对码而非硬编码用户 ID
  6. 审查命令白名单 — 定期审计 config.yaml 中的 command_allowlist
  7. 设置 MESSAGING_CWD — 不要让 Agent 在敏感目录中操作
  8. 以非 root 用户运行 — 绝不以 root 用户运行网关
  9. 监控日志 — 检查 ~/.hermes/logs/ 中的未授权访问尝试
  10. 保持更新 — 定期运行 hermes update 获取安全补丁

保护 API 密钥

# 设置 .env 文件的适当权限
chmod 600 ~/.hermes/.env

# 为不同服务保留独立的密钥
# 绝不将 .env 文件提交到版本控制

网络隔离

为获得最大安全性,在独立的机器或虚拟机上运行网关:

terminal:
backend: ssh
ssh_host: "agent-worker.local"
ssh_user: "hermes"
ssh_key: "~/.ssh/hermes_agent_key"

这使网关的消息连接与 Agent 的命令执行保持分离。