Tool Permission 与 Sandbox 安全流水线
1. 为什么工具系统是 Agent 最危险的部分
一个只会聊天的模型,最多是说错话。一个能调用工具的 Agent,可能会删文件、跑命令、发消息、调用外部 API、读取密钥、修改代码。
所以生产级 Agent 的安全边界,不在 prompt,而在工具系统。
可以用一句话概括:
模型负责提出动作,Harness 负责判断能不能做。Hermes 的源码非常适合说明这件事。它不是把工具写成一堆散函数,而是有完整的工具注册、工具集过滤、危险命令审批、沙箱执行、MCP 工具接入和凭证隔离机制。
2. 第一层:ToolRegistry 让工具先变成“受管对象”
Hermes 的工具注册中心在tools/registry.py。文件开头就说明了设计:
"""Central registry for all hermes-agent tools. Each tool file calls registry.register() at module level to declare its schema, handler, toolset membership, and availability check. """核心注册结构可以简化为:
classToolEntry:def__init__(self,name,toolset,schema,handler,check_fn,requires_env,is_async,description,emoji,max_result_size_chars=None):self.name=name self.toolset=toolset self.schema=schema self.handler=handler self.check_fn=check_fn self.requires_env=requires_env self.is_async=is_async self.description=description self.emoji=emoji self.max_result_size_chars=max_result_size_chars这说明工具不是普通函数,而是带元数据的受管对象。
一个工具至少要说清楚:
- 它叫什么;
- 属于哪个 toolset;
- 参数 schema 是什么;
- 由哪个 handler 执行;
- 需要哪些环境变量;
- 是否可用;
- 最大输出多大。
对新手来说,可以把ToolRegistry理解成“工具仓库管理员”。模型不能随便拿工具,必须先通过这个仓库。
3. 第二层:工具自动发现
Hermes 不是在model_tools.py里手写一个巨大工具列表,而是扫描tools/*.py,找模块顶层是否调用了registry.register(...):
def_module_registers_tools(module_path:Path)->bool:source=module_path.read_text(encoding="utf-8")tree=ast.parse(source,filename=str(module_path))returnany(_is_registry_register_call(stmt)forstmtintree.body)然后导入这些模块:
defdiscover_builtin_tools(tools_dir=None):module_names=[f"tools.{path.stem}"forpathinsorted(tools_path.glob("*.py"))ifpath.namenotin{"__init__.py","registry.py","mcp_tool.py"}and_module_registers_tools(path)]formod_nameinmodule_names:importlib.import_module(mod_name)这个设计有两个好处。
第一,新增工具不用改中心文件,只要在工具文件里注册。
第二,只有真正声明了注册行为的文件才会被加载,减少误导入。
4. 第三层:toolset 控制模型能看到哪些工具
工具注册后,并不代表模型一定能用。toolsets.py把工具按场景分组:
TOOLSETS={"web":{"description":"Web research and content extraction tools","tools":["web_search","web_extract"],"includes":[]},"terminal":{"description":"Terminal/command execution and process management tools","tools":["terminal","process"],"includes":[]},"file":{"description":"File manipulation tools","tools":["read_file","write_file","patch","search_files"],"includes":[]},}model_tools.py会根据 enabled / disabled toolsets 计算最终工具集合:
ifenabled_toolsetsisnotNone:fortoolset_nameinenabled_toolsets:resolved=resolve_toolset(toolset_name)tools_to_include.update(resolved)filtered_tools=registry.get_definitions(tools_to_include,quiet=quiet_mode)通俗解释:工具不是全员上桌,而是按场景发通行证。
如果你做客服 Agent,可能只给知识库和工单工具;如果做代码 Agent,才给文件、终端、测试工具。
5. 第四层:工具 schema 会动态修正
Hermes 里一个很专业的细节是:工具 schema 不是完全静态的。
例如execute_code工具可能允许在沙箱里调用部分工具,但这必须根据当前会话真实可用工具来决定:
if"execute_code"inavailable_tool_names:sandbox_enabled=SANDBOX_ALLOWED_TOOLS&available_tool_names dynamic_schema=build_execute_code_schema(sandbox_enabled,mode=_get_execution_mode())为什么要这么做?
因为如果 schema 里写着“你可以用 web_search”,但实际 web_search 没有配置 API key,模型就容易幻觉调用不可用工具。Hermes 通过动态 schema 降低这种错配。
这就是生产系统和 demo 的差别:demo 只求跑通,生产系统要减少模型误会。
6. 第五层:危险命令审批
Hermes 的危险命令审批在tools/approval.py。文件开头写得很清楚:
"""Dangerous command approval -- detection, prompting, and per-session state. This module is the single source of truth for the dangerous command system: - Pattern detection - Per-session approval state - Smart approval via auxiliary LLM - Permanent allowlist persistence """核心流程可以理解成:
defcheck_dangerous_command(command,env_type,approval_callback=None):is_dangerous,pattern_key,description=detect_dangerous_command(command)ifnotis_dangerous:return{"approved":True}choice=prompt_dangerous_approval(command,description)ifchoice=="deny":return{"approved":False,"message":"BLOCKED: User denied this potentially dangerous command"}return{"approved":True}真实源码比这个复杂,支持:
manual:人工审批;smart:辅助 LLM 判断低风险命令是否可自动批准;off:关闭审批;yolo:当前 session 放行;once / session / always / deny多种用户选择。
新手要记住:危险命令审批不是“模型自己判断危险不危险”,而是代码里的安全模块判断。
7. 第六层:会话级审批隔离
Hermes 使用contextvars绑定当前审批会话:
_approval_session_key=contextvars.ContextVar("approval_session_key",default="",)defset_current_session_key(session_key:str):return_approval_session_key.set(session_keyor"")为什么重要?
因为 Hermes 可以跑 CLI、gateway、多平台消息、子 Agent。多个会话可能同时等待审批。如果审批状态用全局变量乱放,就可能出现 A 会话批准了命令,B 会话误以为自己也被批准。
contextvars的作用就是让并发上下文里的“当前会话”不串线。
8. 第七层:终端执行有沙箱后端
Hermes 的tools/terminal_tool.py文件开头说明:
Supports local execution, containerized backends, and Modal cloud sandboxes.这说明终端工具不只是subprocess.run()。Hermes 会根据配置选择执行环境,管理 sandbox 生命周期,处理后台进程、工作目录、清理逻辑等。
生产场景里,终端工具最危险。Hermes 的做法是把它包成受控服务:
模型提出 command -> approval.py 判断是否危险 -> terminal_tool 选择执行环境 -> sandbox 执行 -> 输出清洗和截断 -> 返回 tool result如果用普通人的话说:模型不是直接摸服务器,而是隔着一个带门禁的操作台。
9. 第八层:凭证不能随便进沙箱
Hermes 里有tools/credential_files.py和tools/env_passthrough.py。
这两个模块非常现实。很多安全事故不是因为命令本身有多危险,而是因为命令拿到了不该拿的凭证。
例如:
- 沙箱需要某个 API key 才能运行测试;
- 远程环境需要少量 credential file;
- 但不能把整个 home 目录挂进去;
- 也不能把所有环境变量透传进去。
所以 Hermes 用专门模块管理“哪些凭证可以进沙箱”。这比简单说“我们有沙箱”更专业。
10. 第九层:MCP 工具也进入统一注册
Hermes 的tools/mcp_tool.py很大,说明 MCP 不是随便拼接进去的。
源码里能看到 MCP 相关能力:
- 连接 MCP server;
- OAuth 恢复;
- HTTP / stdio transport;
- 工具发现;
- 工具名冲突保护;
- 动态注册和注销;
- 断线重连;
- 关闭时清理子进程。
MCP server 提供的工具最终也会注册进 Hermes 的 registry。也就是说,外部工具进入系统后,也要经过 Hermes 的工具生命周期管理。
11. 给新手的完整心智模型
可以把 Hermes 工具系统想成一栋工厂:
ToolRegistry是工具仓库;toolsets.py是工种权限表;model_tools.py是调度员;approval.py是安检;terminal_tool.py是危险设备操作间;- sandbox 是隔离车间;
- MCP 是外部供应商接入通道;
- credential/env 管理是钥匙柜。
模型不是老板,不是它说用什么就用什么。模型只是提出需求,Harness 才决定能不能执行。
12. 结论
结合 Hermes 源码看,工具系统的专业性体现在:
- 工具统一注册;
- 工具按 toolset 暴露;
- schema 根据可用能力动态修正;
- Agent-level 工具由主循环截获;
- 高风险命令进入审批;
- 沙箱执行隔离副作用;
- 凭证透传受控;
- MCP 工具纳入统一生命周期。
所以,Tool Permission 与 Sandbox 不是可选增强,而是 Agent 从玩具走向生产的底线工程。
参考资料
- Hermes 源码:
tools/registry.py - Hermes 源码:
model_tools.py - Hermes 源码:
toolsets.py - Hermes 源码:
tools/approval.py - Hermes 源码:
tools/terminal_tool.py - Hermes 源码:
tools/code_execution_tool.py - Hermes 源码:
tools/mcp_tool.py - MCP Security Best Practices: https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices
- OWASP Top 10 for LLM Applications: https://owasp.org/www-project-top-10-for-large-language-model-applications/
