安全
Hermes Agent 采用纵深防御的安全模型设计。本页会覆盖各个安全边界,从命令审批到容器隔离,再到消息平台上的用户授权。
概览
整个安全模型分为 7 层:
- 用户授权 - 谁可以和 agent 对话(allowlist、DM pairing)
- 危险命令审批 - 对破坏性操作引入人工确认
- 容器隔离 - 通过加固配置使用 Docker / Singularity / Modal 沙箱
- MCP 凭据过滤 - 对 MCP 子进程进行环境变量隔离
- 上下文文件扫描 - 检测项目文件中的 prompt injection
- 跨会话隔离 - 会话之间不能互相访问数据或状态;cron 作业存储路径也做了路径穿越防护
- 输入净化 - 终端工具后端的工作目录参数会与 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 会为当前会话绕过全部危险命令审批提示。启用方式有三种:
- CLI flag:用
hermes --yolo或hermes chat --yolo启动会话 - Slash command:在会话中输入
/yolo进行开关切换 - 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/DATABASE | SQL DROP |
DELETE FROM (without WHERE) | 不带 WHERE 的 SQL DELETE |
TRUNCATE TABLE | SQL 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 启动网关 |
容器绕过: 当终端后端使用 docker、singularity、modal 或 daytona 时,危险命令检查会被跳过,因为容器本身就是安全边界。在容器中执行破坏性命令不会伤害宿主机。
审批流程(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 会把危险命令详情发到聊天中,并等待用户回复:
- 回复 yes、y、approve、ok 或 go 表示批准
- 回复 no、n、deny 或 cancel 表示拒绝
运行网关时会自动设置 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() 会按以下顺序检查:
- 按平台配置的 allow-all flag(例如
DISCORD_ALLOW_ALL_USERS=true) - DM pairing approved list(通过 pairing code 批准的用户)
- 平台专属 allowlist(例如
TELEGRAM_ALLOWED_USERS=12345,67890) - 全局 allowlist(
GATEWAY_ALLOWED_USERS=12345,67890) - 全局 allow-all(
GATEWAY_ALLOW_ALL_USERS=true) - 默认:拒绝
平台允许列表
你可以在 ~/.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 批准。
流程如下:
- 未知用户给 bot 发送 DM
- bot 回复一个 8 位 pairing code
- bot 所有者在 CLI 中运行
hermes pairing approve <platform> <code> - 该用户从此被永久批准,可在该平台上使用 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,清理后所有内容都会消失
对于生产环境中的网关部署,优先使用 docker、modal 或 daytona 后端,把 agent 命令与宿主系统隔离开来。这样一来,危险命令审批本身就不再是必须的安全边界。
如果你把变量名加入 terminal.docker_forward_env,这些变量就会被有意注入容器中的终端命令环境里。这对 GITHUB_TOKEN 之类的任务专用凭据很有帮助,但也意味着容器中的代码可以读取并外传这些值。
终端后端安全对比
| 后端 | 隔离级别 | 危险命令检查 | 适用场景 |
|---|---|---|---|
| local | 无隔离,直接在宿主机运行 | ✅ 是 | 开发环境、受信任用户 |
| ssh | 远程机器 | ✅ 是 | 在独立服务器上执行 |
| docker | 容器 | ❌ 跳过(容器本身就是边界) | 生产网关 |
| singularity | 容器 | ❌ 跳过 | HPC 环境 |
| modal | 云沙箱 | ❌ 跳过 | 可扩展云端隔离 |
| daytona | 云沙箱 | ❌ 跳过 | 持久化云工作区 |
环境变量透传
execute_code 和 terminal 都会从子进程环境中移除敏感变量,以防止 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_code、terminal(本地),以及远程后端(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 | 阻止名称中包含 KEY、TOKEN、SECRET、PASSWORD、CREDENTIAL、PASSWD、AUTH 的变量,只允许安全前缀变量通过 | ✅ 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_search、web_extract、browser_navigate 以及所有支持 URL 的工具上统一生效。
完整说明请见配置指南中的 Website Blocklist。
SSRF 防护
所有支持 URL 的工具(web search、web extract、vision、browser)在发起请求前都会校验 URL,以防止服务器端请求伪造(SSRF)。以下地址会被阻止:
- 私有网络(RFC 1918):
10.0.0.0/8、172.16.0.0/12、192.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.internal、metadata.goog - 保留地址、多播地址和未指定地址
SSRF 保护始终开启,无法禁用。DNS 解析失败会按阻止处理,也就是 fail-closed。重定向链路上的每一跳都会重新校验,以防止通过重定向绕过保护。
Tirith 执行前安全扫描
Hermes 集成了 tirith,在命令执行前进行内容级扫描。Tirith 可以检测那些单纯模式匹配容易漏掉的威胁:
- 同形异义 URL 欺骗(国际化域名攻击)
- 管道到解释器模式(
curl | bash、wget | 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_open 为 true(默认)时,如果 tirith 没装好或超时,命令仍会继续执行。如果你处于高安全环境,应将其设为 false,这样 tirith 不可用时命令就会被直接阻止。
Tirith 的结论会接入审批流程:安全命令直接放行;可疑命令和被阻止命令都会触发用户审批,同时附带完整的 tirith 发现结果(严重等级、标题、描述和更安全的替代方案)。用户可以批准或拒绝,默认选项是拒绝,以保证无人值守场景仍然安全。
上下文文件注入防护
上下文文件(AGENTS.md、.cursorrules、SOUL.md)在被加入系统提示前,会先做 prompt injection 扫描。扫描器会检查:
- 指示模型忽略 / 无视先前指令的内容
- 含可疑关键字的隐藏 HTML 注释
- 读取 secrets 的企图(
.env、credentials、.netrc) - 通过
curl外传凭据 - 不可见 Unicode 字符(零宽空格、双向覆盖符)
如果文件被阻止,会显示如下警告:
[BLOCKED: AGENTS.md contained potential prompt injection (prompt_injection). Content not loaded.]
生产部署最佳实践
网关部署检查清单
- 显式配置 allowlist - 生产环境不要使用
GATEWAY_ALLOW_ALL_USERS=true - 使用容器后端 - 在
config.yaml中设置terminal.backend: docker - 收紧资源限制 - 设定合适的 CPU、内存和磁盘上限
- 妥善保管密钥 - 将 API 密钥存放在
~/.hermes/.env中,并设置正确权限 - 启用 DM pairing - 尽量用 pairing code,而不是把用户 ID 硬编码进去
- 定期审查 command allowlist - 检查
config.yaml中的command_allowlist - 设置
MESSAGING_CWD- 不要让 agent 从敏感目录中运行 - 不要以 root 身份运行网关
- 监控日志 - 检查
~/.hermes/logs/是否有未授权访问尝试 - 保持更新 - 定期运行
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 的命令执行彻底分离开来。