# 安全边界：先读这个，再信任代码

本教程教的是桌面 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/session `cwd`。相关测试在 `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 的异常说明](../examples/mini_workbuddy_demo/README.md#工具失败与记录失败有什么区别)。
- **上下文洪泛**：超过阈值的大工具输出会写入文件，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 只归一个运行时所有；生产实现必须用单写者租约、进程级锁或事务型存储强制这个所有权边界。

## 如果要走向生产，最低门槛是什么

1. **OS 级工具沙盒**：容器、受限用户、命名空间、seccomp 或同等级机制。字符串规则只能作为沙盒内的快速拦截。
2. **把环境过滤覆盖到全部外部进程**：当前 S02 与 `mini_workbuddy` 的 bash 已只传必要环境；生产实现还必须对 sidecar、MCP server 和其他连接器采用同样的默认拒绝策略，并逐项声明确实需要的变量。
3. **技能/连接器信任流水线**：静态扫描、沙盒试跑、声明式权限、签名或来源校验。
4. **Prompt injection 防御姿态**：工具结果和文件内容默认不可信；危险工具必须保持人类审批，模型不能替人点击“允许”。
5. **链外审计锚定**：教学版用本地 head anchor；生产环境应追加到远端或不可变存储，否则攻击者控制本机后仍可能同时行动和改记录。

教学代码的价值是让你看见这些机制的位置。真正上线，需要把它们换成更硬的边界。
