
译者前言:Open Agent SDK 是一个 Swift 写的本地 AI Agent 开发框架,设计思路相当扎实——SessionStore 的 actor 隔离、PermissionPolicy 的可组合策略、路径规范化和段边界匹配防止目录遍历、HookRegistry 的 20+ 生命周期事件。这篇深入分析四个子系统的实现细节,是难得一见的深度技术文。
每次 Agent Loop 运行都会产生一个 messages 数组。如果没有持久化,进程退出时这些数据就会丢失。SessionStore 将对话历史持久化到磁盘,以便下次启动时恢复。
save — 保存会话。将 messages 数组和元数据序列化为 JSON 并写入磁盘。存储结构:~/.open-agent-sdk/sessions/my-session/transcript.json,文件权限为 0600,目录权限为 0700——只有当前用户可以读写。
load — 加载会话。从磁盘读取并反序列化为 SessionData。load 支持分页参数 limit 和 offset,用于在不需要完整历史时仅加载尾部。
list — 列出所有会话,按 updatedAt 降序排列(最新的在前)。
fork — Fork 会话。将现有会话的 messages 复制到新会话,可选指定截断点。
delete — 删除整个会话目录。
Specified sessionId Recovery:给定一个会话 ID,Agent 在启动时加载历史消息。
continueRecentSession:当你不知道会话 ID 时,让 SDK 自动查找最新的。
forkSession + resumeSessionAt:从现有会话 fork 一个新分支,可选在特定消息处截断。
SessionStore 在会话 ID 验证中包含路径遍历防护:会话 ID 不能包含 /、\ 或 ..——防止攻击者利用构造的 ID 读写意外路径。
permissionMode 是一个全局开关,粒度较粗。要按工具名称或属性进行细粒度控制,请使用 canUseTool 回调。canUseTool 返回 CanUseToolResult?。返回 nil 表示回调没有意见,交由下一个检查。
CanUseToolResult 有三个工厂方法:allow() 允许、deny("reason") 拒绝、allowWithInput(modifiedInput) 允许但修改输入参数。allowWithInput 很少见但很实用——你可以在权限检查期间修改工具输入参数,例如将文件写入路径重定向到安全目录。
编写闭包很灵活但不可复用。SDK 提供了 PermissionPolicy 协议,将权限判断封装为可组合的策略:
权限控制管理"这个工具能否执行"。沙箱管理"这个操作是否在允许范围内"。
路径和命令各有两种模式:路径的 allowedReadPaths/allowedWritePaths 是白名单(空 = 允许所有),deniedPaths 是黑名单(优先级更高);命令的 allowedCommands 是白名单,deniedCommands 是黑名单,allowedCommands 优先于 deniedCommands。
SandboxChecker 是一个无状态的 enum,提供 isPathAllowed、checkPath、isCommandAllowed、checkCommand 静态方法。
路径检查使用前缀匹配和段边界保证:关键是 SandboxPathNormalizer——先规范化路径(解析 ..、.、符号链接),然后在段边界期间确保尾部 / 进行前缀比较。路径遍历攻击会被规范化后被 deniedPaths 捕获。
命令检查有三个阶段:Shell 元字符检测(识别绕过模式如 bash -c "cmd"、$(cmd)、`cmd`)、基名提取(从 /usr/bin/rm -rf /tmp 提取 rm)、白名单/黑名单匹配。
前三个系统解决的是"能否做"。Hook 系统解决的是"何时做完"和"事前干预"。SDK 定义了 24 个生命周期事件:
Hook 有两种实现方式:函数回调和 shell 命令。Function Hook 是 Swift 闭包,适合进程内逻辑;Shell Hook 是外部命令,适合集成非 Swift 脚本。
Shell Hook 通过 ShellHookExecutor 执行:使用 /bin/bash -c 启动进程,将 HookInput 序列化为 JSON 传递到 stdin,从 stdout 读取 HookOutput JSON。Shell Hook 环境变量包括 HOOK_EVENT、HOOK_TOOL_NAME、HOOK_SESSION_ID、HOOK_CWD。
HookRegistry 是一个 actor,内部维护 HookEvent 到 HookDefinition 数组的映射。每个 HookDefinition 可以有一个 matcher(正则表达式)。执行时,input.toolName 会与 matcher 检查;不匹配的 Hook 会被跳过。
超时处理:Function Hook 使用 withThrowingTaskGroup 实现超时;Shell Hook 使用 DispatchQueue.asyncAfter 实现超时,超时时终止进程。同上事件上的 Hook 按注册顺序串行执行。
HookOutput 可以 block: true 阻止工具执行;permissionUpdate 在 Hook 执行期间动态修改工具权限;updatedInput 替换工具输入参数。
四个子系统各司其职:SessionStore 记住对话历史、PermissionPolicy 控制工具是否可以执行、SandboxSettings 限制操作范围、HookRegistry 审计和拦截。
多层防御的好处:即使某一层有配置漏洞,其他层也能提供备份。例如,如果你不小心把 Bash 加到了白名单,Hook 的 matcher 仍会拦截它。即使 Hook 也漏掉了,沙箱的命令过滤仍会阻止它。
SessionStore、PermissionPolicy、SandboxSettings 和 HookRegistry——四个系统各管一摊,组合起来形成完整的安全框架:SessionStore 的 actor 隔离和会话 ID 验证确保存储安全、PermissionPolicy 的可组合策略提供灵活的权限管理、SandboxChecker 的路径规范化和段边界匹配防止目录遍历、HookRegistry 的 matcher 过滤和超时机制确保 Hook 系统可靠。