DeepAI Paper Hermes Agent 教程 Hermes terminal 误拦截 setsid:安全过滤为什么不能只靠全文正则

Hermes terminal 误拦截 setsid:安全过滤为什么不能只靠全文正则

终端工具里的安全过滤通常是必要的。比如 nohupdisownsetsid 这类 shell-level background wrapper,确实可能让进程脱离工具跟踪,造成“命令已经启动,但 Agent 不知道它还在跑”的问题。

但如果过滤逻辑只是在整条命令字符串里搜索关键词,就会把很多正常命令误判成危险命令。

NousResearch/hermes-agent issue #20064 记录的就是这个问题:Hermes terminal tool 的安全提示函数 _foreground_background_guidance() 用了一个过于宽泛的正则,导致只要命令文本里出现 nohupdisownsetsid,即使它们只是引号里的字符串、commit message、PR body 或 Python 代码片段,也会被拦截。

这类问题适合客观看:安全边界是对的,但匹配范围太粗。


现象:命令并没有后台运行,却被要求改用 background=true

issue 里的错误提示大意是:

Foreground command uses shell-level background wrappers (nohup/disown/setsid).
Use terminal(background=true) so Hermes can track the process...

这个提示本身有合理性。

如果用户真的执行:

setsid my_server
nohup ./script.sh &
disown

那终端工具提醒改用可跟踪的 background 模式是合理的。

问题是,下面这些命令也会被拦:

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"

这些命令只是把 setsid 当作文本内容处理,不是在 shell 层启动 detached process。


根因:正则匹配的是“任意位置出现关键词”

issue 指向的正则是:

_SHELL_LEVEL_BACKGROUND_RE = re.compile(
    r"\b(?:nohup|disown|setsid)\b",
    re.IGNORECASE,
)

这个正则的语义很简单:

只要完整单词 nohup / disown / setsid 出现在命令字符串里,就算匹配。

它不会区分:

  • 关键词是不是 shell command;
  • 是否在引号内;
  • 是否在 Python / Ruby / Node 的 -c 代码中;
  • 是否只是 git commit message;
  • 是否只是 echogrep 的文本参数;
  • 是否出现在 here-document 或 PR body 中。

因此,它把“命令语法中的操作符”与“文本内容里的单词”混为一谈。

这是安全过滤里很常见的误报来源。


安全过滤要识别意图,而不是只识别单词

终端命令里,同一个字符串可能有完全不同的含义。

例如:

setsid ./server

这里 setsid 是命令名。

但:

echo "setsid ./server"

这里 setsid ./server 是输出内容。

再比如:

python3 -c "import os; os.setsid()"

这可能确实涉及进程控制,但它不是 shell-level wrapper;是否拦截应由另一类规则判断,而不是用 \bsetsid\b 粗暴匹配。

所以这个 issue 的核心不是“不要拦 setsid”,而是:

只应该拦 shell 语法层面真正作为命令使用的 nohup/disown/setsid。

更合理的匹配范围:命令开头或 shell 分隔符之后

issue 中给了一个保守方向:只在关键词作为命令出现时匹配。

例如:

_SHELL_LEVEL_BACKGROUND_RE = re.compile(
    r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\(\s*)(?:nohup|disown|setsid)\b",
    re.IGNORECASE | re.MULTILINE,
)

它的目标是匹配这些场景:

setsid my_server
nohup ./script.sh &
cmd1; setsid my_server
cmd1 && nohup ./script.sh
(cmd1; disown)

同时减少这些误报:

echo "os.setsid()"
git commit -m "replace preexec_fn=os.setsid"
gh pr create --body "removed setsid"
python3 -c "x = 'setsid'"

这个方案仍然不是完整 shell parser,但比全文关键词匹配更接近真实意图。


更稳的方案:先剥离 quoted content,再检查 shell token

如果要进一步降低误报,可以在匹配前先处理命令字符串:

1. 移除或屏蔽单引号内容
2. 移除或屏蔽双引号内容
3. 处理 here-doc / escaped characters
4. 再检查 shell command position

不过这里要注意权衡。

完整解析 shell 语法并不简单,尤其涉及:

  • nested quotes;
  • command substitution;
  • heredoc;
  • escaped newline;
  • alias / function;
  • subshell;
  • platform shell 差异。

所以实际工程里常见做法是分层:

先用保守规则减少明显误报
再给高风险命令保留人工确认或 background=true 引导
最后用测试覆盖常见文本场景

为什么这个问题影响真实工作流?

这个 bug 的影响不只是不方便。

很多开发任务会频繁在文本里提到 setsidnohupdisown

  • 写 commit message;
  • 创建 PR;
  • review 进程管理相关代码;
  • 解释 subprocess 行为;
  • grep 日志或代码;
  • 生成文档;
  • 复制错误信息。

如果这些都被安全过滤拦掉,用户会被迫绕路:

把内容先写进文件
改用临时文件传 body
避免在命令行里出现关键词

这会降低 terminal tool 的可用性,也会让用户对安全提示失去信任。

好的安全过滤应该减少危险操作,而不是把正常文本处理变成障碍。


回归测试应该覆盖哪些命令?

客观修复这类问题,测试比口头判断更重要。

应该保留会被拦截的真实风险命令:

setsid my_server
nohup ./script.sh &
cmd && disown

也要加入不应被拦截的文本场景:

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"
grep -R "setsid" .

这类测试能保证安全过滤仍然有效,同时减少实际开发中的误伤。


排查时如何判断是误报还是风险命令?

遇到提示:

Foreground command uses shell-level background wrappers

可以先问三个问题:

1. nohup / disown / setsid 是否作为命令名出现? 2. 它是否在 shell 分隔符之后被执行,例如 ; setsid&& nohup? 3. 它是不是只出现在引号、commit message、PR body、echo 文本或代码字符串里?

如果答案是第三种,通常就是误报。

如果确实要启动长期进程,则应使用工具提供的 background 模式,让 Hermes 能记录 PID、日志和生命周期。


与模型 API 配置的边界

这个 issue 位于 terminal tool 的安全过滤层:

用户命令 → terminal tool safety filter → foreground/background execution

它发生在模型调用之后、命令执行之前。

如果你的 Hermes 环境还接了 OpenAI-compatible 模型服务或 DeepAI API 中转站,模型 API 层主要负责:

Base URL / API Key / model id / streaming / timeout / usage

而 terminal 命令为什么被拦截,要看本地工具层的 safety filter 和命令解析。这样分层排查,能避免把工具误报和模型响应混在一起。


FAQ

setsid 为什么会被 Hermes terminal 关注?

因为它可以让进程创建新 session,脱离普通前台命令的跟踪。对 Agent 来说,这可能导致进程生命周期不可见。

为什么 echo "setsid" 不应该被拦?

因为这里的 setsid 只是输出文本,不是 shell 正在执行的命令。

只用正则能完全解决吗?

很难。Shell 语法复杂,正则可以覆盖常见场景,但完整准确需要更强的解析策略或分层检测。

如果我真的要启动后台服务,应该怎么做?

应使用 terminal 工具提供的 background 模式,而不是用 nohupdisownsetsid 绕过工具跟踪。

为什么这类误报会影响开发效率?

因为 commit message、PR body、代码审查、文档和 grep 中都可能出现这些关键词。误拦截会迫使用户使用临时文件或改写命令。


总结

#20064 的问题可以概括为一句话:

安全过滤想识别 shell-level background wrapper,实际却匹配了整条命令里的任意关键词。

修复方向不是取消安全过滤,而是让过滤更接近 shell 语义:

只在 nohup / disown / setsid 作为命令执行时拦截;
不要拦截引号、commit message、PR body、echo、grep 或代码字符串里的普通文本。

对终端工具来说,安全和可用性不是二选一。准确识别上下文,才是减少误报、保留防护的关键。

Related Post

DeepSeek thinking mode 报 reasoning_content 必须回传?Hermes Agent 工具调用与 fallback 排错指南DeepSeek thinking mode 报 reasoning_content 必须回传?Hermes Agent 工具调用与 fallback 排错指南

Hermes Agent 接入 DeepSeek thinking 模型时报 reasoning_content 或 content[].thinking must be passed back?这通常不是 API Key 错,而是历史 assistant message 在工具调用、fallback、cron 或 replay 时丢失 thinking 字段。本文整理 DeepSeek provider detection、reasoning_content echo 和排查清单。

Gemini 报 Multiple authentication credentials?Hermes Agent 接入 Google OpenAI-compatible 端点的排错指南Gemini 报 Multiple authentication credentials?Hermes Agent 接入 Google OpenAI-compatible 端点的排错指南

Hermes Agent 接入 Gemini provider 报 HTTP 400 Multiple authentication credentials received?这通常不是简单 API Key 粘错,而是 Google OpenAI-compatible endpoint、Bearer、x-goog-api-key、AIza legacy key 与 AQ 新 key 的认证兼容问题。本文总结排查顺序、临时绕法和 native Gemini adapter 的长期方向。

一个终端开关,为什么会让 Discord 消息开始流式编辑?一个终端开关,为什么会让 Discord 消息开始流式编辑?

Hermes issue #8338 暴露了一个配置优先级反转:display.streaming 本应只控制 CLI 终端显示,却错误覆盖了 Gateway 的 streaming.enabled: false,让 Discord、Telegram 等消息平台开始流式编辑。本文解释根因、影响和修复思路。