Hermes Agent 安全加固与生态扩展:2026-04-23 更新解析

25 阅读5分钟

作者:张大鹏 发布时间:2026-04-23 字数:5634 预计阅读时间:9 分钟


引言

昨天刚研究完 Hermes Agent 的传输层重构,今天上游又双叒叕更新了。

这次的画风不太一样——不是大架构改动,而是安全加固 + 生态扩展。200+ commit 里面,我挑了跟日常使用最相关的几个点来研究:

  • Agent 创建的 skill 终于不再被关键词扫描误杀了
  • Session split-brain 问题被彻底修掉
  • MCP toolset 继承默认开启
  • xAI Grok STT 和 OpenAI Codex 图像插件来了
  • 开发工具链终于加上 ruff + mypy

本文是我的研究笔记。


1. 安全机制终于不扰民了

1.1 之前的痛点

之前的 skills_guard 扫描逻辑比较死板:agent 创建的 skill 内容如果包含 rm -rfcurl | bash 这类关键词,直接被 block。

但问题是——正常写 skill 文档经常要引用这些危险命令。比如写个「如何安全地清理临时文件」的 skill,里面提到 rm -rf /tmp/* 再正常不过了,结果被扫描拦下。

1.2 新增配置开关

这次新增了 config.skills.guard_agent_created 配置项,默认关闭

# config.yaml
skills:
  guard_agent_created: false  # 默认关闭,agent 创建的 skill 不扫描

核心逻辑变更:

# tools/skills_guard.py
def should_scan(action: str, source: str) -> bool:
    if action in ('create', 'edit', 'patch'):
        if source == 'agent_created':
            return config.get('skills.guard_agent_created', False)
    return True  # 其他情况继续扫描

Rationale:如果 agent 本身已经被攻陷,它完全可以通过 terminal() 直接执行危险命令,扫描 skill 内容没有额外的安全价值,只是徒增误杀。

1.3 SSRF 防护可配置

之前 tirith 安全扫描默认禁止访问私有/内网 IP,这次新增了全局开关:

security:
  allow_private_url_resolution: true  # 默认 false,开启需谨慎

2. Gateway Session 并发安全

2.1 那个让你头疼的 split-brain

Session split-brain 是 Hermes Agent gateway 的老问题了:并发场景下多个请求操作同一个 session,导致状态不一致。

这次一口气修了三个相关 bug:

Commit修复内容
d72985b7序列化 reset 命令的 hand-off,修复竞争条件
b7bdf32dstop/reset 后保护 session slot 所有权
5651a733finally-block 中 guard _active_sessions 删除

2.2 修复细节

核心改进是 reset 命令的 hand-off 序列化

# gateway/session.py
async def reset_session(self, session_id: str):
    async with self._session_lock(session_id):
        # 1. 先标记为 resetting
        await self._mark_resetting(session_id)
        # 2. 等待当前请求完成
        await self._drain_pending_requests(session_id)
        # 3. 最后才执行真正的 reset
        await self._do_reset(session_id)

之前的问题是 reset 和正常请求并发执行,导致状态机混乱。现在 reset 必须等所有 pending 请求完成才能执行。

2.3 新增回归测试

这次还专门写了 #11016 split-brain session locks 的回归测试,确保这个问题不会复发:

async def test_split_brain_resession_locks():
    """并发 reset + 正常请求不应该死锁"""
    session = await gateway.create_session()

    async def concurrent_reset():
        await asyncio.gather(*[session.reset() for _ in range(5)])

    async def concurrent_send():
        await asyncio.gather(*[session.send("ping") for _ in range(10)])

    await asyncio.gather(concurrent_reset(), concurrent_send())
    # 不应该死锁,不应该抛出异常

3. Terminal 环境变量修复

3.1 nvm/n 用户的天坑

之前在 Docker 或远程 SSH 环境里用 Hermes Agent,nvm/n 管理的 Node.js 版本经常找不到。原因是 ~/.profile~/.bash_profile 没有被 source。

这次修了:

# tools/environments/local.py
async def _build_env(self) -> dict:
    env = os.environ.copy()

    # 自动 source 登录 shell 配置文件
    for rc_file in ['~/.bash_profile', '~/.profile', '~/.bashrc']:
        rc_path = Path(rc_file).expanduser()
        if rc_path.exists():
            result = await self._run_shell(f'source {rc_path} && env')
            # 解析输出,更新 env
            ...

    return env

现在 nvm/n 的 PATH 在 terminal tool 中也能保持了。


4. MCP Toolset 继承默认开启

4.1 之前的问题

之前的 delegate_task 默认 不继承 父 agent 的 MCP toolsets,导致子 agent 经常找不到父 agent 配置好的 MCP 服务器。

4.2 新行为

# tools/delegate_tool.py
DEFAULT_CONFIG = {
    'inherit_mcp_toolsets': True,  # 默认开启
}

子 agent 自动继承父 agent 的 MCP 配置:

# config.yaml
tools:
  delegate:
    inherit_mcp_toolsets: true  # 默认 true
    # 或者在调用时指定
    # { "task": "分析代码", "inherit_mcp_toolsets": false }

5. 图片生成生态扩展

5.1 OpenAI Codex 插件

之前 Hermes Agent 只支持 fal 的图片生成,这次新增了 OpenAI Codex 的 gpt-image-2 支持:

# config.yaml
image_gen:
  provider: openai-codex
  # 需要配置 Codex OAuth
  codex_client_id: your_client_id
  codex_client_secret: your_secret
# plugins/image_gen/openai-codex/__init__.py
class OpenAICodexProvider(ImageGenProvider):
    """OpenAI Codex 图片生成(通过 OAuth)"""

    async def generate(self, prompt: str, aspect_ratio: str = "1:1", **kwargs) -> str:
        # 使用 Codex OAuth 获取 access token
        token = await self._get_oauth_token()
        # 调用 Codex Images API
        response = await self._call_codex_api(prompt, token)
        return response.data[0].url

5.2 长生命周期 Session 插件刷新

之前一个 session 运行几小时后,图片生成插件的 provider 配置可能过期。这次加了强制刷新逻辑:

# tools/image_generation_tool.py
async def generate(self, prompt: str, ...):
    # 检测 provider 是否需要刷新
    if self._provider.should_refresh():
        await self._provider.refresh()
    return await self._provider.generate(prompt, ...)

6. 消息平台新成员

6.1 xAI Grok STT

新增 xAI Grok 的语音转文字 provider:

# config.yaml
stt:
  provider: xai-grok
  api_key: xai-your-api-key
# agent/transcription.py
class XAIGrokSTTProvider(BaseSTTProvider):
    async def transcribe(self, audio_data: bytes) -> str:
        response = await self.client.audio.transcriptions.create(
            model="grok-stt",
            file=("audio.wav", audio_data, "audio/wav")
        )
        return response.text

6.2 MiniMax-AI/cli Skill

新增 MiniMax-AI/cli 到默认 skill tap:

# 使用方式
/hermes skill install MiniMax-AI/cli

7. 开发工具链完善

7.1 ruff + mypy 加入 dev 依赖

之前项目的 linting 和 type checking 比较松散,这次终于规范化了:

# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py310"

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

现在开发流程:

# lint
ruff check .

# type check
mypy hermes_agent/

# 或者一键
make check

7.2 Windows 兼容性改进

os.kill(pid, 0) 在 Windows 上会抛出 OSError,这次加了兼容处理:

import platform

def is_process_running(pid: int) -> bool:
    if platform.system() == 'Windows':
        # Windows 用 signal 0 不可靠
        import ctypes
        return ctypes.windll.kernel32.OpenProcess(1, False, pid) != 0
    else:
        try:
            os.kill(pid, 0)
            return True
        except OSError:
            return False

总结

本次更新要点

类别改动
安全skills-guard 默认不扫描 agent 创建的 skill;SSRF 防护可配置
GatewaySession split-brain 彻底修复;reset hand-off 序列化
Terminal自动 source ~/.profile,nvm/n PATH 保持
MCPinherit_mcp_toolsets 默认开启
图像OpenAI Codex 插件;长生命周期 session 插件刷新
语音xAI Grok STT provider
SkillMiniMax-AI/cli 加入默认 tap
DevOpsruff + mypy 加入 dev 依赖;Windows 兼容修复

评价

这次更新没有大架构改动,但刀法精准——全是日常使用中的痛点。Session split-brain 修了好几个版本终于修干净了,skills-guard 的误杀问题也算是还开发者一个清净。

唯一要提醒的是 SSRF 防护可配置 这个开关——开启前请确认你的使用场景真的需要解析内网 URL,否则相当于把门拆了。


相关资料