审批流水线、沙箱重构、钩子系统增强、ToolExecutor 改造为 Effect Service#21
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景与问题
closes: #19
1. 安全架构的玩具级现状
整个安全链路只有一个点:
DefaultSandbox.allowCommand()的command.includes()字符串黑名单检查。攻击面:路径规范化绕过(
rm -rf /home/./user/../)、参数顺序变换、管道拆分、alias 替换。这不是沙箱,是玩具。2. 审批模块不存在
当前审批逻辑完全缺失:
3. 钩子系统只有"通知"没有"决策"
tool.execute.*已接入;llm.request.*和session.save.*有定义无调用HookHandler = (payload) => void,无法返回 deny/allow/modifiedInputtool.execute.before(通知性质),没有审批流水线中的决策钩子4. ToolExecutor 脱离 Effect 体系
ToolExecutor是普通 class + 构造函数注入:连锁问题:
HookService被迫开"后门" — 暴露emitSync专门给ToolExecutor使用cli.ts手动 new 对象 — 完全绕过 Layer 体系ServerDeps全部any类型 — 通过 Hono context 传递,零类型安全方案
总体架构
关键设计决策
为什么 Hook 不是 Layer 1? 系统硬规则和只读白名单是不可协商的安全底线。Hook 在系统处理完明确的 deny/allow 之后再介入,职责清晰。
为什么 Permission Mode 在 Layer 3? 它是全局短路策略(bypass 跳过后续所有层、plan 模式直接拒绝写操作),适合在规则引擎和只读白名单之后立即拦截。
为什么 Layer 6 是审计日志而不是 Deny Fallback? "超时拒绝"只是 User Confirmation 层的超时行为,不构成独立语义。审计日志确保无论决策来源,最终都经过统一记录点。
修改范围
新增文件(11 个)
src/approval/types.tssrc/approval/rule-engine.tssrc/approval/presets.tssrc/approval/confirmation.tssrc/approval/pipeline.tssrc/approval/index.tstest/approval/rule-engine.test.tstest/approval/pipeline.test.tstest/approval/presets.test.tstest/hooks/decision.test.tstest/sandbox/wrap-command.test.ts修改文件(18 个)
src/sandbox/index.tsSandboxinterface +DefaultSandboxclass,改为SandboxService(Effect)。动态加载@anthropic-ai/sandbox-runtime,不可用时降级为传统执行src/hooks/registry.tsHookDecision类型、registerDecision()(支持 priority/source)、emitDecision()(按优先级裁决,首次非 null 即停止)、tool.execute.denied/tool.approval.pre/tool.approval.post三个新钩子点。删除emitSync后门src/tools/executor.tsToolExecutorclass,改为ToolExecutorService(Effect)。插入审批流水线 + PreToolUse 决策钩子。所有依赖通过yield*解析src/agent/agent.tsAgentService内yield* ToolExecutorService+ToolService,runStream()去掉 executor 参数。Bash 工具串行执行(避免竞态),其他工具并行src/orchestrate.tssendMessage去掉 executor/hooks 参数src/cli.tsnew DefaultSandbox()和new ToolExecutor(...),全部走 Layer 装配src/server/index.tsServerDeps从{ llm, executor, hooks }缩减为{ llm }src/server/routes/messages.tssrc/layer.tssrc/index.tsToolExecutorService替代ToolExecutorREADME.mdCLAUDE.mdpackage.jsonoptionalDependencies: { "@anthropic-ai/sandbox-runtime": "^1.0.0" }文件统计
预期收益
安全
rm -rf /*等模式匹配,不可绕过架构
emitSync后门cli.ts不再手动 new 对象,依赖图在layer.ts集中管理any变为精确类型开发者体验
未来扩展点
明确不在本次范围的后续方向:
ML 分类器(Layer 3 预留位置):当前六层流水线中 Rule Engine 和 Readonly Whitelist 之间可以插入 ML 分类器,基于历史决策学习自动判断风险。需要额外的 LLM 调用成本和模型配置,当前不引入。
子 Agent 委派工具:审批模块就绪后,可以为子 Agent 分配独立的审批策略(如子 Agent 只读、主 Agent 全权)。
cgroup 资源限制:
@anthropic-ai/sandbox-runtime只负责隔离,不负责资源限制。后续可按需封装 cgroup v2(memory.max、cpu.max)。审批规则持久化:当前 Always/Never 规则仅在运行时内存中生效,后续可持久化到文件(如
.codingcode/approval-rules.yaml)。