跳到主要内容

安全

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

概览

整个安全模型分为 7 层:

  1. 用户授权 - 谁可以和 agent 对话(allowlist、DM pairing)
  2. 危险命令审批 - 对破坏性操作引入人工确认
  3. 容器隔离 - 通过加固配置使用 Docker / Singularity / Modal 沙箱
  4. MCP 凭据过滤 - 对 MCP 子进程进行环境变量隔离
  5. 上下文文件扫描 - 检测项目文件中的 prompt injection
  6. 跨会话隔离 - 会话之间不能互相访问数据或状态;cron 作业存储路径也做了路径穿越防护
  7. 输入净化 - 终端工具后端的工作目录参数会与 allowlist 校验,以防止 shell 注入

危险命令审批

在执行任意命令之前,Hermes 会将其与一组精心维护的危险模式列表比对。一旦命中,用户必须明确批准。

审批模式

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

approvals:
mode: manual # manual | smart | off
timeout: 60 # seconds to wait for user response (default: 60)
模式行为
manual(默认)对危险命令始终要求用户手动审批
smart使用辅助 LLM 评估风险。低风险命令(例如 python -c "print('hello')")会被自动批准;真正危险的命令会被自动拒绝;不确定的情况会升级为手动提示。
off禁用所有审批检查,相当于使用 --yolo 运行。所有命令都会直接执行,不再提示。
注意

设置 approvals.mode: off 会关闭所有安全提示。只应在受信任环境中使用,例如 CI/CD 或容器内部。

YOLO 模式

YOLO mode 会为当前会话绕过全部危险命令审批提示。启用方式有三种:

  1. CLI flag:用 hermes --yolohermes chat --yolo 启动会话
  2. Slash command:在会话中输入 /yolo 进行开关切换
  3. Environment variable:设置 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 mode 在 CLI 和网关会话中都可用。其内部机制是设置 HERMES_YOLO_MODE 环境变量,并在每次执行命令前检查它。

危险

YOLO mode 会关闭该会话中的所有危险命令安全检查。只有在你完全信任将要生成的命令时才应使用,例如在可丢弃环境中的成熟自动化脚本。

审批超时

当出现危险命令提示时,用户只有一段可配置的时间来响应。如果在超时时间内没有回复,命令会按默认规则被拒绝,也就是 fail-closed。

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

approvals:
timeout: 60 # seconds (default: 60)

哪些情况会触发审批

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

模式说明
rm -r / rm --recursive递归删除
rm ... /删除根路径内容
chmod 777/666 / o+w / a+w设置 world/other-writable 权限
chmod --recursive with unsafe perms递归授予危险权限(长参数形式)
chown -R root / chown --recursive root递归 chown 到 root
mkfs格式化文件系统
dd if=磁盘复制
> /dev/sd向块设备写入
DROP TABLE/DATABASESQL DROP
DELETE FROM (without WHERE)不带 WHERE 的 SQL DELETE
TRUNCATE TABLESQL TRUNCATE
> /etc/覆盖系统配置
systemctl stop/disable/mask停止 / 禁用系统服务
kill -9 -1杀掉全部进程
pkill -9强制终止进程
Fork bomb 模式fork bomb(进程炸弹)
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 to /etc/, ~/.ssh/, ~/.hermes/.env用 tee 覆盖敏感文件
> / >> to /etc/, ~/.ssh/, ~/.hermes/.env用重定向覆盖敏感文件
xargs rm配合 rm 的 xargs
find -exec rm / find -delete通过 find 执行破坏性删除
cp/mv/install to /etc/将文件复制 / 移动到系统配置目录
sed -i / sed --in-place on /etc/就地修改系统配置
pkill/killall hermes/gateway防止自我终止
gateway run with &/disown/nohup/setsid防止绕过 service manager 启动网关
信息

容器绕过: 当终端后端使用 dockersingularitymodaldaytona 时,危险命令检查会被跳过,因为容器本身就是安全边界。在容器中执行破坏性命令不会伤害宿主机。

审批流程(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 - 加入永久 allowlist(写入 config.yaml
  • deny(默认)- 阻止该命令

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

在消息平台中,agent 会把危险命令详情发到聊天中,并等待用户回复:

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

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

永久允许列表

使用 “always” 批准过的命令模式会保存到 ~/.hermes/config.yaml

# Permanently allowed dangerous command patterns
command_allowlist:
- rm
- systemctl

这些模式会在启动时加载,并在未来所有会话中静默放行。

提示

你可以通过 hermes config edit 查看或移除永久 allowlist 中的模式。

用户授权(网关)

在运行消息网关时,Hermes 会通过一套分层授权系统控制谁可以与 bot 交互。

授权检查顺序

_is_user_authorized() 会按以下顺序检查:

  1. 按平台配置的 allow-all flag(例如 DISCORD_ALLOW_ALL_USERS=true
  2. DM pairing approved list(通过 pairing code 批准的用户)
  3. 平台专属 allowlist(例如 TELEGRAM_ALLOWED_USERS=12345,67890
  4. 全局 allowlistGATEWAY_ALLOWED_USERS=12345,67890
  5. 全局 allow-allGATEWAY_ALLOW_ALL_USERS=true
  6. 默认:拒绝

平台允许列表

你可以在 ~/.hermes/.env 中把允许的用户 ID 配置为逗号分隔列表:

# Platform-specific allowlists
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=111222333444555666
WHATSAPP_ALLOWED_USERS=15551234567
SLACK_ALLOWED_USERS=U01ABC123

# Cross-platform allowlist (checked for all platforms)
GATEWAY_ALLOWED_USERS=123456789

# Per-platform allow-all (use with caution)
DISCORD_ALLOW_ALL_USERS=true

# Global allow-all (use with extreme caution)
GATEWAY_ALLOW_ALL_USERS=true
注意

如果没有配置任何 allowlist,并且也没有设置 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).

私信配对系统

为了提供更灵活的授权方式,Hermes 内置了基于 pairing code 的系统。你不需要提前知道用户 ID;未知用户给 bot 发送私信后,会收到一次性的 pairing code,然后 bot 所有者再通过 CLI 批准。

流程如下:

  1. 未知用户给 bot 发送 DM
  2. bot 回复一个 8 位 pairing code
  3. bot 所有者在 CLI 中运行 hermes pairing approve <platform> <code>
  4. 该用户从此被永久批准,可在该平台上使用 bot

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

unauthorized_dm_behavior: pair

whatsapp:
unauthorized_dm_behavior: ignore
  • pair 是默认值。未授权的私信会收到 pairing code 回复。
  • ignore 会静默丢弃未授权私信。
  • 平台级配置会覆盖全局默认值,因此你可以在 Telegram 上启用 pairing,同时让 WhatsApp 保持静默。

安全特性(参考 OWASP 和 NIST SP 800-63-4 指南):

特性说明
编码格式8 位字符,来自 32 字符无歧义字母表(不含 0/O/1/I)
随机性密码学安全随机(secrets.choice()
编码有效期1 小时过期
速率限制每用户每 10 分钟最多 1 次请求
待处理上限每个平台最多 3 个待审批 code
锁定机制连续 5 次批准失败后锁定 1 小时
文件安全所有 pairing 数据文件均使用 chmod 0600
日志记录code 不会输出到 stdout 日志

配对相关 CLI 命令:

# List pending and approved users
hermes pairing list

# Approve a pairing code
hermes pairing approve telegram ABC12DEF

# Revoke a user's access
hermes pairing revoke telegram 123456789

# Clear all pending codes
hermes pairing clear-pending

存储位置: pairing 数据会保存在 ~/.hermes/pairing/ 中,按平台拆分为不同 JSON 文件:

  • {platform}-pending.json - 待处理 pairing 请求
  • {platform}-approved.json - 已批准用户
  • _rate_limits.json - 速率限制和锁定信息

容器隔离

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

Docker 安全参数

每个容器都会带着这些参数运行(定义在 tools/environments/docker.py 中):

_SECURITY_ARGS = [
"--cap-drop", "ALL", # Drop ALL Linux capabilities
"--cap-add", "DAC_OVERRIDE", # Root can write to bind-mounted dirs
"--cap-add", "CHOWN", # Package managers need file ownership
"--cap-add", "FOWNER", # Package managers need file ownership
"--security-opt", "no-new-privileges", # Block privilege escalation
"--pids-limit", "256", # Limit process count
"--tmpfs", "/tmp:rw,nosuid,size=512m", # Size-limited /tmp
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m", # No-exec /var/tmp
"--tmpfs", "/run:rw,noexec,nosuid,size=64m", # No-exec /run
]

资源限制

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

terminal:
backend: docker
docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
docker_forward_env: [] # Explicit allowlist only; empty keeps secrets out of the container
container_cpu: 1 # CPU cores
container_memory: 5120 # MB (default 5GB)
container_disk: 51200 # MB (default 50GB, requires overlay2 on XFS)
container_persistent: true # Persist filesystem across sessions

文件系统持久化

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

对于生产环境中的网关部署,优先使用 dockermodaldaytona 后端,把 agent 命令与宿主系统隔离开来。这样一来,危险命令审批本身就不再是必须的安全边界。

注意

如果你把变量名加入 terminal.docker_forward_env,这些变量就会被有意注入容器中的终端命令环境里。这对 GITHUB_TOKEN 之类的任务专用凭据很有帮助,但也意味着容器中的代码可以读取并外传这些值。

终端后端安全对比

后端隔离级别危险命令检查适用场景
local无隔离,直接在宿主机运行✅ 是开发环境、受信任用户
ssh远程机器✅ 是在独立服务器上执行
docker容器❌ 跳过(容器本身就是边界)生产网关
singularity容器❌ 跳过HPC 环境
modal云沙箱❌ 跳过可扩展云端隔离
daytona云沙箱❌ 跳过持久化云工作区

环境变量透传

execute_codeterminal 都会从子进程环境中移除敏感变量,以防止 LLM 生成的代码窃取凭据。不过,一些技能会在 required_environment_variables 中声明其确实需要访问的变量。

工作原理

沙箱过滤器允许特定变量通过的方式有两种:

1. 技能级直通(自动)

当某个技能被加载(通过 skill_view/skill 命令),并声明了 required_environment_variables 时,只要这些变量在当前环境中实际存在,就会自动被注册为 passthrough。那些尚未设置、仍处于待配置状态的变量则不会被注册。

# In a skill's 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 与技能级 passthrough 是两套独立机制。现在它们已经合并,技能声明的环境变量会自动转发进 Docker 容器和 Modal 沙箱,无需再手动加入 docker_forward_env

2. 基于配置的直通(手动)

对于没有被任何技能声明的环境变量,你可以在 config.yaml 中把它们加入 terminal.env_passthrough

terminal:
env_passthrough:
- MY_CUSTOM_KEY
- ANOTHER_TOKEN

凭据文件透传(OAuth token 等)

有些技能不仅需要环境变量,还需要文件进入沙箱。例如 Google Workspace 会把 OAuth token 存成活动 profile 的 HERMES_HOME 下的 google_token.json。这类文件可以通过 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 会检查这些文件是否存在于当前 profile 的 HERMES_HOME 中,并将它们注册为可挂载文件:

  • Docker:只读 bind mount(-v host:container:ro
  • Modal:在沙箱创建时挂载,并在每次命令前重新同步(支持会话中途完成 OAuth 配置)
  • Local:无需额外处理,因为本地本来就能访问这些文件

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

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

这些路径是相对于 ~/.hermes/ 的,在容器中会挂载到 /root/.hermes/

各沙箱的默认过滤规则

沙箱默认过滤透传例外
execute_code阻止名称中包含 KEYTOKENSECRETPASSWORDCREDENTIALPASSWDAUTH 的变量,只允许安全前缀变量通过✅ passthrough 变量会绕过这两层检查
terminal (local)阻止显式的 Hermes 基础设施变量(provider 密钥、网关 token、工具 API key)✅ passthrough 变量会绕过 blocklist
terminal (Docker)默认不继承任何宿主机环境变量✅ passthrough 变量和 docker_forward_env 会通过 -e 转发
terminal (Modal)默认不继承宿主环境变量和文件✅ 凭据文件会挂载,环境变量通过同步传入
MCP除安全系统变量和显式配置的 env 之外全部阻止❌ 不受 passthrough 影响,应通过 MCP 的 env 配置传入

安全注意事项

  • passthrough 只影响你或技能明确声明的变量,任意 LLM 生成代码的默认安全边界并未改变
  • 凭据文件挂载到 Docker 容器中时始终是只读
  • Skills Guard 会在安装前扫描技能内容,查找可疑的环境变量访问模式
  • 未设置或不存在的变量永远不会被注册
  • Hermes 自身的基础设施机密(provider API 密钥、网关 token)不应加入 env_passthrough,它们有专门的处理机制

MCP 凭据处理

MCP(Model Context Protocol)服务器子进程接收到的是一个经过过滤的环境,以避免无意泄露凭据。

安全环境变量

从宿主机传给 MCP stdio 子进程的环境变量只包括:

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

以及所有 XDG_* 变量。除此之外的环境变量(API keys、tokens、secrets)都会被剥离

在 MCP server 的 env 配置中显式声明的变量会被传入:

mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..." # Only this is passed

凭据脱敏

MCP 工具返回的错误消息在发回 LLM 之前会被清洗。以下模式会替换成 [REDACTED]

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

网站访问策略

你可以限制 agent 通过 web 工具和浏览器工具可以访问哪些网站,这对于阻止它访问内部服务、管理后台或其他敏感 URL 很有帮助。

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

当访问被阻止的 URL 时,工具会返回错误,说明该域名被策略禁止。这个 blocklist 会在 web_searchweb_extractbrowser_navigate 以及所有支持 URL 的工具上统一生效。

完整说明请见配置指南中的 Website Blocklist

SSRF 防护

所有支持 URL 的工具(web search、web extract、vision、browser)在发起请求前都会校验 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。重定向链路上的每一跳都会重新校验,以防止通过重定向绕过保护。

Tirith 执行前安全扫描

Hermes 集成了 tirith,在命令执行前进行内容级扫描。Tirith 可以检测那些单纯模式匹配容易漏掉的威胁:

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

Tirith 在首次使用时会从 GitHub release 自动安装,并进行 SHA-256 校验;如果系统中有 cosign,还会做 provenance 验证。

# In ~/.hermes/config.yaml
security:
tirith_enabled: true # Enable/disable tirith scanning (default: true)
tirith_path: "tirith" # Path to tirith binary (default: PATH lookup)
tirith_timeout: 5 # Subprocess timeout in seconds
tirith_fail_open: true # Allow execution when tirith is unavailable (default: true)

tirith_fail_opentrue(默认)时,如果 tirith 没装好或超时,命令仍会继续执行。如果你处于高安全环境,应将其设为 false,这样 tirith 不可用时命令就会被直接阻止。

Tirith 的结论会接入审批流程:安全命令直接放行;可疑命令和被阻止命令都会触发用户审批,同时附带完整的 tirith 发现结果(严重等级、标题、描述和更安全的替代方案)。用户可以批准或拒绝,默认选项是拒绝,以保证无人值守场景仍然安全。

上下文文件注入防护

上下文文件(AGENTS.md.cursorrulesSOUL.md)在被加入系统提示前,会先做 prompt injection 扫描。扫描器会检查:

  • 指示模型忽略 / 无视先前指令的内容
  • 含可疑关键字的隐藏 HTML 注释
  • 读取 secrets 的企图(.envcredentials.netrc
  • 通过 curl 外传凭据
  • 不可见 Unicode 字符(零宽空格、双向覆盖符)

如果文件被阻止,会显示如下警告:

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

生产部署最佳实践

网关部署检查清单

  1. 显式配置 allowlist - 生产环境不要使用 GATEWAY_ALLOW_ALL_USERS=true
  2. 使用容器后端 - 在 config.yaml 中设置 terminal.backend: docker
  3. 收紧资源限制 - 设定合适的 CPU、内存和磁盘上限
  4. 妥善保管密钥 - 将 API 密钥存放在 ~/.hermes/.env 中,并设置正确权限
  5. 启用 DM pairing - 尽量用 pairing code,而不是把用户 ID 硬编码进去
  6. 定期审查 command allowlist - 检查 config.yaml 中的 command_allowlist
  7. 设置 MESSAGING_CWD - 不要让 agent 从敏感目录中运行
  8. 不要以 root 身份运行网关
  9. 监控日志 - 检查 ~/.hermes/logs/ 是否有未授权访问尝试
  10. 保持更新 - 定期运行 hermes update,获取安全补丁

保护 API 密钥

# Set proper permissions on the .env file
chmod 600 ~/.hermes/.env

# Keep separate keys for different services
# Never commit .env files to version control

网络隔离

如果希望获得更高安全性,建议把网关运行在单独的机器或虚拟机上:

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

这样可以把网关的消息连接与 agent 的命令执行彻底分离开来。