有时候不是命令危险,而是过滤器太敏感。
NousResearch/hermes-agent issue #20064 讲的是一个很典型的终端工具误判:Hermes 的 terminal safety filter 会用一个简单正则去找 shell-level background wrapper 关键词:
\b(?:nohup|disown|setsid)\b
问题是,这个规则太粗了。
它只要在整段命令字符串里看到这些词,不管它们是在:
- 单引号里;
- 双引号里;
- Python
-c代码里; - git commit message 里;
gh pr create --body里;- echo / grep 的普通文本里;
都会误杀。
于是你可能只是写了一句说明文字,Hermes 却把它当成了真的后台包裹命令。
这不是“后台命令危险”,而是“字符串里出现了关键词”
issue 里的复现非常直接。
这些本来应该允许的命令,都会被拦:
python3 -c "x = 'preexec_fn=os.setsid'"
git commit -m "fix: replace preexec_fn=os.setsid with process_group=0"
gh pr create --body "We removed preexec_fn=os.setsid..."
echo "The function os.setsid() creates a new session"
你会得到类似提示:
Foreground command uses shell-level background wrappers (nohup/disown/setsid).
Use terminal(background=true) so Hermes can track the process...
这提示本身没错,错的是触发条件。
真正应该拦的是什么?
正确的拦截对象应该是这种 真正在 shell 层执行的命令:
setsid my_server
nohup ./script.sh &
也就是关键词作为命令本身出现,而不是只作为字符串内容出现。
这两类内容差别很大:
setsid my_server:危险/需要特殊处理"the word setsid appears in this sentence":普通文本
但旧正则没做语义分层,看到词就亮红灯。
根因:单纯的全局正则匹配
issue 指出的核心逻辑是:
_SHELL_LEVEL_BACKGROUND_RE = re.compile(r"\b(?:nohup|disown|setsid)\b", re.IGNORECASE)
这个模式的问题是:
1. 没有判断关键词是不是命令开头; 2. 没有判断它是不是在引号内部; 3. 没有判断它是不是 ;、&&、|| 之后的真正 shell 子命令; 4. 没有考虑 Python / shell / PR body / commit message 这些不同语义层。
所以它是“文本扫描器”,不是“命令解析器”。
为什么这种 bug 会严重影响日常使用?
因为 Hermes 的 terminal 工具不只是跑命令,它还经常用于:
- review PR 内容;
- 写 commit message;
- 调试 subprocess;
- 生成说明文本;
- 复制粘贴脚本片段;
- 在
-c参数里临时写小段代码。
这些场景里,setsid / nohup / disown 之类词很常见,但很多时候只是被“提到”,不是被“执行”。
一旦误杀,用户就会被迫:
- 把内容写进临时文件;
- 先 echo 到文件再执行;
- 绕开 inline 命令;
- 反复改写文本避免触发关键词。
这对一个终端代理来说很烦。
更合理的拦截方式
issue 提出的方向是:
只在关键词作为命令时匹配,而不是在字符串或参数里匹配。
一个保守修复是把正则改成只匹配命令位置,比如:
_SHELL_LEVEL_BACKGROUND_RE = re.compile(
r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\(\s*)(?:nohup|disown|setsid)\b",
re.IGNORECASE | re.MULTILINE,
)
这样至少能避免很多“引号里有关键词”的误报。
更稳一点的做法是:
1. 先剥离 quoted strings; 2. 再检测 shell-level command token; 3. 或者直接做简单 shell tokenization,而不是裸 regex。
从安全角度看,这个问题为什么值得修?
因为安全过滤器如果误报太多,用户会开始不信它。
一旦误报频率高,后果通常是:
- 用户无视警告;
- 用户绕过工具;
- 真正危险命令也被当成“又一次误报”;
- 安全层失去威慑力。
所以安全过滤最怕两件事:
- 漏报;
- 乱报。
#20064 这种就是典型的 乱报。
排查时怎么判断是不是这个问题?
如果你看到类似下面的情况,就很像这个 bug:
- 命令本身不是
nohup/disown/setsid; - 只是字符串、说明文字、commit message 里提到了这些词;
- Hermes 仍然提示要用
terminal(background=true); - 你改成写到文件后就能过。
举例:
git commit -m "refactor: remove preexec_fn=os.setsid"
这不是在启动后台进程,但旧过滤器会误判。
对 DeepAI API 中转站用户的边界说明
这篇问题发生在 Hermes 的 terminal tool,不在模型 API 层。
所以不要把它理解成 DeepAI API 中转站的问题。
DeepAI 适合解决的是:
- OpenAI-compatible Base URL;
- API Key 统一;
- 模型调用入口统一;
- 某些支持 OpenAI 兼容协议的客户端接入。
但它不能修复:
- Hermes terminal safety regex;
- 本地命令判断逻辑;
- 误把文本当 shell 命令的安全规则。
所以文中最好把 DeepAI 作为“上游 API 层”对比,而不是“修复这个 bug 的工具”。
FAQ
这是安全功能坏了吗?
不是坏了,是规则太粗,误伤太多。
为什么引号里的 setsid 也会触发?
因为旧规则扫描的是整段字符串,不区分语义位置。
只拦真正的 shell 命令该怎么做?
至少要限制关键词出现在命令位置,或者先剥离 quoted strings,再做 token 级判断。
这个问题会影响什么场景?
PR review、commit message、python -c、echo 文本、脚本注释、甚至普通说明字符串都可能被误杀。
DeepAI 能修复吗?
不能。这是 Hermes 本地 terminal 工具的过滤逻辑问题,不是模型 API 问题。
总结
#20064 的教训很简单:
安全过滤不能只靠关键词扫描。
nohup、disown、setsid 这些词只有在 shell-level command 里才危险;一旦出现在引号、参数或说明文字里,它们只是文本。
如果过滤器不分语境,就会把“提醒用户注意”误判成“用户真的要后台跑进程”,最后把正常工作流卡死。