deer-flow/docs/superpowers/specs/2026-06-19-guardrail-request-attribution-design.md
Miracle778 5a699e24a1
feat(guardrails): expose authenticated runtime context in GuardrailRequest (#3665)
* docs: guardrail runtime attribution spec

* docs: guardrail request attribution implementation plan

* feat(guardrails): add runtime user context and attribution fields to GuardrailRequest

Extend GuardrailRequest with optional runtime attribution fields so that
pluggable GuardrailProviders can access authenticated user context and
tool-call-level attribution:

- Gateway injects user_role, oauth_provider, oauth_id into runtime context
  alongside the existing user_id (server-authenticated only, client spoofing
  prevented)
- GuardrailRequest gains: user_id, user_role, oauth_provider, oauth_id,
  run_id, tool_call_id (all optional, backward compatible)
- GuardrailMiddleware reads these from ToolCallRequest.runtime.context
- thread_id now actually populated from context (was always None before)
- Tests: 15 new/expanded tests covering Gateway injection, runtime context
  reading, partial/missing fields, and client spoofing prevention
- Docs: new Runtime Attribution section in GUARDRAILS.md with provider
  example and YAML policy illustration

* fix(guardrails): propagate attribution to subagents

* fix(guardrails): complete subagent attribution propagation

---------

Co-authored-by: Miracle778 <miracle778@no-reply.com>
2026-06-21 16:08:25 +08:00

5.8 KiB
Raw Permalink Blame History

GuardrailRequest 运行时用户上下文与归因字段补充

概述

GuardrailRequest 补充可选的运行时用户上下文和工具调用归因字段,使可插拔 GuardrailProvider 能访问 DeerFlow 已认证用户、外部身份映射、run/thread/tool-call 定位信息。

本设计不新增治理系统、不定义统一 policy schema也不改变默认 allow/deny 行为。它只把 DeerFlow 运行时已经掌握的上下文传给 provider。

背景

DeerFlow 已通过 GuardrailMiddleware + 可插拔 GuardrailProvider 实现工具调用前授权。当前 GuardrailDecision 已能表达 allow/deny、原因、policy_id 和 metadata缺口在 GuardrailRequestprovider 只能看到 tool_nametool_input,无法可靠知道“谁发起了这次工具调用”以及“这次调用属于哪个 run/tool_call”。

源码中 DeerFlow 已有用户身份模型:

  • users.idDeerFlow 内部稳定用户 ID。
  • users.system_roleadmin / user
  • users.oauth_providerusers.oauth_id:未来 OAuth/SSO 外部身份映射字段local user 下可为空。

这些字段当前已存在于认证后的 request.state.user,但 run-time middleware/tool 阶段只能通过 runtime.context 访问上下文。因此应在 Gateway 构建 run config 时注入 server-authenticated user context再由 GuardrailMiddleware 消费。

改动范围

  • app/gateway/services.pyinject_authenticated_user_context() 注入更多 authenticated user context。
  • guardrails/provider.pyGuardrailRequest 新增 optional 字段。
  • guardrails/middleware.py:从 ToolCallRequest.runtime.context 读取字段。
  • tests/test_setup_agent_e2e_user_isolation.py:验证 Gateway 注入到 runtime context。
  • tests/test_guardrail_middleware.py:验证 runtime context 进入 GuardrailRequest
  • backend/docs/GUARDRAILS.md:更新 custom provider 示例。

设计

GuardrailRequest 字段

@dataclass
class GuardrailRequest:
    tool_name: str
    tool_input: dict[str, Any]
    agent_id: str | None = None
    thread_id: str | None = None
    is_subagent: bool = False
    timestamp: str = ""

    user_id: str | None = None
    user_role: str | None = None
    oauth_provider: str | None = None
    oauth_id: str | None = None
    run_id: str | None = None
    tool_call_id: str | None = None

所有新增字段均为 optional缺失时保持 NoneGuardrailDecision 不变。

字段来源

字段 来源 说明
user_id request.state.user.idruntime.context["user_id"] DeerFlow 内部稳定用户 ID
user_role request.state.user.system_roleruntime.context["user_role"] 可用于简单 role-based policy
oauth_provider request.state.user.oauth_providerruntime.context["oauth_provider"] OAuth/SSO 外部 providerlocal user 可为空
oauth_id request.state.user.oauth_idruntime.context["oauth_id"] 外部 provider subject/user idlocal user 可为空
run_id _build_runtime_context() 写入 runtime.context["run_id"] run 级审计归因
thread_id _build_runtime_context() 写入 runtime.context["thread_id"] 修正已有字段未填充问题
tool_call_id request.tool_call.get("id") 单次 tool call 定位

Gateway 注入只信任服务端认证态 request.state.user。客户端 body.context 里的 user_id/user_role/oauth_* 不应覆盖 authenticated user。

收益

稳定审计归因

user_id/run_id/thread_id/tool_call_id 让 provider 或外部审计系统能回答:

  • 哪个 DeerFlow 用户触发了 tool call
  • 哪个 run 里发生了 deny
  • 同一轮中多次同名工具调用时,具体是哪一次?

可读的本地策略示例

user_id 是 UUID不适合直接写人工 policy。user_role 可以支持简单示例:

field: role_tool_key
operator: eq
value: admin:bash

provider 可派生:

role_tool_key = f"{request.user_role or ''}:{request.tool_name}"

外部身份映射

oauth_provider/oauth_id 保留了未来接 OAuth/SSO/IAM 时的外部 subject 信息。当前 OAuth 路由仍是 placeholderlocal user 下这些字段通常为 None,但字段 optional不影响现有部署。

兼容性

  • 所有新增字段 optional。
  • 现有 provider 不读取新字段时行为不变。
  • GuardrailDecision 不变。
  • 未认证或无 runtime context 时字段为 None
  • local user 没有 OAuth 信息时 oauth_provider/oauth_idNone
  • agent_id 语义不变,仍保持现有 passport/agent hint 含义。

测试

新增或扩展以下测试:

测试 覆盖
TestConfigAssembly::test_authenticated_user_context_includes_role_and_oauth_identity Gateway 将 user_id/user_role/oauth_provider/oauth_id 注入 runtime context
TestConfigAssembly::test_client_supplied_user_id_is_overridden 客户端伪造 identity context 不覆盖服务端认证态
TestGuardrailRequestAttribution::test_authenticated_user_context_present runtime.context 中用户上下文进入 GuardrailRequest
TestGuardrailRequestAttribution::test_all_attribution_fields_present 用户上下文 + run/tool_call 归因字段同时传递
缺失 runtime/context 测试 字段保持 None,向后兼容

验证命令:

cd backend
PYTHONPATH=. uv run pytest \
  tests/test_guardrail_middleware.py::TestGuardrailRequestAttribution \
  tests/test_setup_agent_e2e_user_isolation.py::TestConfigAssembly -v

未涉及

  • 不新增 central governance subsystem。
  • 不新增 DeerFlow 内置 policy schema。
  • 不修改 MCP 配置机制。
  • 不修改 OAuth/SSO 实现状态。
  • 不让 GuardrailMiddleware 直接依赖 FastAPI request、DB 或 auth repository。