安全、密钥与凭据管理
本教程共 25 篇 · 第 24 篇 · 更新于 2026-07-26 · 约 16 分钟阅读
24. 安全、密钥与凭据管理
本节目标:理解 Hermes Agent 的八层安全模型,掌握危险命令审批机制,学会用外部密钥管理器替代明文
.env,了解容器隔离和出口代理如何保护你的 API Key 不被沙盒里的 Agent 偷走。
让 Agent 帮你干活很爽,但 Agent 能跑终端命令、能读写文件、能发网络请求—这意味着一旦判断失误,它可能删掉重要文件、泄露 API Key、或者执行不可逆的破坏性操作。这一章讲 Hermes Agent 怎么在”让 Agent 有能力做事”和”不让 Agent 搞出灾难”之间找平衡。
安全模型总览
Hermes Agent 采用纵深防御(defense-in-depth)策略,八层安全边界从外到内:
- 用户授权:谁能跟 Agent 说话(允许列表、DM 配对)
- 危险命令审批:破坏性操作需要人工确认
- 文件写入安全:黑名单 + 可选写入沙盒
- 容器隔离:Docker / Singularity / Modal 沙盒加硬化设置
- MCP 凭据过滤:MCP 子进程的环境变量隔离
- 上下文文件扫描:项目文件的提示注入检测
- 跨会话隔离:会话间不能互相访问数据和状态
- 输入净化:终端工具的工作目录参数经过允许列表验证,防 shell 注入
危险命令审批
三种审批模式
Agent 每次执行终端命令前,Hermes 会拿命令去匹配一张危险模式列表。命中了就拦下来,根据审批模式决定怎么处理。
approvals:
mode: smart # smart | manual | off
timeout: 300 # 等用户回复的秒数(默认 300)
cron_mode: deny # deny | approve - 定时任务碰到危险命令怎么办
mcp_reload_confirm: true # /reload-mcp 前先问
destructive_slash_confirm: true # /clear /new /reset /undo 前先问
三种模式:
| 模式 | 行为 |
|---|---|
| smart(默认) | 用辅助 LLM 评估风险。低风险命令(如 python -c "print('hello')") 自动放行。真正危险的自动拒绝。不确定的问人 |
| manual | 命中危险模式就问用户 |
| off | 关闭所有审批检查,所有命令直接执行 |
Warning
approvals.mode: off关闭所有安全提示。只在可信环境(CI/CD、容器等)里用。
YOLO 模式
YOLO 模式跳过当前会话的所有危险命令审批。三种激活方式:
# CLI 标志
hermes --yolo
hermes chat --yolo
# 会话内切换(开关式,再输一次关闭)
/yolo
# 环境变量
HERMES_YOLO_MODE=1
开启后会有两个持续视觉提醒:会话开始时的红色横幅和状态栏里的 ⚠ YOLO 标记,让你不容易忘记自己跳过了审批。
WarningYOLO 模式跳过所有危险命令安全检查—除了硬线黑名单(下面讲)。只在完全信任命令的场景用(比如一次性环境里的测试脚本)。
硬线黑名单(永远开着)
有些命令太灾难性了—不可逆的文件系统擦除、fork bomb、直接写块设备—Hermes 拒绝执行它们,不管你是不是开了 YOLO、是不是设了 approvals.mode: off、是不是 cron 在无头模式跑。
黑名单在审批层看到命令之前就拦截,没有覆盖开关:
| 模式 | 为什么硬线 |
|---|---|
rm -rf / 及变体 | 擦除文件系统根 |
rm -rf --no-preserve-root / | 明确”我要删根”的变体 |
:(){ :|:& };:(bash fork bomb) | 把主机钉死到重启 |
mkfs.* 挂载的根设备 | 格式化正在运行的系统 |
dd if=/dev/zero of=/dev/sd* | 清零物理磁盘 |
管道不可信 URL 到 sh | 远程代码执行,攻击面太广 |
碰到黑名单,工具调用返回解释性错误给 Agent,什么都不执行。
用户自定义拒绝规则
硬线黑名单是代码里固定的。approvals.deny 是它的用户可编辑版本—一组 glob 模式,匹配的终端命令无条件阻止,在 YOLO 和 off 模式之前生效。
approvals:
deny:
- "git push --force*"
- "*curl*|*sh*"
- "dd if=* of=/dev/*"
适合”YOLO 加例外”的场景:让 Agent 啥都能做,但这几样永远不行。
Note拒绝规则防的是”诚实但犯错”的 Agent,不是恶意进程。要防恶意进程,用隔离后端(Docker、Modal)或出口受限环境。
审批流程
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(默认):拒绝
消息平台(Telegram、Discord 等)上,Agent 把命令详情发到聊天,等你回复 yes / no。
选了 always 的模式会存到配置文件:
command_allowlist:
- rm
- systemctl
以后所有会话自动放行这些模式。
Tip用
hermes config edit可以查看或删除永久允许列表里的模式。
什么会触发审批
30 多条危险模式覆盖了:
- 递归删除(
rm -r) - 磁盘操作(
mkfs、dd) - SQL 破坏(
DROP TABLE、DELETE FROM不带WHERE、TRUNCATE) - 系统服务(
systemctl stop/restart/disable) - 进程管理(
kill -9 -1、pkill -9) - fork bomb
- 远程脚本执行(
curl ... | sh) - 系统配置覆写(
> /etc/、tee到~/.ssh/) - Git 破坏性操作(
git reset --hard) - Shell 脚本执行(
bash -c、python -e)
Note在 Docker / Singularity / Modal / Daytona 后端里,危险命令检查跳过,因为容器本身就是安全边界。容器里的破坏性命令伤不到宿主机。
文件写入安全
write_file 和 patch 在写磁盘前,会检查目标路径。被阻止的写入直接返回错误给 Agent—没有审批提示,没有聊天界面的覆盖方式。
受保护路径(永远阻止)
| 类别 | 例子 |
|---|---|
| OS 凭据存储 | ~/.ssh/、~/.aws/、~/.kube/、/etc/sudoers、~/.netrc |
| Hermes 凭据存储 | auth.json、.env、mcp-tokens/、pairing/ |
| 项目密钥文件 | 任意位置的 .env、.env.local、.env.production、.envrc |
可选写入沙盒
设了 HERMES_WRITE_SAFE_ROOT 后,write_file 和 patch 只能写这个目录前缀内的路径。外面的硬阻止。
# 允许项目目录和 Hermes 主目录
export HERMES_WRITE_SAFE_ROOT=/path/to/project:/home/you/.hermes
官方 Docker 镜像自动设了 HERMES_WRITE_SAFE_ROOT=/opt/data。
Warning写入保护只管
write_file和patch。terminal工具以同一个 OS 用户身份运行,仍然能通过 shell 命令cat或覆盖被拒绝的路径。黑名单减少意外损害,不沙盒恶意 Agent。
用户授权(网关)
跑消息网关时,Hermes 控制谁能跟 Bot 交互。授权检查顺序:
- 平台级全允许标志(如
DISCORD_ALLOW_ALL_USERS=true) - DM 配对已批准列表(通过配对码批准的用户)
- 平台专属允许列表(如
TELEGRAM_ALLOWED_USERS=12345,67890) - 全局允许列表(
GATEWAY_ALLOWED_USERS) - 全局全允许(
GATEWAY_ALLOW_ALL_USERS=true) - 默认:拒绝
# 平台专属允许列表
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=111222333444555666
# 跨平台允许列表
GATEWAY_ALLOWED_USERS=123456789
# 平台级全允许(慎用)
DISCORD_ALLOW_ALL_USERS=true
# 全局全允许(极度慎用)
GATEWAY_ALLOW_ALL_USERS=true
Warning如果没配任何允许列表且没设
GATEWAY_ALLOW_ALL_USERS,所有用户都被拒绝。网关启动时会打印警告提示你配置。
DM 配对系统
不用提前收集用户 ID,用配对码流程:
- 陌生用户给 Bot 发私信
- Bot 回复一个 8 字符配对码
- Bot 主人在 CLI 批准:
hermes pairing approve telegram ABC12DEF - 该用户被永久批准
安全特性(基于 OWASP + NIST SP 800-63-4):
| 特性 | 细节 |
|---|---|
| 码格式 | 32 字符无歧义字母表(无 0/O/1/I)取 8 字符 |
| 随机性 | 密码学安全(secrets.choice()) |
| 码有效期 | 1 小时 |
| 速率限制 | 每用户每 10 分钟 1 次请求 |
| 待处理上限 | 每平台最多 3 个待处理码 |
| 锁定 | 5 次失败批准 -> 锁 1 小时 |
| 文件安全 | 所有配对数据文件 chmod 0600 |
| 日志 | 码永远不输出到 stdout |
容器隔离
用 Docker 终端后端时,Hermes 给每个容器加严格的安全硬化:
_BASE_SECURITY_ARGS = [
"--cap-drop", "ALL", # 丢弃所有 Linux 能力
"--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 不可执行
]
资源限制可配置:
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)
container_persistent: true
Tip生产网关部署用
docker、modal或daytona后端隔离 Agent 命令,彻底消除危险命令审批的需要。
密钥后端:不用明文存 .env
为什么要用密钥管理器
默认情况下,所有 API Key 都存在 ~/.hermes/.env 里。小规模用没问题,但一旦你需要轮换密钥、团队共享、或者避免明文存储,就该上密钥管理器了。
Hermes 支持在进程启动时从外部密钥管理器拉取 API Key,替换 .env 里的明文存储。引导令牌(密钥管理器的访问凭证)留在 .env,其他所有提供商的密钥可以全部放管理器里集中轮换。
三种内置后端:
| 后端 | 工具 | 特点 |
|---|---|---|
| Bitwarden Secrets Manager | bws CLI(自动下载) | 免费层可用,机器账户无需 2FA |
| 1Password | op CLI(需自行安装) | op:// 引用,服务账户或桌面会话认证 |
| Command helper | 任意 CLI 金库 | 通过用户配置的辅助脚本输出 KEY=VALUE |
多源组合与优先级
可以同时启用多个密钥源。优先级规则:
- 你的
.env/ shell 默认优先。源只有设了override_existing: true才覆盖已有值(Bitwarden 默认 true,方便集中轮换) - 映射源胜过批量源。显式绑定环境变量到引用的源(
env:映射)优先于注入整个项目密钥的源 - 先到先得。同一类型内,
secrets.sources列表顺序决定,后来的对已声明变量的声明被跳过(带启动警告)
每个注入的凭据都标注来源—hermes model 会显示 (from Bitwarden) 告诉你值从哪来。
Bitwarden Secrets Manager
工作流程:
- 在 Bitwarden 网页应用创建机器账户,给它读权限,生成访问令牌
- Hermes 把令牌存到
~/.hermes/.env作为BWS_ACCESS_TOKEN - 每次
hermes启动时,调bws secret list <project_id>拉取密钥注入环境变量 - 默认覆盖已有值,在网页应用轮换一次所有 Hermes 进程下次启动就生效
bws 二进制首次使用时自动下载到 ~/.hermes/bin/,不用 apt / brew / sudo。
设置向导:
hermes secrets bitwarden setup
向导会:
- 下载并验证
bws二进制 - 提示输入访问令牌(隐藏输入)
- 问你的 Bitwarden 区域(US Cloud / EU Cloud / 自托管)
- 列出机器账户可见的项目,选一个
- 测试拉取并显示哪些环境变量会被解析
- 开启
secrets.bitwarden.enabled: true
非交互式设置:
hermes secrets bitwarden setup \
--access-token "$BWS_ACCESS_TOKEN" \
--server-url https://vault.bitwarden.eu \
--project-id <project-uuid>
确认状态:
hermes secrets bitwarden status
配置项:
secrets:
bitwarden:
enabled: false
access_token_env: BWS_ACCESS_TOKEN
project_id: ""
server_url: ""
cache_ttl_seconds: 300
override_existing: true
auto_install: true
NoteBitwarden 永远不会阻塞 Hermes 启动。出问题时你在 stderr 看到一行警告,Hermes 继续用
.env里已有的凭据。
1Password
工作流程:
- 安装官方
opCLI 并认证(服务账户令牌或桌面会话) - 在
config.yaml里把环境变量名映射到op://引用 - 每次
hermes启动时,对每个引用跑op read解析值注入环境变量
设置:
# 1. 安装并登录 op
op whoami
# 2. 启用集成
hermes secrets onepassword setup
# 3. 映射凭据
hermes secrets onepassword set OPENAI_API_KEY "op://Private/OpenAI/api key"
hermes secrets onepassword set ANTHROPIC_API_KEY "op://Private/Anthropic/credential"
# 4. 预览和确认
hermes secrets onepassword sync # 试运行
hermes secrets onepassword status # 查看状态
配置:
secrets:
onepassword:
enabled: false
env:
OPENAI_API_KEY: "op://Private/OpenAI/api key"
ANTHROPIC_API_KEY: "op://Private/Anthropic/credential"
account: ""
service_account_token_env: OP_SERVICE_ACCOUNT_TOKEN
cache_ttl_seconds: 300
override_existing: true
Tip服务器 / CI 环境推荐用服务账户令牌(
OP_SERVICE_ACCOUNT_TOKEN)。笔记本可以用桌面会话(op signin)。Hermes 不会替你认证,也不会替你下载op。
Profile 别名
一个共享金库服务多个 Profile 时,Profile 别名功能让每个 Profile 自动拿到自己的密钥。金库里存 TELEGRAM_BOT_TOKEN_MILLA,milla Profile 的适配器读固定名 TELEGRAM_BOT_TOKEN 时自动拿到正确值。默认开启,secrets.profile_alias: false 关闭。
也可以用 preserve_existing 保护某些 Profile 专属密钥不被覆盖:
secrets:
preserve_existing: [FEISHU_APP_SECRET, TELEGRAM_BOT_TOKEN]
出口代理:iron-proxy
沙盒里的 Agent 能偷你的 Key
Docker 终端沙盒里,Agent 拿着真实的上游 API Key。一个被提示注入的 Agent 可以 cat ~/.config/openrouter/auth.json 或 printenv | grep -i key 把密钥偷走。
出口代理(egress proxy)解决这个问题:沙盒里只放不透明的代理令牌,不是真实密钥。所有沙盒出站流量经过主机上的本地 iron-proxy 守护进程,它终止 TLS 并在转发请求前把代理令牌换成真实凭据。沙盒被攻破,攻击者拿到的令牌只在配置的可信代理边界内有效。
快速上手
# 1. 安装 iron-proxy 二进制(固定版本,SHA-256 验证)
hermes egress install
# 2. 运行向导:生成 CA、铸造代理令牌、写 proxy.yaml
hermes egress setup
# 3. 启动代理守护进程
hermes egress start
# 4. 检查状态
hermes egress status
运行后,Docker 终端后端自动:
- 把
~/.hermes/proxy/ca.crt挂载到沙盒 - 设置
HTTPS_PROXY、HTTP_PROXY、REQUESTS_CA_BUNDLE、SSL_CERT_FILE等环境变量让所有 HTTP 运行时走代理并信任 CA - 在标准提供商环境变量名下导出代理令牌(如
OPENROUTER_API_KEY设为代理令牌) - 添加
--add-host=host.docker.internal:host-gateway让沙盒能到达主机端代理
配置
proxy:
enabled: false # 主开关
tunnel_port: 9090 # 隧道监听端口
auto_install: true # 自动下载 iron-proxy
credential_source: env # env | bitwarden
enforce_on_docker: true # 代理没跑时拒绝启动沙盒
allow_env_fallback: false # BW 模式下是否回退到环境变量
upstream_deny_cidrs: null # SSRF 拒绝列表
extra_allowed_hosts: [] # 额外允许的上游主机
默认允许的上游主机包括 OpenRouter、OpenAI、Anthropic、Google、xAI、Mistral、Groq、Together、DeepSeek、Nous Research。需要加自定义端点就加到 extra_allowed_hosts,支持通配符(*.foo.com)。
默认 SSRF 拒绝列表覆盖:环回地址、链路本地(含云元数据 IP 169.254.169.254)、RFC1918 私有网段、IPv6 ULA、IPv4 映射 IPv6、CGNAT。
支持的认证方案
| 提供商 | 环境变量 | 替换位置 |
|---|---|---|
| OpenRouter、OpenAI、Groq、Together、DeepSeek、Mistral、xAI、Nous | *_API_KEY | Authorization 头 |
| Anthropic | ANTHROPIC_API_KEY | x-api-key + Authorization |
| Azure OpenAI | AZURE_OPENAI_API_KEY | api-key + Authorization |
| Google AI Studio | GEMINI_API_KEY / GOOGLE_API_KEY | x-goog-api-key 头或 ?key= 参数 |
Note涉及请求签名或 SDK 生成的 OAuth 的提供商(AWS Bedrock 的 SigV4、GCP Vertex AI 的服务账户 OAuth)无法通过静态头替换。这些环境变量在沙盒里会保留真实凭据,出口隔离保证不完整。不用就
unset掉这些变量清除警告。
Bitwarden 集成
如果你已经用了 Bitwarden Secrets Manager,出口代理可以直接从那里拉真实凭据:
hermes egress setup --from-bitwarden
轮换流程变成:
- 在 Bitwarden 网页应用轮换密钥
- 主机上
hermes egress stop && hermes egress start - 之后启动的沙盒用新值
不用改 .env,不用重启主机上的 Hermes。
安全这一章到此为止。核心思路是纵深防御—不是靠单一机制,而是多层叠加:用户授权管谁能用,审批管能做什么,文件保护管能写哪里,容器隔离管能伤到什么,密钥管理器管凭据怎么存,出口代理管沙盒里能拿到什么。下一章是最后一章—排障、FAQ 和版本升级,帮你解决实际使用中碰到的问题。