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

135 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# GuardrailRequest 运行时用户上下文与归因字段补充
## 概述
`GuardrailRequest` 补充可选的运行时用户上下文和工具调用归因字段,使可插拔 `GuardrailProvider` 能访问 DeerFlow 已认证用户、外部身份映射、run/thread/tool-call 定位信息。
本设计不新增治理系统、不定义统一 policy schema也不改变默认 allow/deny 行为。它只把 DeerFlow 运行时已经掌握的上下文传给 provider。
## 背景
DeerFlow 已通过 `GuardrailMiddleware` + 可插拔 `GuardrailProvider` 实现工具调用前授权。当前 `GuardrailDecision` 已能表达 allow/deny、原因、policy_id 和 metadata缺口在 `GuardrailRequest`provider 只能看到 `tool_name``tool_input`,无法可靠知道“谁发起了这次工具调用”以及“这次调用属于哪个 run/tool_call”。
源码中 DeerFlow 已有用户身份模型:
- `users.id`DeerFlow 内部稳定用户 ID。
- `users.system_role``admin` / `user`
- `users.oauth_provider``users.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.py``inject_authenticated_user_context()` 注入更多 authenticated user context。
- `guardrails/provider.py``GuardrailRequest` 新增 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 字段
```python
@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缺失时保持 `None``GuardrailDecision` 不变。
### 字段来源
| 字段 | 来源 | 说明 |
|------|------|------|
| `user_id` | `request.state.user.id``runtime.context["user_id"]` | DeerFlow 内部稳定用户 ID |
| `user_role` | `request.state.user.system_role``runtime.context["user_role"]` | 可用于简单 role-based policy |
| `oauth_provider` | `request.state.user.oauth_provider``runtime.context["oauth_provider"]` | OAuth/SSO 外部 providerlocal user 可为空 |
| `oauth_id` | `request.state.user.oauth_id``runtime.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` 可以支持简单示例:
```yaml
field: role_tool_key
operator: eq
value: admin:bash
```
provider 可派生:
```python
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_id``None`
- `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`,向后兼容 |
验证命令:
```bash
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。