安全边界:先读这个,再信任代码
本教程教的是桌面 agent 安全层的结构:权限门、hook、审计哈希链、路径保护、大输出外部化。这些机制都值得学,但教学 harness 不是生产沙盒。把 code.py 直接当成真实安全产品来跑,是这个仓库最不希望读者产生的误解。
这份文档把边界说清楚:当前代码挡得住什么,故意没有挡什么,真正上线还缺什么。
为什么这不是假问题
近年的公开 agent / skill 供应链事件已经说明:一旦 agent 能加载第三方技能、运行命令、读取本地文件,安全层就不再是“附加功能”,而是产品本体。字符串黑名单只能当安全带,不能当墙。
本教程保留字符串规则,是为了让读者先看懂权限判断的位置和数据流;真正的隔离必须下沉到 OS / 容器 / 沙盒边界。
教学 harness 已经保护的东西
- 明显危险命令:S04 会不可覆盖地拒绝
sudo、递归强制删除、格式化、关机重启和典型设备写入;其他未证明只读的 shell 命令进入用户审批。mini_workbuddy仍采用更小的首 token deny-list。见s04_permission_hooks和mini_workbuddy/tools.py::_check_command。 - 工作区路径逃逸:S04 的文件工具 policy 与
mini_workbuddy.read_file都会解析真实路径,拒绝绝对外部路径、..穿越和符号链接逃出 workspace/sessioncwd。相关测试在test_permission_gates.py、test_mini_workbuddy.py和test_security_regressions.py。 - 无法安全解析的命令:例如不平衡引号。现在会 fail closed,返回拒绝,而不是崩溃或继续执行。
- bash 子进程直接继承宿主凭据的风险:S02 与
mini_workbuddy会重新构造子进程环境,只保留PATH、语言区域和临时目录变量,并把HOME、PWD指向当前工作区。OPENAI_API_KEY、ANTHROPIC_API_KEY、SSH_AUTH_SOCK及任意未列入允许列表的父进程变量不会传入工具进程。相关测试在test_s02_tool_dispatch.py和test_mini_workbuddy.py。 - 审计链的并发追加、崩溃恢复与篡改检测:
AuditLog先用进程内锁串行化同一服务中的线程,再用文件锁协调共享目录的进程;它在锁内校验索引、哈希链和 head anchor,发现损坏就拒绝继续写。新条目以O_APPEND写入并fsync,随后原子替换记录条目数和链尾哈希的audit.head。若进程在两次持久化之间退出,HarnessRuntime只会在完整哈希链有效、旧 anchor 精确指向倒数第二条且仅多一条记录时推进 anchor。哈希链检测历史条目被修改,head anchor 检测删尾截断;裸哈希链做不到后者,因为任何合法前缀本身仍是一条合法链。 - 运行时 Transcript 的完整记录边界:
mini_workbuddy.storage为每条事件写入schema_version、单调sequence、稳定event_id和session_id,在同一进程内串行化同一会话的追加,并以O_APPEND、完整写入和fsync持久化。读取时会校验完整 JSON 行的序号、会话和事件 ID;只有没有换行的最后一段会被视为尚未提交的崩溃尾行,并在下一次追加前删除。完整坏行不会被静默跳过。MiniAgent在执行工具前先持久化带tool_call_id的tool_call,再写入引用同一 ID 的tool_result或tool_error;Audit 与 SSE 只在相应 Transcript 事件落盘后发布,并引用其event_id。 - 工具执行与后续记录的异常边界:
MiniAgent只把ToolRegistry.run()抛出的已知工具异常转成tool_error。工具返回后的结果落盘、SSE 发布或 Audit 追加失败时,原始异常向调用方传播;不会把已执行的工具改记为失败,也不会自动重跑。已落盘记录保留,但这些步骤不是跨组件事务;只有tool_call而没有结果时,不能据此断言工具未执行。具体例子见 集成 demo 的异常说明。 - 上下文洪泛:超过阈值的大工具输出会写入文件,prompt 里只保留预览和指针,避免多 MB 输出挤爆上下文窗口。
教学 harness 明确挡不住的东西
这些边界不是疏忽,而是刻意写进文档和测试里的教学边界。
- 绕过字符串黑名单的间接命令:
find . -delete、echo x | xargs rm、写脚本再执行、base64 解码 payload、环境变量伪装等,都不是简单命令字符串规则能可靠拦住的。生产系统必须靠 OS 级隔离。 - Prompt injection:恶意文件、网页、工具结果都可以夹带“忽略上文、泄露密钥、调用危险工具”之类指令。当前教程只是承认这个攻击面,还没有专门做攻击/防御章节。
- 恶意 Skill / MCP Connector:s16/s17 演示的是加载机制,不包含签名验证、静态扫描、声明式权限 diff、沙盒试跑。要上生产,这些都不能省。
- 环境变量之外的密钥读取路径:过滤环境变量只能阻止 bash 直接继承宿主凭据。如果工作区里存在
.env,或者当前系统用户仍能读取工作区外的凭据文件,命令依然可能主动读取;这需要文件系统和 OS 级沙盒继续约束。 - 网络出口控制:教学版没有网络 allow-list。能跑 shell 的 agent 理论上也能访问网络。
- 审计日志的模糊崩溃状态:
audit.jsonl和audit.head无法组成一次文件系统事务。教学 harness 能恢复“旧 anchor 后恰好多一条完整有效记录”这一种可证明状态;多条未锚定记录、anchor 未指向倒数第二条、记录损坏或非法 JSON 都会阻止运行时启动,需要人工核验。文件锁也只协调遵守同一协议的进程,不能阻止恶意进程直接改文件。 - 同一会话被多个运行时同时写入:Transcript 锁解决的是
SafeThreadingHTTPServer同一进程内的并发请求,不协调两个独立进程。教学架构假设一个 session 只归一个运行时所有;生产实现必须用单写者租约、进程级锁或事务型存储强制这个所有权边界。
如果要走向生产,最低门槛是什么
- OS 级工具沙盒:容器、受限用户、命名空间、seccomp 或同等级机制。字符串规则只能作为沙盒内的快速拦截。
- 把环境过滤覆盖到全部外部进程:当前 S02 与
mini_workbuddy的 bash 已只传必要环境;生产实现还必须对 sidecar、MCP server 和其他连接器采用同样的默认拒绝策略,并逐项声明确实需要的变量。 - 技能/连接器信任流水线:静态扫描、沙盒试跑、声明式权限、签名或来源校验。
- Prompt injection 防御姿态:工具结果和文件内容默认不可信;危险工具必须保持人类审批,模型不能替人点击“允许”。
- 链外审计锚定:教学版用本地 head anchor;生产环境应追加到远端或不可变存储,否则攻击者控制本机后仍可能同时行动和改记录。
教学代码的价值是让你看见这些机制的位置。真正上线,需要把它们换成更硬的边界。