Workspace、Shell 与 SSRF 安全边界
对应源码
| 安全层 | 源码 |
|---|---|
| Workspace 路径和策略 | nanobot/security/workspace_access.py、workspace_policy.py |
| 网络目标与 SSRF | nanobot/security/network.py |
| 文件、Shell 和 Sandbox Tool | nanobot/agent/tools/path_utils.py、filesystem.py、shell.py、sandbox.py |
| Runner 违规分类 | nanobot/agent/runner.py::_classify_violation |
| 重复违规反馈 | nanobot/utils/runtime.py::repeated_workspace_violation_error |
学习目标
读完后,你应当能够:
- 区分 workspace scope、文件路径 guard、shell 命令 guard 和 OS sandbox;
- 解释 SSRF guard 为什么既检查 URL 又解析 DNS;
- 判断 Windows 上配置
sandbox: bwrap的实际结果; - 在新增 Tool 时列出必须继承的安全约束。
前置知识
- 理解绝对路径、相对路径、符号链接和
Path.resolve(); - 知道 SSRF 是服务端被诱导请求内网或云 metadata 的风险;
- 知道“模型生成的参数”是不可信输入,即使 system prompt 要求模型守规矩。
1. 安全边界从哪里开始
在 Agent 系统中,下面这些内容都应视为不可信:
- 用户消息;
- 网页和文档中的提示注入;
- 模型生成的 Tool 参数;
- MCP server 返回的数据;
- Channel 上传的文件名和 URL;
- 外部 Provider 的错误文本。
安全不能只写在 prompt 中。nanobot 把关键限制放到工具执行前的确定性代码里。
2. WorkspaceScope 与 ToolWorkspace
nanobot/security/workspace_access.py 定义:
WorkspaceScope:一个 Turn 的项目路径、访问模式和 sandbox 状态;WorkspaceScopeResolver:根据入口消息和 Session metadata 解析当前 scope;ToolWorkspace:Tool 执行时看到的有效根目录和限制开关;- ContextVar:把当前 Turn 的 scope 传给深层 Tool,而不需要修改每个函数参数。
默认 access mode:
restrictToWorkspace=true→restrictedrestrictToWorkspace=false→full
WebSocket 是可携带项目 scope 的入口;其他 Channel 默认使用配置的 workspace scope。具体分支在 WorkspaceScopeResolver.for_turn() 中。
3. 文件 Tool 的路径约束
文件 Tool 通过:
nanobot/agent/tools/path_utils.py::resolve_workspace_pathnanobot/security/workspace_policy.py::resolve_allowed_path
完成路径解析和 containment 检查。
允许范围不是简单字符串前缀,而是解析后的目录关系。根据具体 Tool,还可能加入:
- 只读 media 目录;
- 明确的额外允许目录;
- 精确允许的单个文件。
读根和写根可能不同。新增文件类 Tool 时,应复用统一路径 helper,不能自己写 if path.startswith(workspace)。
4. Shell guard 与 sandbox 是两层
ExecTool 在 _prepare_command() 中先做应用层检查:
- 解析当前
ToolWorkspace; - 验证
working_dir没有逃出 workspace; - 用
_guard_command()检查危险命令、路径和内网 URL; - 构造最小环境变量;
- 如配置 sandbox,再包装命令;
- 生成平台对应 subprocess。
flowchart TD A["模型生成 command"] --> B["working_dir containment"] B --> C["deny / allow pattern 与路径 guard"] C --> D["SSRF URL guard"] D --> E{"配置 sandbox?"} E -->|"否"| F["宿主 shell"] E -->|"是,Unix"| G["wrap_command"] G --> H["bwrap"] E -->|"是,Windows"| I["记录警告"] I --> F
应用级 guard 能阻止已识别的越界方式,但它不是内核强制隔离。
5. 当前 sandbox 后端
nanobot/agent/tools/sandbox.py 当前只注册:
_BACKENDS = {"bwrap": _bwrap}Bubblewrap 包装大致提供:
- workspace 读写 bind mount;
- media 目录只读;
- workspace 父目录以 tmpfs 遮蔽,从而隐藏
config.json; /tmp、/proc、/dev等有限系统视图;--die-with-parent,父进程退出时结束。
在 Windows 上,ExecTool 明确记录:
Sandbox '...' is not supported on Windows; running unsandboxed随后继续执行未经 bwrap 包装的命令。因此,在 Windows 上不能因为配置里写了 "sandbox": "bwrap" 就声称 shell 已被 OS sandbox。
必须准确表述
restrictToWorkspace开启且无系统 provider 时,workspace_sandbox_status()返回 application-level、enforced=False。这是源码对自身安全级别的明确描述。
6. Shell 的其他限制
ExecTool 还实现:
- 默认 60 秒硬超时;模型单次请求的 timeout 上限 600 秒;
- config timeout 可设为 0 表示不限制;
- 输出截断;
- allow/deny regex;
- 只转发允许的环境变量;
- 危险磁盘/删除/关机命令的默认 deny patterns;
- 禁止直接覆盖内部 memory history/cursor;
- 取消或超时时终止 subprocess。
这些限制降低风险,但不能穷举所有 shell 语义。真正高风险部署仍应依赖容器、OS sandbox、最小权限用户和网络策略。
7. SSRF guard
validate_url_target() 依次检查:
- URL scheme 只能是 HTTP/HTTPS;
- 必须有 hostname;
- 解析 hostname 的所有地址;
- 任一地址命中阻止网段即拒绝。
当前阻止范围包括:
- loopback;
- RFC 1918 私网;
- CGNAT;
- link-local;
- 常见云 metadata 地址段;
- IPv6 loopback、ULA 和 link-local。
IPv4-mapped IPv6 会先归一化,避免 ::ffff:127.0.0.1 绕过 IPv4 blocklist。
重定向目标还会由 validate_resolved_url() 再检查。配置中的 tools.ssrfWhitelist 可加入明确 CIDR,load_config() 会调用 _apply_ssrf_whitelist()。
8. 本地 WebUI 预览的窄例外
代码支持在特定 workspace scope 下访问 literal loopback,用于本地预览。allow_loopback=True 的例外非常窄:
- hostname 必须是
localhost或 literal loopback; - 所有解析地址都必须是 loopback;
- 不会因此放行 RFC 1918、link-local、metadata;
- 公网域名即使解析到 loopback也不被当成合法本地别名。
这比简单的“允许所有内网”安全得多。
9. 凭据与访问控制
学习环境中建议:
- key 放环境变量,配置只写
${DEEPSEEK_API_KEY}; - 不把解析后的 config、环境变量或完整 HTTP header 打进日志;
- WebUI/Gateway 的 token、pairing 和 Channel allowlist 按官方文档配置;
- 不把 WebUI 监听地址扩大到公网后仍沿用本机信任假设;
- MCP HTTP server 当作外部服务审查,stdio command 当作本机代码执行审查。
相关入口:
nanobot/pairing/nanobot/gateway/nanobot/channels/websocket.pynanobot/agent/tools/mcp.pynanobot/config/schema.py
10. 新 Tool 的安全清单
新增 Tool 前逐项回答:
- 参数是否经过 Pydantic/JSON Schema 校验?
- 文件路径是否用
resolve_workspace_path()? - 写操作的允许根是否比读操作更窄?
- URL 是否用
validate_url_target(),重定向是否再次检查? - 是否可能泄漏环境变量、token 或本机路径?
- 超时与取消能否清理资源?
- 失败结果是否会诱导模型绕过安全策略?
- 是否真的可以把
concurrency_safe设为True? - 是否有安全边界测试,而不只有 happy path?
11. 源码与测试
源码:
nanobot/security/workspace_access.pynanobot/security/workspace_policy.pynanobot/security/network.pynanobot/agent/tools/path_utils.pynanobot/agent/tools/filesystem.pynanobot/agent/tools/shell.pynanobot/agent/tools/sandbox.pynanobot/agent/runner.py::_classify_violationnanobot/utils/runtime.py::repeated_workspace_violation_error
测试:
tests/security/test_security_network.pytests/security/test_workspace_policy.pytests/security/test_workspace_sandbox.pytests/tools/test_filesystem_tools.pytests/tools/test_exec_platform.pytests/tools/test_exec_security.pytests/tools/test_web_fetch_security.pytests/tools/test_web_fetch_url_sanitization.py
自检
- 应用级 workspace guard 与 bwrap 的信任边界有何不同?
- 为什么 SSRF 检查不能只匹配 URL 文本中的
127.0.0.1? - Windows 上配置 bwrap 后,实际执行路径是什么?
- 为什么 prompt 中写“不要访问私有文件”不能替代路径 guard?
下一篇:打包、WebUI 构建与跨平台