§12 护栏与权限沙箱
DeerFlow 在工具调用真正发生前,会过一道护栏。这是它敢默认放开工具调用的原因。
GuardrailMiddleware + GuardrailProvider
架构是「中间件 + 可插拔 provider」:
GuardrailMiddleware:在工具调用前拦截,构造GuardrailRequest,交给 provider 判定。GuardrailProvider:你实现的判定逻辑,返回GuardrailDecision。
GuardrailRequest 携带的字段(2.0 已补全运行时归因):
@dataclass
class GuardrailRequest:
tool_name: str
tool_input: dict
agent_id: str | None = None
thread_id: str | None = None
is_subagent: bool = False
timestamp: str = ""
user_id: str | None = None # DeerFlow 内部稳定用户 ID
user_role: str | None = None # admin / user
oauth_provider: str | None = None # 未来 OAuth/SSO
oauth_id: str | None = None
run_id: str | None = None # run 级审计
tool_call_id: str | None = None # 单次调用定位
GuardrailDecision 表达 allow / deny、原因、policy_id、元数据。
身份只信服务端
关键安全点:Gateway 注入只信任服务端认证态 request.state.user。客户端 body 里伪造的 user_id / user_role / oauth_* 不会覆盖服务端身份。这堵死了「假装是 admin」的越权路。
第二道护栏:ReadBeforeWriteMiddleware
代码写入还有一道新鲜度护栏。规则很硬:
read_file成功 → 记一个 read-mark:sha256(当前完整文件内容)。- 改已存在文件(
write_file覆盖 /append/str_replace)前,校验「最新 read-mark 的 hash == 当前文件 hash」。 - 不匹配 → 拦截,不执行写入,返回「请先读该文件」。
- 写入成功会立刻让上一次 read-mark 过期 → 连续改之间被逼着重读。
- 写不存在的新文件 → 放行。
它默认开启,挂在中间件链尾、SandboxAudit 之后。对并行同文件写入,用 per-path 锁保证确定性拦截。
标叔的结论:DeerFlow 的安全是「双层 + 默认开」。护栏管「能不能调」,读写门管「改文件前看没看」。你做自己的 provider,至少把 user_role 用起来——比如 admin:bash 才放行危险工具。
能力讲完,来到界面与流式。下一章讲终端工作台。